水题记录2.1
文章目录
- [ARC200C] Movie Theater
- 前置 CF825E Minimal Labels
- CF1623E Middle Duplication
- CF1830E Bully Sort
- luogu P5309 [Ynoi2011] 初始化
[ARC200C] Movie Theater
link
题目:
前置 CF825E Minimal Labels
link
简要题意:找出字典序最小的拓扑序。
考虑贪心,正着,从小往大,发现有后效性。于是考虑反着,从大到小填。
具体证明可以去看 这篇文章。
我口糊一下,就是把最大的数字扔到了后面,前面一定是最小的,正确性由于不用考虑后效性而显然。
这里使用优先队列,代码:
signed main(){ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);cin>>n>>m;cnt=n;for(int i=1;i<=m;i++){int x,y;cin>>x>>y;E[y].push_back(x);deg[x]++;}priority_queue<int>Q;for(int i=1;i<=n;i++)if(!deg[i])Q.push(i);while(!Q.empty()){int u=Q.top();Q.pop();a[u]=cnt--;for(int v:E[u]){deg[v]--;if(!deg[v])Q.push(v);}}for(int i=1;i<=n;i++)cout<<a[i]<<" ";return 0;
}
回到这题。
考虑按照区间来分类:
- li<lj<rj<ril_i< l_j< r_j< r_ili<lj<rj<ri此时我们需要让 iii 的座位在 jjj 后面,这样子贡献为 000,否则贡献为 222。
- ri<ljr_i<l_jri<lj 此时这两人对答案无影响。
- li<lj<ri<rjl_i<l_j<r_i<r_jli<lj<ri<rj 此时这两个人对答案的影响固定为 111。
那么考虑对于 111 类进行连边,然后跑最小拓扑序即为答案。
由区间形式可以知道,不会出现环,所以可以跑拓扑排序。
那么这道题目就做完了。
时间复杂度为 O(n2)O(n^2)O(n2),但是优先队列,所以 O(n2logn)O(n^2\log n)O(n2logn)。
代码:
void solve(){cin>>n;for(int i=1;i<=n;i++)cin>>l[i]>>r[i],E[i].clear(),deg[i]=0;cnt=n;for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++){if(l[i]<l[j]&&r[j]<r[i])E[i].push_back(j),deg[j]++;}priority_queue<int>Q;for(int i=1;i<=n;i++)if(!deg[i])Q.push(i);while(!Q.empty()){int u=Q.top();Q.pop();a[u]=cnt--;for(int v:E[u]){deg[v]--;if(!deg[v])Q.push(v);}}for(int i=1;i<=n;i++)cout<<a[i]<<" ";cout<<"\n";
}
CF1623E Middle Duplication
link
题目:
考虑贪心。
先把中序遍历得到的字符串弄出来,那么一个点被复制使答案变小,当且仅当在它后面第一个与它不同的字符比它大,为了方便,在最后插入一个 {
。
证明显然。
现在考虑贪心,对于一个节点 uuu,分类讨论。
如果 uuu 是值得被复制的(下文记为值得的),那么根据中序遍历先左然后根后右的遍历,能复制当前节点肯定更优。
此时其实对左子树并没有影响,因为左子树比当前节点先被遍历。
但是,对于右子树,如果当前节点不值得,那么肯定不会复制右子树的点。因为一定会复制当前节点,导致字典序变小。
但是,会有另一种情况,就是左子树某个位置需要复制,导致当前根节点已经被复制了,那么现在进入右子树遍历也是优的。
实现也很简单,考虑在 dfs 的时候加入一个变量 ccc,表示当前节点到根节点,没有被复制的节点有多少个,能复制则复制。
那么这题就做完了。
void dfs(int u){if(l[u])dfs(l[u]);idf[u]=++cnt;a[cnt]=s[u];if(r[u])dfs(r[u]);
}
int calc(int u,int c){if(k<=0)return 0;if(vis[idf[u]]&&k>=c)k-=c,used[u]=1,c=0;if(l[u])used[u]+=calc(l[u],c+1);if(used[u]&&r[u])used[u]+=calc(r[u],1);return used[u];
}
void pr(int u){if(l[u])pr(l[u]);cout<<s[u];if(used[u])cout<<s[u];if(r[u])pr(r[u]);
}
void solve(){cin>>n>>k;cin>>s;s=" "+s;s=s+'{';for(int i=1;i<=n;i++)cin>>l[i]>>r[i];dfs(1);int j=1;for(int i=1;i<=n;i++){while(a[j]==a[i])j++;if(a[i]<a[j])vis[i]=1;}calc(1,1);pr(1);
}
CF1830E Bully Sort
link
题目:
超级人类智慧题,加上勾使翻译。
正确题意:
- 找到最大的 pip_ipi,使得 pi≠ip_i\ne ipi=i
- 找到最小的 pjp_jpj,使得 i<ji<ji<j
- …\dots…
人类智慧地,观察一下,交换 (i,j)(i,j)(i,j),则满足 ∀k∈(i,pi],pi>pk,pk≥pj\forall k\in(i,p_i],p_i>p_k,p_k\ge p_j∀k∈(i,pi],pi>pk,pk≥pj。因为 [pi+1,n][p_i+1,n][pi+1,n] 肯定已经排完序了。
然后你就可以非常人类智慧地,发现一些性质:
- pi>i,pj<jp_i>i,p_j<jpi>i,pj<j
证明:考虑选择 pip_ipi 时,若 pi<ip_i<ipi<i,则与上述性质矛盾。若 pj≥jp_j\ge jpj≥j,则 [i,pi][i,p_i][i,pi] 中有 pi−i+1p_i-i+1pi−i+1 个数,但是你能填的范围是 [pj,pi][p_j,p_i][pj,pi],因此可得,pj≤i<jp_j\le i<jpj≤i<j。
然后你就可以人类智慧地,构造出两个变量:
对于 A=∑i=1n∣pi−i∣,B=A=\sum_{i=1}^n|p_i-i|,B=A=∑i=1n∣pi−i∣,B= 序列此时逆序对数。
考虑一次交换 (i,j)(i,j)(i,j):
- AAA 的变化量为 ∣pi−i∣+∣pj−j∣−∣pi−j∣−∣pj−i∣=2(j−i)|p_i-i|+|p_j-j|-|p_i-j|-|p_j-i|=2(j-i)∣pi−i∣+∣pj−j∣−∣pi−j∣−∣pj−i∣=2(j−i)
- BBB 的变化量是 2(j−i−1)+1=2(j−i)−12(j-i-1)+1=2(j-i)-12(j−i−1)+1=2(j−i)−1
可以发现,一次操作会使 AAA 和 BBB 的差值减小 111,那么最后的序列是单调递增的,AAA 和 BBB 的值均为 000。
所以答案即为 A−BA-BA−B。
考虑如何维护,对于 AAA,可以方便地线性维护。
对于 BBB,可以离线后 cdq 分治,可以树套树在线做,不过这里用的是分块。
记一块块长为 BBB,则份复杂度为 O(q(nBlogB+B))O(q(\frac{n}{B}\log B+B))O(q(BnlogB+B)),最优的时候,根据 AI 所说,B=enB=e\sqrt nB=en。
总复杂度近似于 O(qnlogn)O(q\sqrt n\log n)O(qnlogn)。
非常人类智慧。
代码:
inline int lowbit(int x){return x&(-x);
}
inline void add(int x){while(x<N){tr[x]++;x+=lowbit(x);}
}
inline int ask(int x){int res=0;while(x>0){res+=tr[x];x-=lowbit(x);}return res;
}
void Add(int x){int id=pos[x];D[id].insert(upper_bound(D[id].begin(),D[id].end(),a[x]),a[x]);
}
void Del(int x){int id=pos[x];D[id].erase(lower_bound(D[id].begin(),D[id].end(),a[x]));
}
ll calc(int x){int id=pos[x];ll res=0;for(int i=1;i<id;i++)res+=D[i].end()-upper_bound(D[i].begin(),D[i].end(),a[x]);for(int i=id+1;i<=ck;i++)res+=lower_bound(D[i].begin(),D[i].end(),a[x])-D[i].begin();int L=(id-1)*ks+1,R=min(id*ks,n);bool fl=0;for(int i=L;i<=R;i++){if(a[i]==a[x]){fl=1;continue;}res+=fl^(a[i]>a[x]);}return res;
}
void solve(){cin>>n>>q;ks=e*sqrt(n);for(int i=1;i<=n;i++){cin>>a[i];pos[i]=(i-1)/ks+1;cnt1+=abs(a[i]-i);cnt2+=ask(N-1)-ask(a[i]);add(a[i]);Add(i);}ck=pos[n];while(q--){int l,r;cin>>l>>r;cnt1-=abs(a[l]-l)+abs(a[r]-r);cnt2-=calc(l)+calc(r);if(a[l]>a[r]) cnt2++;Del(l),Del(r);swap(a[l],a[r]);Add(l),Add(r);if(a[l]>a[r]) cnt2--;cnt1+=abs(a[l]-l)+abs(a[r]-r);cnt2+=calc(l)+calc(r);cout<<cnt1-cnt2<<"\n";}
}
luogu P5309 [Ynoi2011] 初始化
link
题目:
考虑分块和根号分治。
记块长为 BBB,则若 x>Bx>Bx>B,暴力跟新。
否则考虑记一个 ci,jc_{i,j}ci,j 表示 x=i,y=jx=i,y=jx=i,y=j 时的 zzz 的总和,我们再对它做一个前后缀和。这样子最多更新 x+1x+1x+1 个位置。
然后考虑查询操作。先是普通分块即可得到答案,但是还要算上 x≤Bx\le Bx≤B 的跟新的答案。
考虑枚举 xxx,然后根据 xxx 对 l,rl,rl,r 进行分块,整块直接乘法解决,散块使用我们维护的前后缀和来解决。
这样子复杂度是 O(m(B+nB))O(m(B+\frac{n}{B}))O(m(B+Bn)) 的,BBB 取 n\sqrt nn 理论最优,不过另一部分常数比较大,平衡一下,BBB 取 300300300 更优。
代码(注意代码中前后缀和是在模 xxx 意义下的):
inline void add(int &x,int y){y=(y>=mod?y-mod:y);x=(x+y>=mod?x+y-mod:x+y);
}
signed main(){n=read(),m=read();int ks=sqrt(n);ks=min(ks,400);for(int i=1;i<=n;i++)a[i]=read(),pos[i]=(i-1)/ks+1,add(sum[pos[i]],a[i]);while(m--){int op=read();int x=read(),y=read(),z;if(op==1){z=read();y=(y>=x?0:y);z=(z>=mod?0:z);if(x>ks)for(int i=y;i<=n;i+=x)add(a[i],z),add(sum[pos[i]],z);else{for(int i=0;i<=y;i++)add(suf[x][i],z);for(int i=y;i<x;i++)add(pre[x][i],z);}}else{int l=x,r=y;int idl=pos[l],idr=pos[r];int res=0;if(idl==idr)for(int i=l;i<=r;i++)add(res,a[i]);else{for(int i=idl+1;i<=idr-1;i++)add(res,sum[i]);int R=min(n,idl*ks);for(int i=l;i<=R;i++)add(res,a[i]);int L=(idr-1)*ks+1;for(int i=L;i<=r;i++)add(res,a[i]);}
// printf("%d pre\n",res);for(int i=1;i<=ks;i++){int sum1=suf[i][0];add(res,1ll*max(r/i-l/i-1,0)*sum1%mod);if(l/i==r/i)add(res,mod+pre[i][r%i]-(l%i==0?0:pre[i][l%i-1]));elseadd(res,suf[i][l%i]),add(res,pre[i][r%i]);}add(res,mod);printf("%d\n",res);}}return 0;
}