coi 2013-2014 round 5 domine


对于这样一个问题我们可以这样来看,对于棋盘的一个点,假如在这个地方放一个骨牌,那么它能对那些造成影响呢?很明显它会对与这个点相邻的点造成影响。

这样我们把棋盘上的点黑白染色,黑点向白点连边,费用为负权值和,流量为1,这样我们就把这个问题转化成了最小费用最大流的问题,然后再设一个点向超级起点建一个流量为K的边。这样就能确保我们最多会放K个。

所以最小费用最大流可以解决这个问题。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define N 10000
#define ll long long
using namespace std;
int p=1;
ll ans;
int map[N][4],n,dian[N][4],dui[N][4],bj[N][4],zai[N][10];
int head[N],use[N],dis[N],que[N],tot,s,t,s1,k;
struct data
{
	int go,next,wgt,f;
}edge[200000];
struct hh
{
	int f,da,num;
}jj[N];
void build(int a,int b,int c,int d)
{
	p++;
	edge[p].go=b;
	edge[p].next=head[a];
	head[a]=p;
	edge[p].wgt=c;
	edge[p].f=d;
}
void addedge(int a,int b,int c,int d)
{
	build(a,b,c,d);
	build(b,a,-c,0);
}
void up()
{
	int k=jj[t].da;
	for (int i=t;i!=s1;i=jj[i].f)
	{
		int node=jj[i].num;
		edge[node].f-=k;
		edge[node^1].f+=k;
	}
}
int spfa()
{
	int tou=1,wei=1;
	memset(use,0,sizeof(use));
	for (int i=1;i<=t;i++) dis[i]=-1000000000;
	que[1]=s1;
	use[s1]=1;
	dis[s1]=0;
	jj[s1].da=12345678;
	for (;tou<=wei;tou++)
	{
		int node=que[tou%N];
		for (int i=head[node];i;i=edge[i].next)
		{
			if (dis[node]+edge[i].wgt>dis[edge[i].go]&&edge[i].f>0)
			{
				dis[edge[i].go]=dis[node]+edge[i].wgt;
				jj[edge[i].go].f=node;
				jj[edge[i].go].da=min(jj[node].da,edge[i].f);
				jj[edge[i].go].num=i;
				if (!use[edge[i].go])
				{
					use[edge[i].go]=1;
					wei++;
					que[wei%N]=edge[i].go;
				}
			}
		}
		use[node]=0;
	}
	return dis[t];
}
int main()
{
	freopen("domine.in","r",stdin);
	freopen("domine.out","w",stdout);
	scanf("%d%d",&n,&k);
	s1=9002;
	s=9000;
	t=s+1;
	addedge(s1,s,0,k);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=3;j++)
			scanf("%d",&map[i][j]);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=3;j++)
			{
				dui[i][j]=++tot;
				dian[i][j]=++tot;
				addedge(dui[i][j],dian[i][j],0,1);
			}
	for (int i=1;i<=n;i++)
	 	{
	 		if (i%2) bj[i][1]=1;
	 		else bj[i][1]=0;
	 		for (int j=2;j<=3;j++) 
	 		bj[i][j]=bj[i][j-1]^1;
	 	}
	for (int i=1;i<=n;i++)
		for (int j=1;j<=3;j++) 
		zai[i][j]=1;
	for (int i=1;i<=n;i++)
		for (int j=1;j<=3;j++)
			{
				if (bj[i][j]==1)
				{
					if (zai[i][j+1]) addedge(dian[i][j],dui[i][j+1],map[i][j]+map[i][j+1],1);
					if (zai[i][j-1]) addedge(dian[i][j],dui[i][j-1],map[i][j]+map[i][j-1],1);
					if (zai[i+1][j]) addedge(dian[i][j],dui[i+1][j],map[i][j]+map[i+1][j],1);
					if (zai[i-1][j]) addedge(dian[i][j],dui[i-1][j],map[i-1][j]+map[i][j],1);<span style="font-family: Arial, Helvetica, sans-serif;">			addedge(s,dian[i][j],0,1);</span>
				}
				else 
				{
					addedge(dian[i][j],t,0,1);
				}
			}
	int time=0;
	while (1)
	{
		int x=spfa();
		time++;
		if (time==k+1) break;
		ans=ans+(ll)(x);
		up();
	}
	printf("%I64d\n",ans);
	return 0;	
}
代码很丑。

接下来算下空间时间

一共有4N+3个点,大概8N的边,而由于是二分图,所以最小费用最大流速度会很快。

  还有另外一种算法。

  定义状态 F[I][J][K],表示前i行,放J个,当前行的状态为K对后面的影响,这样我们可以使用状态压缩DP来做。

 一位大牛的代码,我考试的时候只写了费用流。

<pre name="code" class="cpp">#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000+10,lolo=0X7fffffff-1;

int n,kk,now,A,B,C;
int map[maxn][3];
int f[maxn][maxn][8];
void gengxin(int w,int zhi,int ge,int now){
	if (ge+B>kk) return;
	if (w>3){
		//如果当前的位置大于3,则直接更新; 
		f[A+1][B+ge][now]=max(f[A+1][B+ge][now],f[A][B][C]+zhi);
		return;
	}
	if ((C>>(w-1))&1) gengxin(w+1,zhi,ge,now);//如果当前是1则不更新下一位置因为不会对后面造成影响。否则就求出对下面的影响。 
	else{
		if (w<=2&&(!((C>>w)&1))) gengxin(w+2,zhi+map[A][w]+map[A][w+1],ge+1,now);
		gengxin(w+1,zhi+map[A][w]+map[A+1][w],ge+1,now|(1<<(w-1)));//放牌。 
		gengxin(w+1,zhi,ge,now);//不放。 
	}	
}
void chuli(){
	for(int i=0;i<=n;i++)
	 for (int j=0;j<=kk;j++)
	  for (int k=0;k<=7;k++)
	   if(f[i][j][k]!=-lolo) 
	{
		A=i;B=j;C=k;
		gengxin(1,0,0,0);
	}
}
void pretreatment(){
	for (int i=0;i<maxn;i++)
	 for (int j=0;j<maxn;j++)
	  for (int k=0;k<8;k++) f[i][j][k]=-lolo;
	f[0][0][7]=0;
}
void reading(){
	scanf("%d%d",&n,&kk);
	for (int i=1;i<=n;i++) 
	 for (int j=1;j<=3;j++)scanf("%d",&map[i][j]);
}
int main(){
	freopen("domine.in","r",stdin);
	freopen("domine.out","w",stdout);
	reading();
	pretreatment();
	chuli();
	printf("%d",f[n+1][kk][0]);
	return 0;
}


 


   



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值