hi~
题目大意:
给你一个n*n的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。
n<=20
一看这个题目,其实我的第一想法是..... 这他喵的不是最小割吗?
先对矩阵黑白染色,然后S向每个白点连流量为数的边,黑点向T连流量为数的边
然后每个点跟周围的点连inf的边,表示不能舍弃这个联系(这两个点不能够同时存在)
然后跑dinic 用所有数的和减去最小割就是答案了
上代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 30010;
const int maxm = 1e5+1e2;
const int inf = 1e9;
int point[maxn],nxt[maxm],to[maxm],val[maxm];
int a[310][310],mm[310][310];
int h[maxn],q[maxn];
int sum;
int cnt=1,n,m,s,t;
void addedge(int x,int y,int w)
{
nxt[++cnt]=point[x];
to[cnt]=y;
val[cnt]=w;
point[x]=cnt;
}
void insert(int x,int y,int w){
addedge(x,y,w);
addedge(y,x,0);
}
bool bfs()
{
memset(h,-1,sizeof(h));
int head=0,tail=1;
q[tail]=s;
h[s]=0;
while (head<tail)
{
head++;
int x = q[head];
for (int i=point[x];i;i=nxt[i])
{
int p =to[i];
if (val[i]>0 &&h[p]==-1)
{
h[p]=h[x]+1;
q[++tail]=p;
}
}
}
if (h[t]==-1) return 0;
else return 1;
}
int dfs(int x,int low)
{
if (x==t || low==0) return low;
int totflow=0;
for (int i=point[x];i;i=nxt[i])
{
int p=to[i];
if (val[i]>0 &&h[p]==h[x]+1)
{
int tmp = dfs(p,min(val[i],low));
val[i]-=tmp;
val[i^1]+=tmp;
low-=tmp;
totflow+=tmp;
if (low==0) return totflow;
}
}
if (low>0) h[x]=-1;
return totflow;
}
int dinic()
{
int ans=0;
while (bfs())
{
ans+=dfs(s,inf);
}
return ans;
}
int main()
{
scanf("%d%d",&m,&n);
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
scanf("%d",&a[i][j]),sum+=a[i][j];
int num=0;
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
{
++num;
mm[i][j]=num;
}
s=10001;
t=10002;
for(int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
if ((i+j)%2==0){
insert(s,mm[i][j],a[i][j]);
}
else
{
insert(mm[i][j],t,a[i][j]);
}
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
{
if ((i+j)%2!=0) continue;
if (i-1>0) insert(mm[i][j],mm[i-1][j],inf);
if (i+1<=m) insert(mm[i][j],mm[i+1][j],inf);
if (j-1>0) insert(mm[i][j],mm[i][j-1],inf);
if (j+1<=n) insert(mm[i][j],mm[i][j+1],inf);
}
cout<<sum-dinic();
return 0;
}
如果有想要学习dinic的相关知识的爷话 可以加我q 752742355
一条华丽的分割线
---------------------------------------------------------------------------------------------------------------------------------
看到这个数据范围,又是最优解问题
哇......状压dp搞一搞咯
令f[i][j]表示前i行当前行的状态为j的最大和
在写这个题的时候我用了一个小tip
先提前把合法的状态筛选进了一个cnt数组
然后将f数组的第二维的j 表示在cnt中的第j个状态 省空间
然后随便搞搞 就行了
上代码!
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 1e5;
int f[30][maxn];
int cnt[maxn];
int a[31][31];
int tmp,n;
void init(int n)
{
tmp=0;
for (int i=0;i<(1 << n);i++)
{
if ((i & (i << 1))==0) cnt[++tmp]=i;
}
}
int count(int hang,int S)
{
int line = 0,ans=0;
while (S)
{
line++;
if (S&1){
ans+=a[hang][n-line+1];
}
S/=2;
}
return ans;
}
int main()
{
while (scanf("%d",&n)!=EOF)
{
memset(f,0,sizeof(f));
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++)
a[i][j]=read();
}
init(n);
for (int i=1;i<=tmp;i++)
f[1][i]=count(1,cnt[i]);
for (int i=2;i<=n;++i)
{
for (int j=1;j<=tmp;++j)
{
int tt=count(i,cnt[j]);
for (int k=1;k<=tmp;++k)
{
if ((cnt[j]&cnt[k])) continue;
f[i][j]=max(f[i-1][k]+tt,f[i][j]);
}
}
}
int ans=0;
for (int i=1;i<=tmp;i++)
ans=max(ans,f[n][i]);
cout<<ans<<endl;
}
return 0;
}