CF1408G 题解
模拟赛 T2。
思路
从小到大加边,用并查集维护,记录当前联通块的点数 \(siz_i\) 和边数 \(num_i\)。如果 \(num_i=\frac{siz_i\times (siz_i-1)}{2}\) 说明当前联通块是一个合法的组。但是不好计数。
手玩发现,对于任意两个合法的组,他们要么相互包含要么相互不交。这说明我们可以自己给点定一个顺序使得一个组重排后是一个区间。这样设 \(dp_{i,j}\) 表示重新排序后前 \(i\) 个点有 \(j\) 个组的方案数,把一段合法的区间放在最左边的点上,可以 dp。
先做一遍并查集,记录 \(n-1\) 次合并前 \(f_u\) 的值。从后往前遍历每次合并 \(f_v=u\),将合并后所有 \(f_x=u\) 的 \(x\) 按合并前 \(f_x=u\) 和 \(f_x=v\) 将分为左右两边。再用新的 \(id_u\) 做并查集,找到的合法的组都是区间。
可以合理相信合法的组的数量不多,小于 \(n^2\)。复杂度 \(O(n^2)\)。
code
int n,a[maxn][maxn];
int f[maxn],siz[maxn],num[maxn];
int fd(int x){
if(f[x]==x)return x;
return f[x]=fd(f[x]);
}
pii pos[maxn*maxn];
int lst[maxn][maxn],tp;
pii st[maxn];
int id[maxn],tmp[maxn],rnk[maxn];
vector<int> p[maxn];
int dp[maxn][maxn];
void work(){
n=read();
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)a[i][j]=read();
a[i][i]=inf;
}
for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)pos[a[i][j]]={i,j};
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1;i<=n*(n-1)/2;i++){
int u=pos[i].first,v=pos[i].second;
u=fd(u),v=fd(v);
if(u!=v){
st[++tp]={v,u};
for(int j=1;j<=n;j++)lst[tp][j]=fd(j);
f[v]=u;
}
}
++tp;for(int j=1;j<=n;j++)lst[tp][j]=fd(j);
for(int i=1;i<=n;i++)id[i]=i;
for(int i=tp-1;i;i--){
for(int j=1;j<=n;j++)tmp[j]=id[j];
int l,r;
// for(int j=1;j<=n;j++)cout<<id[j]<<" ";cout<<"\n";
for(int j=1;j<=n;j++)if(lst[i+1][tmp[j]]==st[i].second){l=j;break;}
for(int j=1;j<=n;j++)if(lst[i+1][tmp[j]]==st[i].second)r=j;
// cout<<i<<" "<<st[i].first<<" "<<st[i].second<<" "<<l<<" "<<r<<"\n";
int h=l;
for(int j=l;j<=r;j++)if(lst[i][tmp[j]]==st[i].second)id[h++]=tmp[j];
for(int j=l;j<=r;j++)if(lst[i][tmp[j]]==st[i].first)id[h++]=tmp[j];
}
// for(int i=1;i<=n;i++)cout<<id[i]<<" ";cout<<"\n";
for(int i=1;i<=n;i++)rnk[id[i]]=i;
for(int i=1;i<=n;i++)f[i]=i,siz[i]=1,num[i]=0,p[i].push_back(i);
for(int i=1;i<=n*(n-1)/2;i++){
int u=rnk[pos[i].first],v=rnk[pos[i].second];
if(u>v)swap(u,v);
u=fd(u),v=fd(v);
if(u==v)num[u]++;
else{
f[v]=u;siz[u]+=siz[v];num[u]+=num[v]+1;
}
if(num[u]==siz[u]*(siz[u]-1)/2){
// for(int j=1;j<=n;j++)cout<<fd(j)<<" ";cout<<"\n";
// for(int j=u;j<=n+1;j++)if(fd(j)!=u){
// p[u].push_back(j-1);
// // cout<<u<<" "<<j-1<<"\n";
// break;
// }
p[u].push_back(u+siz[u]-1);
}
}
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)if(dp[i-1][j-1]){
// cout<<i<<" "<<j<<" "<<dp[i-1][j-1]<<"\n";
for(int k:p[i])(dp[k][j]+=dp[i-1][j-1])%=mod;
}
}
for(int i=1;i<=n;i++)printf("%lld ",dp[n][i]);
}