一道建图题

题意

你有一个 n × m n\times m n×m 的矩阵,要求每对四联通的相邻格子的和不小于一个值。
最小化所有格子上数字之和,输出方案。
n ≤ 4 , m ≤ 1 0 6 n\leq 4,m\leq 10^6 n4,m106

首先考虑最小化所有格子上数字之和 a n s ans ans
这里有一个结论:如果把每个输入的限制看做带有权值的边,每个格子看成一个结点,那么 a n s ans ans=这个图的最大匹配(网格图显然是二分图)。
证明是这样的:如果一个方案不是最大匹配,那么至少会出现一条增广边,这个增广边的含义就是有一对相邻格子的限制没有被满足,而在最大匹配的时候,是恰好没有增广路的,所以一定可以找出一种合法的分配方案。我们也可以感受到如果权值大于了最大匹配,就会有一部分权值被“浪费”了。所以 a n s ans ans=最大匹配。

然后我们考虑怎么求出这个最大匹配,因为 n n n很小,
所以考虑状压dp(知识点,状压dp求最大匹配): d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前dp到第j列,第j列已经匹配上的点的情况为i的最大权值。
结合具体代码解释

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e6+5;
const int inf=1e18;
inline int read(){
	char c=getchar();int t=0,f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
	return t*f;
}
int n,m,p[maxn],alfa[maxn];
int b[5][maxn],a[5][maxn],dp[16][maxn];
signed main(){
	//freopen("farcue.in","r",stdin);
	//freopen("farcue.out","w",stdout);
	n=read(),m=read();int lim=(1<<n)-1;
	if((n*m)&1){puts("Far Cue");return 0;}
	for(int i=1;i<n;i++)
	for(int j=1;j<=m;j++)a[i][j]=read();
	for(int i=1;i<=n;i++)
	for(int j=1;j<m;j++)b[i][j]=read();
	for(int i=0;i<=lim;i++){
		for(int j=1;j<=m;j++)dp[i][j]=-inf;
	}
	dp[0][1]=0;
	int tmp=0;
	for(int i=1;i<=m;i++){
		for(int j=0;j<=lim;j++){
			if(!(j&3)){dp[j|3][i]=max(dp[j|3][i],dp[j][i]+a[1][i]);}//这里是枚举每一条边,做同一列间的转移
			if(!(j&6)){dp[j|6][i]=max(dp[j|6][i],dp[j][i]+a[2][i]);}
			if(!(j&12)){dp[j|12][i]=max(dp[j|12][i],dp[j][i]+a[3][i]);}
		}
		for(int j=0;j<=lim;j++)if(dp[j][i]>-inf){
			for(int k=1;k<=n;k++)
				if(!(j&(1<<k-1)))tmp+=b[k][i];
			dp[lim^j][i+1]=max(dp[lim^j][i+1],dp[j][i]+tmp);tmp=0;//这里是做相邻两列间的转移,意义是每个点都要选择一条边,那在dp[j][i]中第i列没有被选择的点就一定要在第i+1列的匹配中被选上。
		}
	}
	printf("%lld\n",dp[lim][m]);
	
	return 0;
}

