正睿 2021 Noip 十连测 Day2
A
写个暴力找规律即可。
B
\(80\) 分做法:数位 dp,设 \(f_{i,j}\) 为低 \(i\) 位,\(\bmod a=j\) 是否可行,并记录一下转移。
考虑建一张 \(a\) 个点 \(0\sim(a-1)\) 的图,对于每个合法的转移 \(x\to (10x+y)\bmod a\) 连边,最小位数就是任意非零点到 \(0\) 号点的最短路,可以直接 bfs 出来。
然后贪心地从高位到低位确定即可。
C
注意到任意选边都能构成完美匹配的条件,是原图的每个极大连通块都是左右部点数相同的完全二分图。
于是问题变成了:给你若干个连通块,你需要合并其中一些,使得每个连通块内左右部点数相同,且所有连通块左部点数的平方和最小。
因为 \(n\le 25\),考虑大力状压:把左右部点数相同的先去掉,然后记录左右部的孤点各有多少个,以及剩下的连通块。
设 \(f_{S,i,j}\) 为把 \(S\) 内的所有连通块拼成若干个左右部点数相同的连通块,并且使用了 \(i\) 个左部孤点,\(j\) 个右部孤点,平方和最小是多少。转移时枚举 \(S\) 的一个子集,强制配上一些孤点使得这个子集并成一个左右部点数相同的连通块,贡献到 \(f\) 上即可。
复杂度:考虑最多形成多少个连通块?可以发现左部一个点、右部两个点(或者反过来)能形成 \(2/3n\) 个连通块。于是复杂度是 \(\mathcal{O}(3^{2/3n}n^2)\)。算一下可以发现这玩意最多 \(4\times 10^{10}\),但连通块个数越多,孤点个数越小,所以远远跑不满。
代码
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define For(Ti,Ta,Tb) for(int Ti=(Ta);Ti<=(Tb);++Ti)
#define Dec(Ti,Ta,Tb) for(int Ti=(Ta);Ti>=(Tb);--Ti)
template<typename T> void Read(T &x){
x=0;int _f=1;
char ch=getchar();
while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();
while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
x=x*_f;
}
template<typename T,typename... Args> void Read(T &x,Args& ...others){
Read(x);Read(others...);
}
typedef long long ll;
const int N=27,M=17;
int T,n;char a[N][N];
struct DSU{
int fa[N<<1];
int Find(int p){
return fa[p]==p?p:fa[p]=Find(fa[p]);
}
void Union(int p,int q){
fa[Find(p)]=Find(q);
}
};
int dots[2][1<<M],dot[2],siz[2][N<<1],cnt,bl[2][N<<1],cost[1<<M],f[1<<M][N][N];
DSU dsu;
int main(){
Read(T);
while(T--){
memset(dots,0,sizeof dots);
memset(siz,0,sizeof siz);
cnt=dot[0]=dot[1]=0;
memset(bl,0,sizeof bl);
memset(cost,0,sizeof cost);
Read(n);
For(i,1,n*2) dsu.fa[i]=i;
int ans=0;
For(i,1,n){
scanf("%s",a[i]+1);
For(j,1,n) if(a[i][j]=='1') dsu.Union(i,j+n),--ans;
}
For(i,1,n*2){
++siz[i>n][dsu.Find(i)];
}
For(i,1,n*2){
if(dsu.Find(i)!=i) continue;
if(siz[0][i]==siz[1][i]) ans+=siz[0][i]*siz[1][i];
else if(siz[0][i]+siz[1][i]==1) ++dot[siz[1][i]];
else{
++cnt;
bl[0][cnt]=siz[0][i],bl[1][cnt]=siz[1][i];
}
}
int S=(1<<cnt)-1;
For(st,1,S){
for(int i=1;i<=cnt;++i){
if((st&1<<(i-1))==0) continue;
dots[0][st]+=bl[0][i],dots[1][st]+=bl[1][i];
}
if(dots[0][st]>dots[1][st]){
cost[st]=dots[0][st]*dots[0][st];
dots[1][st]=dots[0][st]-dots[1][st],dots[0][st]=0;
}else{
cost[st]=dots[1][st]*dots[1][st];
dots[0][st]=dots[1][st]-dots[0][st],dots[1][st]=0;
}
}
For(i,0,S) For(j,0,dot[0]) For(k,0,dot[1]) f[i][j][k]=0x3f3f3f3f;
f[0][0][0]=0;
For(i,1,S){
for(int st=i;st;st=(st-1)&i){
For(j,dots[0][st],dot[0]){
For(k,dots[1][st],dot[1]){
f[i][j][k]=min(f[i][j][k],f[i^st][j-dots[0][st]][k-dots[1][st]]+cost[st]);
}
}
}
}
int res=0x3f3f3f3f;
For(i,0,min(dot[0],dot[1])){
res=min(res,f[S][dot[0]-i][dot[1]-i]+i);
}
printf("%d\n",res+ans);
}
return 0;
}
Written by Alan_Zhao