题意
你有一个
n
×
m
n\times m
n×m 的矩阵,要求每对四联通的相邻格子的和不小于一个值。
最小化所有格子上数字之和,输出方案。
n
≤
4
,
m
≤
1
0
6
n\leq 4,m\leq 10^6
n≤4,m≤106
首先考虑最小化所有格子上数字之和
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)−y≤x,然后这是一个查分约束的形式。这里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;
}