接下来是输出方案的部分:
首先每条匹配边的两个端点可以看做同一个点(因为它们的权值之间的关系已经被定下来了),然后就是每条非匹配边对点的限制,假如一条非匹配边连接了两个不同匹配点x,y,那么这两个点之间的权值关系应该是: w ( e ) ≤ x + y = > w ( e ) − y ≤ x w(e)\leq x+y =>w(e)-y \leq x w(e)x+y=>w(e)yx,然后这是一个查分约束的形式。这里spfa直接求解其实复杂度不对,正解是做每一列之间的floyd转移的,但是已经可以通过数据。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e6+5;
const int inf=1e18;
inline int read(){
	char c=getchar();int t=0,f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
	return t*f;
}
int n,m,p[maxn],alfa[maxn],tot;
int b[5][maxn],a[5][maxn],dp[16][maxn],c[5][maxn],h[maxn<<1],ins[maxn<<1],dis[maxn<<1],ans[maxn<<2];
queue<int> q;
struct edge{
	int v,p,w;
}e[maxn<<2];
int cnt;
inline void add(int a,int b,int c){
	e[++cnt].p=h[a];
	e[cnt].v=b;
	e[cnt].w=c;
	h[a]=cnt;
}
void link(int x,int y,int tx,int ty,int w){
	if(c[x][y]==c[tx][ty])return ;//如果是匹配边,就返回
	if(tx+ty&1)return link(tx,ty,x,y,w);//确定边的顺序(根据我们对图中边的定义来的)
	add(c[x][y],c[tx][ty],ans[c[tx][ty]]-w);
}
void spfa(){
	while(!q.empty()){
		int u=q.front();q.pop();ins[u]=0;
		for(int i=h[u];i;i=e[i].p){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				if(!ins[v]){ins[v]=1;
					q.push(v);
				}
			}
		}
	}
}
signed main(){
	//freopen("farcue.in","r",stdin);
	//freopen("farcue.out","w",stdout);
	n=read(),m=read();int lim=(1<<n)-1;
	if((n*m)&1){puts("Far Cue");return 0;}
	for(int i=1;i<n;i++)
	for(int j=1;j<=m;j++)a[i][j]=read();
	for(int i=1;i<=n;i++)
	for(int j=1;j<m;j++)b[i][j]=read();
	for(int i=0;i<=lim;i++){
		for(int j=1;j<=m;j++)dp[i][j]=-inf;
	}
	dp[0][1]=0;
	int tmp=0;
	for(int i=1;i<=m;i++){
		for(int j=0;j<=lim;j++){
			if(!(j&3)){dp[j|3][i]=max(dp[j|3][i],dp[j][i]+a[1][i]);}
			if(!(j&6)){dp[j|6][i]=max(dp[j|6][i],dp[j][i]+a[2][i]);}
			if(!(j&12)){dp[j|12][i]=max(dp[j|12][i],dp[j][i]+a[3][i]);}
		}
		for(int j=0;j<=lim;j++)if(dp[j][i]>-inf){
			for(int k=1;k<=n;k++)
				if(!(j&(1<<k-1)))tmp+=b[k][i];
			dp[lim^j][i+1]=max(dp[lim^j][i+1],dp[j][i]+tmp);tmp=0;
		}
	}
	printf("%lld\n",dp[lim][m]);
	int x=lim;
	for(int i=m;i>=1;i--){//这里在确定哪些边是匹配边
		if((x&3)==3&&dp[x][i]==dp[x^3][i]+a[1][i]){c[1][i]=c[2][i]=++tot;x^=3;}
		if((x&6)==6&&dp[x][i]==dp[x^6][i]+a[2][i]){c[2][i]=c[3][i]=++tot;x^=6;}
		if((x&12)==12&&dp[x][i]==dp[x^12][i]+a[3][i]){c[3][i]=c[4][i]=++tot;x^=12;}
		for(int k=1;k<=n;k++)if(x&(1<<k-1))c[k][i]=c[k][i-1]=++tot;
		x=~x&lim;
	}
	if(n==1){
		int x=0;
		for(int i=1;i<=m;i++){printf("%lld ",x);x=b[1][i]-x;}
		return 0;
	}
	if(n==3){memset(dis,0x3f,sizeof(dis));q.push(1);dis[1]=0;}
	else{for(int i=1;i<=tot;i++)q.push(i),ins[i]=1;tot=0;}
	for(int i=1;i<n;i++){//定好同一列的匹配权值(其实就是找到匹配边,把 上面的权值赋给点)
		for(int j=1;j<=m;j++)if(c[i][j]==c[i+1][j])ans[c[i][j]]=a[i][j];
	}
	for(int i=1;i<=n;i++){//定好同一行的匹配权值
		for(int j=1;j<m;j++)if(c[i][j]==c[i][j+1])ans[c[i][j]]=b[i][j];
	}
	for(int i=1;i<n;i++){//连接同一列的非匹配边
		for(int j=1;j<=m;j++)link(i,j,i+1,j,a[i][j]);
	}
	for(int i=1;i<=n;i++){//连接同一行的
		for(int j=1;j<m;j++)link(i,j,i,j+1,b[i][j]);
	}
	spfa();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			printf("%lld ",(i+j&1)?dis[c[i][j]]:ans[c[i][j]]-dis[c[i][j]]);
		}
		puts("");
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值