#线性动态规划#CH 5104 SGU 167 I-country

题目

在一个n×mn\times mn×m的矩阵,找出一个含有k个格子的凸连通块(中间没有空缺),使权值最大,并输出方案


分析

既然要选出凸连通块且没有空缺,假定选择lllrrr行,那么第iii行(l≤i≤rl\leq i\leq rlir)选择的区间[li…ri](li−1≤li≤li+1,ri−1≤ri≤ri+1)[l_i\dots r_i](l_{i-1}\leq l_i\leq l_{i+1},r_{i-1}\leq r_i\leq r_{i+1})[liri](li1lili+1,ri1riri+1),然而已经很接近了,对于输出方案需要特判,在此不多讲
状态转移方程f[row+1][l][r][0/1(左边无/有凸起)][0/1(右边无/有凸起)][占用格子数]=max(f[row][l][r][0/1][0/1][k]+∑i=lrg[row][i])f[row+1][l][r][0/1(左边无/有凸起)][0/1(右边无/有凸起)][占用格子数]=max(f[row][l][r][0/1][0/1][k]+\sum_{i=l}^{r}g[row][i])f[row+1][l][r][0/1(/)][0/1(/)][]=max(f[row][l][r][0/1][0/1][k]+i=lrg[row][i]),然后初始化就是=前缀和


代码

#include <cstdio>
#include <cstring>
#define anns pre[mx][lx][rx][lp][rp][k]//存方案的地方(状态压缩)
int f[2][16][16][2][2][226],pre[16][16][16][2][2][226],n,m,k,s[16][16],ans,mxx,lxx,rxx,lpp,rpp;
int in(){//逐字符输入
	int ans=0; char c=getchar();
	while (c<48||c>57) c=getchar();
	while (c>47&&c<58) ans=ans*10+c-48,c=getchar();
	return ans;
}
int max(int a,int b){return (a>b)?a:b;}
void update(int mx,int lx,int rx,int lp,int rp,int p){
	if (mx==n) return;//已经到了最后一层不需要更新
	for (register int nl=(lp?lx:1);nl<=rx;nl++)//左端点可能会凸起
	for (register int nr=max(lx,nl);nr<=(rp?m:rx);nr++){//右端点可能会凸起
		int t1,t2;
		if (nl==lx) t1=lp; else t1=nl>=lx;//是否存在凸起
		if (nr==rx) t2=rp; else t2=nr>=rx;
		if (f[mx&1^1][nl][nr][t1][t2][p+nr-nl+1]<f[mx&1][lx][rx][lp][rp][p]+s[mx+1][nr]-s[mx+1][nl-1]) //动态规划
		    f[mx&1^1][nl][nr][t1][t2][p+nr-nl+1]=f[mx&1][lx][rx][lp][rp][p]+s[mx+1][nr]-s[mx+1][nl-1],
		    pre[mx+1][nl][nr][t1][t2][p+nr-nl+1]=(lx<<12)+(rx<<8)+(lp<<4)+rp;//记录方案,状态压缩
	}
}
void dp(){
	for (register int mx=1;mx<=n;mx++){
    memset(f[mx&1^1],-1,sizeof(f[mx&1^1]));//初始化为-1
	    for (register int lx=1;lx<=m;lx++)
	    for (register int rx=lx;rx<=m;rx++)
	    for (register int lp=0;lp<2;lp++)
	    for (register int rp=0;rp<2;rp++){
		    f[mx&1][lx][rx][lp][rp][rx-lx+1]=s[mx][rx]-s[mx][lx-1];//前缀和
		    for (register int p=rx-lx+1;p<=k;p++)
		    if (f[mx&1][lx][rx][lp][rp][p]>0){//存在答案
			    update(mx,lx,rx,lp,rp,p);//更改
			    if (p==k&&f[mx&1][lx][rx][lp][rp][p]>ans){//更大的答案
				    ans=f[mx&1][lx][rx][lp][rp][p];
				    mxx=mx; lxx=lx; rxx=rx; lpp=lp; rpp=rp;
			    }
		    }
	    }
    }
}
void print(int ans){if (ans>9) print(ans/10); putchar(ans%10+48);}//逐字符输出
void write(int mx,int lx,int rx,int lp,int rp,int k){
	if (!mx||k<=0) return;//没有格子或者走到底退出
	write(mx-1,(anns>>12)%16,(anns>>8)%16,(anns>>4)%16,anns%16,k-rx+lx-1);//pre数组状态压缩
	for (register int i=lx;i<=rx;i++)
	    print(mx),putchar(' '),print(i),putchar('\n');//输出方案
}
int main(){
	n=in(); m=in(); k=in();
	for (register int i=1;i<=n;i++)
	for (register int j=1;j<=m;j++) s[i][j]=s[i][j-1]+in();//前缀和
	memset(f[1],-1,sizeof(f[1]));//初始化为-1
	dp(); printf("Oil : %d\n",ans);
	write(mxx,lxx,rxx,lpp,rpp,k);//输出方案
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值