这里会显示出您选择的修订版和当前版本之间的差别。
| 两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
|
2020-2021:teams:legal_string:组队训练比赛记录:contest4 [2021/07/20 17:29] jxm2001 |
2020-2021:teams:legal_string:组队训练比赛记录:contest4 [2021/07/25 00:00] (当前版本) jxm2001 [题解] |
||
|---|---|---|---|
| 行 2: | 行 2: | ||
| ====== 题解 ====== | ====== 题解 ====== | ||
| + | |||
| + | ===== A. Arithmetic Progression ===== | ||
| + | |||
| + | ==== 题意 ==== | ||
| + | |||
| + | 给定一个序列,问有多少连续子串从小到大排列后可以构成等差序列。 | ||
| + | |||
| + | ==== 题解 ==== | ||
| + | |||
| + | 给定一个序列 $a_1,a_2\cdots a_n$,则 $\max(a_i)-\min(a_i)\ge (n-1)\times \text{gcd}(a_2-a_1,a_3-a_2\cdots a_n-a_{n-1})$。 | ||
| + | |||
| + | 等号成立充要条件为 $a_1,a_2\cdots a_n$ 从小到大排列后可以构成等差序列。具体见 [[..:jxm2001:other:结论_3#等差序列判定|证明]]。 | ||
| + | |||
| + | 于是问题转化为统计有多少区间满足 $\max(a[l\sim r])-\min(a[l\sim r])=(r-l)\text{gcd}(a_{l+1}-a_l\cdots a_r-a_{r-1})$。 | ||
| + | |||
| + | 考虑构造序列 $b_i=\max(a[i\sim r])-\min(a[i\sim r])+i\times \text{gcd}(a_{i+1}-a_i\cdots a_r-a_{r-1})$。 | ||
| + | |||
| + | 线段树维护区间最小值和最小值个数,对固定的 $r$,统计 $[i,r](i\lt r)$ 贡献时,枚举 $\text{gcd}(a_{i+1}-a_i\cdots a_r-a_{r-1})$ 不同的区间。 | ||
| + | |||
| + | 对每个区间将 $b_i=r\times \text{gcd}(a_{i+1}-a_i\cdots a_r-a_{r-1})$ 的数值个数计入贡献。 | ||
| + | |||
| + | 易知 $\text{gcd}(a_{i+1}-a_i\cdots a_r-a_{r-1})$ 不同的区间最多只有 $O(\log v)$ 个,于是总询问次数 $O(n\log v)$。 | ||
| + | |||
| + | 然后移动 $r$ 更新区间,需要维护 $\max(a[l\sim r]),\min(a[l\sim r]),i\times \text{gcd}(a_{i+1}-a_i\cdots a_r-a_{r-1})$ 的变化。 | ||
| + | |||
| + | 对 $\max(a[l\sim r]),\min(a[l\sim r])$ 的变化可以用单调栈维护,总更新次数 $O(n)$。 | ||
| + | |||
| + | 对 $i\times \text{gcd}(a_{i+1}-a_i\cdots a_r-a_{r-1})$ 的变化,可以维护 $\text{gcd}(a_{i+1}-a_i\cdots a_r-a_{r-1})$ 不同的段。 | ||
| + | |||
| + | 每次 $r$ 变化时暴力遍历 $O(\log v)$ 个段,每个段的值发生变化时暴力遍历整个段更新。 | ||
| + | |||
| + | 由于每个位置的 $\text{gcd}$ 值最多变化 $O(\log v)$ 次,所以总更新次数 $O(n\log v)$。 | ||
| + | |||
| + | 于是线段树只需要支持区间加操作和区间查询操作,总时间复杂度 $O(n\log n\log v)$。 | ||
| + | |||
| + | <hidden 查看代码> | ||
| + | <code cpp> | ||
| + | const int MAXN=1e5+5; | ||
| + | struct Node{ | ||
| + | LL v; | ||
| + | int cnt; | ||
| + | Node operator + (const Node &b)const{ | ||
| + | Node c; | ||
| + | if(v==b.v)c.v=v,c.cnt=cnt+b.cnt; | ||
| + | else if(v<b.v)c=*this; | ||
| + | else c=b; | ||
| + | return c; | ||
| + | } | ||
| + | }s[MAXN<<2]; | ||
| + | LL tag[MAXN<<2]; | ||
| + | int lef[MAXN<<2],rig[MAXN<<2]; | ||
| + | void push_up(int k){ | ||
| + | s[k]=s[k<<1]+s[k<<1|1]; | ||
| + | } | ||
| + | void push_add(int k,LL v){ | ||
| + | s[k].v+=v; | ||
| + | tag[k]+=v; | ||
| + | } | ||
| + | void push_down(int k){ | ||
| + | if(tag[k]){ | ||
| + | push_add(k<<1,tag[k]); | ||
| + | push_add(k<<1|1,tag[k]); | ||
| + | tag[k]=0; | ||
| + | } | ||
| + | } | ||
| + | void build(int k,int L,int R){ | ||
| + | lef[k]=L,rig[k]=R,tag[k]=0; | ||
| + | if(L==R){ | ||
| + | s[k].v=0; | ||
| + | s[k].cnt=1; | ||
| + | return; | ||
| + | } | ||
| + | int M=L+R>>1; | ||
| + | build(k<<1,L,M); | ||
| + | build(k<<1|1,M+1,R); | ||
| + | push_up(k); | ||
| + | } | ||
| + | void update(int k,int L,int R,LL v){ | ||
| + | if(L<=lef[k]&&rig[k]<=R){ | ||
| + | push_add(k,v); | ||
| + | return; | ||
| + | } | ||
| + | push_down(k); | ||
| + | int mid=lef[k]+rig[k]>>1; | ||
| + | if(mid>=L) | ||
| + | update(k<<1,L,R,v); | ||
| + | if(mid<R) | ||
| + | update(k<<1|1,L,R,v); | ||
| + | push_up(k); | ||
| + | } | ||
| + | Node query(int k,int L,int R){ | ||
| + | if(L<=lef[k]&&rig[k]<=R) | ||
| + | return s[k]; | ||
| + | push_down(k); | ||
| + | int mid=lef[k]+rig[k]>>1; | ||
| + | if(mid>=R) | ||
| + | return query(k<<1,L,R); | ||
| + | else if(mid<L) | ||
| + | return query(k<<1|1,L,R); | ||
| + | else | ||
| + | return query(k<<1,L,R)+query(k<<1|1,L,R); | ||
| + | } | ||
| + | int gcd(int a,int b){ | ||
| + | while(b){ | ||
| + | int t=b; | ||
| + | b=a%b; | ||
| + | a=t; | ||
| + | } | ||
| + | return a; | ||
| + | } | ||
| + | int a[MAXN],st1[MAXN],st2[MAXN]; | ||
| + | pair<int,int> st3[MAXN]; | ||
| + | int main() | ||
| + | { | ||
| + | int T=read_int(); | ||
| + | while(T--){ | ||
| + | int n=read_int(); | ||
| + | _rep(i,1,n)a[i]=read_int(); | ||
| + | build(1,1,n); | ||
| + | LL ans=n; | ||
| + | int top1=0,top2=0,top3=0; | ||
| + | _rep(i,1,n){ | ||
| + | while(top1&&a[st1[top1]]<=a[i]){ | ||
| + | update(1,st1[top1-1]+1,st1[top1],a[i]-a[st1[top1]]); | ||
| + | top1--; | ||
| + | } | ||
| + | st1[++top1]=i; | ||
| + | while(top2&&a[st2[top2]]>=a[i]){ | ||
| + | update(1,st2[top2-1]+1,st2[top2],a[st2[top2]]-a[i]); | ||
| + | top2--; | ||
| + | } | ||
| + | st2[++top2]=i; | ||
| + | if(i==1)continue; | ||
| + | st3[++top3]=make_pair(abs(a[i]-a[i-1]),i-1); | ||
| + | update(1,i-1,i-1,1LL*(i-1)*st3[top3].first); | ||
| + | for(int j=top3-1;j;j--){ | ||
| + | if(st3[j+1].first%st3[j].first!=0){ | ||
| + | int temp=st3[j].first; | ||
| + | st3[j].first=gcd(st3[j].first,st3[j+1].first); | ||
| + | _rep(k,st3[j-1].second+1,st3[j].second) | ||
| + | update(1,k,k,1LL*k*(st3[j].first-temp)); | ||
| + | } | ||
| + | } | ||
| + | int temp=0; | ||
| + | _rep(j,1,top3){ | ||
| + | if(st3[j].first==st3[temp].first) | ||
| + | st3[temp].second=st3[j].second; | ||
| + | else | ||
| + | st3[++temp]=st3[j]; | ||
| + | } | ||
| + | top3=temp; | ||
| + | _rep(j,1,top3){ | ||
| + | Node temp=query(1,st3[j-1].second+1,st3[j].second); | ||
| + | if(temp.v==1LL*i*st3[j].first) | ||
| + | ans+=temp.cnt; | ||
| + | } | ||
| + | } | ||
| + | enter(ans); | ||
| + | } | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | </hidden> | ||
| ===== B. Cannon ===== | ===== B. Cannon ===== | ||
| 行 15: | 行 178: | ||
| 对于第一个子问题,我们发现可以把 $k!$ 提出来,之后对于里面的式子,可以变成两个组合数的乘积,再利用组合数的常用结论,可以推出其等于 $2^{k}k!C(n+m,k)$ ,这个式子可以通过预处理 $2$ 的方幂,阶乘以及阶乘的逆来 $O(1)$ 求得,于是总共复杂度 $O(n+m)$ 。 | 对于第一个子问题,我们发现可以把 $k!$ 提出来,之后对于里面的式子,可以变成两个组合数的乘积,再利用组合数的常用结论,可以推出其等于 $2^{k}k!C(n+m,k)$ ,这个式子可以通过预处理 $2$ 的方幂,阶乘以及阶乘的逆来 $O(1)$ 求得,于是总共复杂度 $O(n+m)$ 。 | ||
| - | 对于第二个子问题, $2^{k} \sum {\frac {n!} {(n-i)!}} {\frac {m!} {(m-(k-i))!}} = 2^{k} \sum {\frac {n!m!} {(n+m-k)!}} {\frac {(n+m-k)!} {(n-i)!(m-(k-i))!}} = 2^{k} {\frac {n!m!} {(n+m-k)!}} \sum_{i=n-k}^{n} C(n+m-k,i)$ | + | 对于第二个子问题, $2^{k} \sum {\frac {n!} {(n-i)!}} {\frac {m!} {(m-(k-i))!}} = 2^{k} \sum {\frac {n!m!} {(n+m-k)!}} {\frac {(n+m-k)!} {(n-i)!(m-(k-i))!}} = 2^{k} {\frac {n!m!} {(n+m-k)!}} \sum_{i=k-m}^{n} C(n+m-k,i)$ |
| 虽然没有直接的结论,但是这个可以用步移算法来解决。 | 虽然没有直接的结论,但是这个可以用步移算法来解决。 | ||