题解:P10683 [COTS 2024] 划分 Particija

· · 题解

思路

转化一下题意,a_ib_i 恰有一个限制被满足,变成在一个二分图上有一些边 (a_i,b_i),求最小点覆盖。

不难发现整个图是由若干二分图联通块组成的。 当 $k=0$ 时,对每个二分图求最小点覆盖,直接 $O(n)$ 黑白染色。 当 $k=1$ 时,因为有 $(\min(x_1,y_1)+\min(x_2,y_2)) = \min(x_1+x_2,x_1+y_2,x_2+y_1,x_2+y_2) \le \min(x_1+x_2,y_1+y_2)$,这启发我们把一个块分成两个块才能使答案变小。 于是我们更改的点一定属于一条割边,然后我们考虑应该把这个端点挪移到哪里。 假如分成的两个连通块存在一个不是孤点,那么直接把更改的端点变成这个连通块里的点即可,这样做不会产生新的贡献。 如果分成的两个连通块都是孤点,那么更改这条边不会更优,代码实现上可以直接不管。 然后枚举每一条割边计算答案即可。 当 $k=2$ 时,我们要合并两个连通块。 假如一个连通块左部点大于右部点,那么我们肯定找一个右部点大于左部点的块和它合并。每次贪心的拿右部点比左部点多的数量最多的出来匹配。 左部点数量小于右部点同理,找右部点比左部点少的数量最多的出来匹配。 所以我们枚举所有边,计算把它连给另一个连通块的贡献就行。 但是如果移动的这条边是割边,那还得额外再算分开连通块的贡献,然后再分别算分成的两个连通块和哪个连通块合并。 但是其实还有一个小问题,我们上面的不等式是基于总点数不变的。仔细阅读题面,发现我们的更改操作其实是可以**新建节点**的。这个问题在 $k=1$ 的时候不会体现,因为新建节点只会让答案变大。 于是,我们还得再额外统计一下新建一个节点能产生的贡献,新建的节点必须和原节点的颜色一样。 最后记得特判 $n=1$,此时无论如何不会存在两个连通块。 ## 代码 怕写挂,遂对每一个包都重新写了 Tarjan 和统计答案。 $k=2$ 含巨量分讨,慎看。 ```cpp const int N = 4e5+10; int n,a[N],b[N]; struct{ int to,nex,from; }edge[N<<1]; int head[N],edge_num; inline void add(int x,int y){ edge[++edge_num].to = y; edge[edge_num].nex = head[x]; head[x] = edge_num; edge[edge_num].from = x; } // k = 0 namespace Sub0{ int cnt[2],vis[N]; inline void dfs(int now){ cnt[now>n]++; vis[now] = 1; for(int i=head[now];i;i=edge[i].nex){ int tto = edge[i].to; if(vis[tto]) continue; dfs(tto); } } inline void solve(int T){ int ans = 0; for(int i=1;i<=n;++i) if(!vis[a[i]]){ cnt[0] = cnt[1] = 0; dfs(a[i]); ans += Min(cnt[0],cnt[1]); } for(int i=1;i<=n;++i) if(!vis[b[i]+n]){ cnt[0] = cnt[1] = 0; dfs(b[i]+n); ans += Min(cnt[0],cnt[1]); } printf("%d",ans); if(T) for(int i=1;i<=n+n;++i) vis[i] = 0; } } // k = 1 namespace Sub1{ int dfn[N],low[N],dtop,is_cut[N]; int cnt[N][2],tmp[N][2]; inline void tarjan(int now,int fu,int tp){ dfn[now] = low[now] = ++dtop; int flag = 0; cnt[tp][now>n]++; for(int i=head[now];i;i=edge[i].nex){ int tto = edge[i].to; if(!dfn[tto]){ tarjan(tto,now,tp); low[now] = Min(low[now],low[tto]); if(low[tto]>dfn[now]) is_cut[tto] = 1; } else{ if(tto!=fu || flag) low[now] = Min(low[now],dfn[tto]); else flag = 1; } } } int num,ans; inline void get_ans(int now,int tp){ tmp[now][now>n]++; for(int i=head[now];i;i=edge[i].nex){ int tto = edge[i].to; if(tmp[tto][0]+tmp[tto][1]) continue; get_ans(tto,tp); tmp[now][0] += tmp[tto][0]; tmp[now][1] += tmp[tto][1]; if(is_cut[tto]){ ans = Min(ans,num-Min(cnt[tp][0],cnt[tp][1])+ Min(tmp[tto][0],tmp[tto][1])+Min(cnt[tp][0]-tmp[tto][0],cnt[tp][1]-tmp[tto][1])); } } } inline void solve(int T){ for(int i=1;i<=n;++i) if(!dfn[a[i]]){ cnt[a[i]][0] = cnt[a[i]][1] = 0; tarjan(a[i],a[i],a[i]); num += Min(cnt[a[i]][0],cnt[a[i]][1]); } for(int i=1;i<=n;++i) if(!dfn[b[i]+n]){ cnt[b[i]+n][0] = cnt[b[i]+n][1] = 0; tarjan(b[i]+n,b[i]+n,b[i]+n); num += Min(cnt[b[i]+n][0],cnt[b[i]+n][1]); } ans = num; for(int i=1;i<=n;++i) if(!(tmp[a[i]][0]+tmp[a[i]][1])) get_ans(a[i],a[i]); for(int i=1;i<=n;++i) if(!(tmp[b[i]+n][0]+tmp[b[i+n]][1])) get_ans(b[i]+n,b[i]+n); printf("%d",ans); if(T){ dtop = num = 0; for(int i=1;i<=n+n;++i){ low[i] = dfn[i] = is_cut[i] = 0; cnt[i][0] = cnt[i][1] = 0; tmp[i][0] = tmp[i][1] = 0; } } } } // k = 2 namespace Sub2{ int dfn[N],low[N],dtop,is_cut[N]; int cnt[N][2],tmp[N][2]; inline void tarjan(int now,int fu,int tp){ dfn[now] = low[now] = ++dtop; int flag = 0; cnt[tp][now>n]++; for(int i=head[now];i;i=edge[i].nex){ int tto = edge[i].to; if(!dfn[tto]){ tarjan(tto,now,tp); low[now] = Min(low[now],low[tto]); if(low[tto]>dfn[now]) is_cut[tto] = 1; } else{ if(tto!=fu || flag) low[now] = Min(low[now],dfn[tto]); else flag = 1; } } } int minn[2],maxn[2],num,ans; inline void get_ans(int now,int tp){ tmp[now][now>n]++; for(int i=head[now];i;i=edge[i].nex){ int tto = edge[i].to; if(tmp[tto][0]+tmp[tto][1]) continue; get_ans(tto,tp); tmp[now][0] += tmp[tto][0]; tmp[now][1] += tmp[tto][1]; if(is_cut[tto]){ if(cnt[tp][0]-tmp[tto][0]>cnt[tp][1]-tmp[tto][1]){ ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(minn[0],minn[1])+ Min(tmp[tto][0],tmp[tto][1])+Min(cnt[tp][0]-tmp[tto][0]+minn[0],cnt[tp][1]-tmp[tto][1]+minn[1])); } else{ ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(maxn[0],maxn[1])+ Min(tmp[tto][0],tmp[tto][1])+Min(cnt[tp][0]-tmp[tto][0]+maxn[0],cnt[tp][1]-tmp[tto][1]+maxn[1])); } if(tmp[tto][0]>tmp[tto][1]){ ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(minn[0],minn[1])+ Min(tmp[tto][0]+minn[0],tmp[tto][1]+minn[1])+Min(cnt[tp][0]-tmp[tto][0],cnt[tp][1]-tmp[tto][1])); } else{ ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(maxn[0],maxn[1])+ Min(tmp[tto][0]+maxn[0],tmp[tto][1]+maxn[1])+Min(cnt[tp][0]-tmp[tto][0],cnt[tp][1]-tmp[tto][1])); } ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])+ Min(tmp[tto][0]+(tto>n),tmp[tto][1]+(tto<=n))+Min(cnt[tp][0]-tmp[tto][0],cnt[tp][1]-tmp[tto][1])); ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])+ Min(tmp[tto][0],tmp[tto][1])+Min(cnt[tp][0]-tmp[tto][0]+(now>n),cnt[tp][1]-tmp[tto][1]+(now<=n))); } else{ if(cnt[tp][0]>cnt[tp][1]){ ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(minn[0],minn[1])+ Min(cnt[tp][0]+minn[0],cnt[tp][1]+minn[1])); } else{ ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])-Min(maxn[0],maxn[1])+ Min(cnt[tp][0]+maxn[0],cnt[tp][1]+maxn[1])); } ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])+ Min(cnt[tp][0]+1,cnt[tp][1])); ans = Max(ans,num-Min(cnt[tp][0],cnt[tp][1])+ Min(cnt[tp][0],cnt[tp][1]+1)); } } } inline void solve(int T){ minn[0] = 0,minn[1] = 0; maxn[0] = 0,maxn[1] = 0; for(int i=1;i<=n;++i) if(!dfn[a[i]]){ cnt[a[i]][0] = cnt[a[i]][1] = 0; tarjan(a[i],a[i],a[i]); num += Min(cnt[a[i]][0],cnt[a[i]][1]); if(minn[0]-minn[1]>cnt[a[i]][0]-cnt[a[i]][1]){ minn[0] = cnt[a[i]][0]; minn[1] = cnt[a[i]][1]; } if(maxn[0]-maxn[1]<cnt[a[i]][0]-cnt[a[i]][1]){ maxn[0] = cnt[a[i]][0]; maxn[1] = cnt[a[i]][1]; } } for(int i=1;i<=n;++i) if(!dfn[b[i]+n]){ cnt[b[i]+n][0] = cnt[b[i]+n][1] = 0; tarjan(b[i]+n,b[i]+n,b[i]+n); num += Min(cnt[b[i]+n][0],cnt[b[i]+n][1]); if(minn[0]-minn[1]>cnt[b[i]+n][0]-cnt[b[i]+n][1]){ minn[0] = cnt[b[i]+n][0]; minn[1] = cnt[b[i]+n][1]; } if(maxn[0]-maxn[1]<cnt[b[i]+n][0]-cnt[b[i]+n][1]){ maxn[0] = cnt[b[i]+n][0]; maxn[1] = cnt[b[i]+n][1]; } } ans = num; for(int i=1;i<=n;++i) if(!(tmp[a[i]][0]+tmp[a[i]][1])) get_ans(a[i],a[i]); for(int i=1;i<=n;++i) if(!(tmp[b[i]+n][0]+tmp[b[i+n]][1])) get_ans(b[i]+n,b[i]+n); printf("%d",ans); if(T){ dtop = num = 0; for(int i=1;i<=n+n;++i){ low[i] = dfn[i] = is_cut[i] = 0; cnt[i][0] = cnt[i][1] = 0; tmp[i][0] = tmp[i][1] = 0; } } } } // main int main(){ // freopen("in.in","r",stdin); // freopen("out.out","w",stdout); int T,ID; read(T,ID); while(T--){ read(n); for(int i=1;i<=n;++i) read(a[i]); for(int i=1;i<=n;++i) read(b[i]); if(n==1){ puts("1"); continue; } for(int i=1;i<=n;++i){ add(a[i],b[i]+n); add(b[i]+n,a[i]); } if(ID==0) Sub0 :: solve(T); else if(ID==1) Sub1 :: solve(T); else Sub2 :: solve(T); if(T){ for(int i=1;i<=n+n;++i) head[i] = 0; edge_num = 0; putchar('\n'); } } // fclose(stdin); // fclose(stdout); return 0; } ```