[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 <cstdio>
#include <cstring>
#include <algorithm>
#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……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值