JZOJ 6299. 2019.08.12【NOIP提高组A】工厂
题目
Description
Input
Output
Sample Input
Sample 1:
2
11
10
Sample 2:
2
10
00
Sample 3:
3
000
110
000
Sample Output
Sample 1:
1
Sample 2:
1
Sample 3:
3
Data Constraint
Hint
题解
- 先来看看题目背景,类似二分图匹配,
- 若任意极大匹配(随机顺序随机选取直到不能再选)后,最终都能得到完美匹配( n n n个人和 n n n台机器能分别相互匹配),才是符合题意的,
- 不难发现,二分图分成的若干联通块,当且仅当它们左右点数相等,并且两两均有连边,才是符合条件的。
- 设分成的第 i i i个联通块大小分别为 s [ i ] s[i] s[i],则最终连边总数 s u m = ∑ ∀ i s [ i ] 2 sum=sum_{forall i} s[i]^2 sum=∑∀is[i]2,那么答案即为 s u m − ∑ i = 1 n ∑ j = 1 n e [ i ] [ j ] sum-sum_{i=1}^{n}sum_{j=1}^{n}e[i][j] sum−∑i=1n∑j=1ne[i][j],其中 e [ i ] [ j ] = 0 / 1 e[i][j]=0/1 e[i][j]=0/1,表示第 i i i个工人是否会操作第 j j j台机器,
- 由于后面减去的值是固定的,所以只需要最小化 s u m sum sum的值,则可以取到最优的答案。
- 设原图中分成的每个联通块 A [ i ] A[i] A[i],是由 x [ i ] x[i] x[i]个工人及 y [ i ] y[i] y[i]台机器组成,考虑状压DP,
- f [ s ] [ i ] f[s][i] f[s][i]表示已选的联通块集合表示为 s s s,左边还剩 i i i个点还未匹配的最小边数,
- 设 s x sx sx为集合 s s s中 x [ i ] x[i] x[i]总和, s y sy sy同理,
- 每次枚举一个新加入的联通块 k k k,
- 直接转移就 f [ s ] [ i ] f[s][i] f[s][i]更新 f [ s ⋃ k ] [ i + x [ k ] ] f[sigcup k][i+x[k]] f[s⋃k][i+x[k]],这里不需要加入新的花费,
- 直到当前剩余的与新加的可以组成一个左右点数相等的联通块,也就是 s x + x [ k ] = s y + y [ k ] sx+x[k]=sy+y[k] sx+x[k]=sy+y[k]时,
- 用 f [ s ] [ i ] + ( i + x [ k ] ) 2 f[s][i]+(i+x[k])^2 f[s][i]+(i+x[k])2更新 f [ s ⋃ k ] [ 0 ] f[sigcup k][0] f[s⋃k][0]。
- s u m sum sum的最小值为 f [ S ] [ 0 ] f[S][0] f[S][0], S S S为所有联通块的集合。
- 但这样的时间复杂度是 O ( 2 n ∗ n 2 ) O(2^n*n^2) O(2n∗n2)的,过不了,
- 发现第一维的状态 s s s可能会有很多浪费,对于最初的联通块假设有 ( x [ i ] , y [ i ] ) = ( x [ j ] , y [ j ] ) (x[i],y[i])=(x[j],y[j]) (x[i],y[i])=(x[j],y[j]),它们其实可以看作是等价的,
- 当有很多组 ( x , y ) (x,y) (x,y)相同时,可以把它们压成同样的一组;若有许多不同,那么总联通块数就会远远小于 n n n,
- 于是仍然设状态 f [ s ] [ i ] f[s][i] f[s][i],唯一与上面不同的是, s s s在这里不是二进制,而是一种每一位进制不同的数,表示的是每一种联通块分别选了多少个( 0 − c n t [ i ] 0-cnt[i] 0−cnt[i]),这样 s s s的最大取值是小于 2 ∗ 1 0 5 2*10^5 2∗105的,可以极大地优化。
- 转移类似,需要预处理前缀积。(具体可见处理类似的JZOJ 5831. 【NOIP提高A组模拟2018.8.18】 number或代码)
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,s=0,b1[31],b2[31],p[31][31];
int sum[31][31],g[31],f[200010][31],c[31];
char st[31];
struct
{
int x,y,s;
}a[31];
int sqr(int x)
{
return x*x;
}
void dfs(int k,int o)
{
if(o==1)
{
a[s].x++;
for(int i=1;i<=n;i++) if(p[k][i]&&!b2[i])
{
b2[i]=1;
dfs(i,2);
}
}
else
{
a[s].y++;
for(int i=1;i<=n;i++) if(p[i][k]&&!b1[i])
{
b1[i]=1;
dfs(i,1);
}
}
}
int main()
{
int i,j,k;
scanf("%d
",&n);
for(i=1;i<=n;i++)
{
scanf("%s
",st+1);
for(j=1;j<=n;j++) p[i][j]=st[j]-'0';
}
for(i=1;i<=n;i++) if(!b1[i])
{
s++;
b1[i]=1;
dfs(i,1);
}
for(i=1;i<=n;i++) if(!b2[i])
{
s++;
b2[i]=1;
dfs(i,2);
}
for(i=1;i<=s;i++) sum[a[i].x][a[i].y]++;
s=0;
for(i=0;i<=n;i++)
for(j=0;j<=n;j++) if(sum[i][j]) a[++s].x=i,a[s].y=j,a[s].s=sum[i][j];
g[0]=1;
for(i=1;i<=s;i++) g[i]=g[i-1]*(a[i].s+1);
for(i=0;i<=g[s];i++)
for(j=0;j<=n;j++) f[i][j]=1e+9;
f[0][0]=0;
for(i=0;i<g[s];i++)
{
int t=i,tx=0,ty=0;
for(j=s;j;j--)
{
c[j]=0;
while(t>=g[j-1]) c[j]++,t-=g[j-1],tx+=a[j].x,ty+=a[j].y;
}
for(j=0;j<=n;j++) if(f[i][j]<1e+9)
{
for(k=1;k<=s;k++) if(c[k]<a[k].s)
{
if(f[i][j]<f[i+g[k-1]][j+a[k].x]) f[i+g[k-1]][j+a[k].x]=f[i][j];
if(tx+a[k].x==ty+a[k].y&&f[i][j]+sqr(j+a[k].x)<f[i+g[k-1]][0]) f[i+g[k-1]][0]=f[i][j]+sqr(j+a[k].x);
}
}
}
int ans=0;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++) ans-=p[i][j];
printf("%d
",ans+f[g[s]-1][0]);
return 0;
}