P10683 [COTS 2024] 划分 Particija
思路
转化一下题意,\(a_i\) 和 \(b_i\) 恰有一个限制被满足,变成在一个二分图上有一些边 \((a_i,b_i)\),求最小点覆盖。
\(k>0\) 时可以更改一条边的一个端点,使得最大或最小化最小点覆盖。
不难发现整个图是由若干二分图联通块组成的。
当 \(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\) 含巨量分讨,慎看。
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;
}

浙公网安备 33010602011771号