[JZOJ6299] 2019.08.12【NOIP提高组A】工厂

题目

题目大意

工厂内每个人只会操作一些机器。
他们会以随机的顺序来,每次选任意一台机器来操作。
一台机器只能由一个工人来操作。
可以花费一的代价来使某个工人学会一种机器。
问花费最少的代价,使得在所有的情况下每个人都能操纵一台机器。

正解

这题可以转化成个二分图。而答案一定满足:所有联通块都是个完全二分图。
我们要用最少的代价来造出这样的二分图。
预处理出所有的联通块,每个联通块用 ( x , y ) (x,y) (x,y)表示,意味着左边有 x x x个,右边有 y y y个。
于是就有了下面这个状压DP: f S , i f_{S,i} fS,i表示 S S S集中的联通块都被使用了,其中完成了的联通块的 x x x之和为 i i i的最小边数。
等等,这个状态会不会存不下?
实际上,对于相同的 ( x , y ) (x,y) (x,y),我们只关心数量,所以可以进一步压缩。然后状态的数量就变得少了很多。
最后的答案记得要减去原来就有的边数。

代码

using namespace std;
#include 
#include 
#include 
#define N 33
inline void update(int &x,int y){x>y?x=y:0;}
int n;
char can[N][N];
bool vis[N][2];
int cntl,cntr,have;
void find(int x,bool k){
	vis[x][k]=1;
	if (k==0){
		cntl++;
		for (int i=1;i<=n;++i)
			if (can[x][i]=='1' && !vis[i][1])
				find(i,1);
	}
	else{
		cntr++;
		for (int i=1;i<=n;++i)
			if (can[i][x]=='1' && !vis[i][0])
				find(i,0);
	}
}
int sc[N][N];
struct State{
	int x,y;
	int num;
} lis[N];
int m;
int pro[N];
int chose[N];
int f[200000][N];
void dfs(int k,int s,int x,int y){
	if (k>m){
		if (s==0)
			f[0][0]=0;
		for (int j=0;j<n;++j){
			if (x==y)
				update(f[s][x],f[s][j]+(x-j)*(x-j));
			for (int i=1;i<=m;++i)
				if (chose[i]<lis[i].num)
					update(f[s+pro[i-1]][j],f[s][j]);
		}
		return;
	}
	for (int i=0;i<=lis[k].num;++i){
		chose[k]=i;
		dfs(k+1,s+i*pro[k-1],x+i*lis[k].x,y+i*lis[k].y);
	}
}
int main(){
	freopen("factory.in","r",stdin);
	freopen("factory.out","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;++i){
		scanf("%s",can[i]+1);
		for (int j=1;j<=n;++j)
			if (can[i][j]=='1')
				have++;
	}
	for (int i=1;i<=n;++i){
		if (!vis[i][0]){
			cntl=cntr=0;
			find(i,0);
			sc[cntl][cntr]++;
		}
		if (!vis[i][1]){
			cntl=cntr=0;
			find(i,1);
			sc[cntl][cntr]++;
		}
	}
	pro[0]=1;
	for (int i=0;i<=n;++i)
		for (int j=0;j<=n;++j)
			if (sc[i][j]){
				lis[++m]={i,j,sc[i][j]};
				pro[m]=pro[m-1]*(sc[i][j]+1);
			}
	memset(f,127,sizeof f);
	f[0][0]=0;
	dfs(1,0,0,0);
	printf("%d\n",f[pro[m]-1][n]-have);
	return 0;
}

总结

居然还有如此鬼畜的DP……

你可能感兴趣的:(动态规划(DP),二分图)