简单动态规划(2)——从入门到放弃

本文深入探讨了动态规划中的区间DP问题,包括SCOI2007的压缩问题、Dire Wolf(HDU5115)的解题策略、祖玛(JSOI2007)的颜色联通块处理以及SCOI2005和SCOI2008的二进制状态压缩方法。通过具体题目实例解析,帮助读者理解并掌握区间DP的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

又强行凑了一波...

今天我们来讲区间和状压

区间DP的水题

已经不打算写任何概论了XD

(1)压缩(SCOI2007)

题面见链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1068

对于每一个区间(l,r)我们都可以进行如下操作:

如果这个区间可以在i处被分成两段进行压缩,那么我们更新的结果就是f[l][i]+f[i+1][r]+1(1为M)

反之,则只压缩其中一段

如果该区间有两个完全相同的字符串,则直接合并一个串为R

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
	int i=0,f=1;
	char ch;
	for(ch=getchar();!isdigit(ch);ch=getchar())
		if(ch=='-') f=-1;
	for(;isdigit(ch);ch=getchar())
		i=(i<<3)+(i<<1)+(ch^48);
	return i*f;
}
int buf[1024];
inline void write(int x){
	if(!x){putchar('0');return ;}
	if(x<0){putchar('-');x=-x;}
	while(x){buf[++buf[0]]=x%10,x/=10;}
	while(buf[0]) putchar(buf[buf[0]--]+48);
	return ;
}
#define stan 111
char a[stan];
int f[stan][stan][stan],len;
bool check(int l,int r){
	int sze=r-l+1;
	if(sze&1) return false;
	for(int i=l;i<=(l+r)/2;++i)
		if(a[i]!=a[i+sze/2]) return 0;
	return true;
}
int dp(int l,int r,bool tag){
	int sze=r-l+1;
	if(sze==1) return 1;
	if(f[l][r][tag]!=-1) return f[l][r][tag];
	if(tag)
		for(int i=l;i<r;++i)
			sze=min(sze,dp(l,i,1)+dp(i+1,r,1)+1);
	for(int i=l;i<r;++i)
		sze=min(sze,dp(l,i,tag)+r-i);
	if(check(l,r))
		sze=min(sze,dp(l,(l+r)/2,0)+1);
	return f[l][r][tag]=sze;
}
signed main(){
	scanf("%s",a+1);
	len=strlen(a+1);
	memset(f,255,sizeof(f));
	write(dp(1,len,1));
	return 0;
}

(2)Dire Wolf(HDU5115)

题面见链接http://acm.hdu.edu.cn/showproblem.php?pid=5115

枚举区间(i,j),表示消灭除i,j外的所有点

枚举(i,j)中的k点,表示消灭k点时k与i,j相邻

然后这道套路就出来了

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
	int i=0,f=1;
	char ch;
	for(ch=getchar();!isdigit(ch);ch=getchar())
		if(ch=='-') f=-1;
	for(;isdigit(ch);ch=getchar())
		i=(i<<3)+(i<<1)+(ch^48);
	return i*f;
}
int buf[1024];
inline void write(int x){
	if(!x){putchar('0');return ;}
	if(x<0){putchar('-');x=-x;}
	while(x){buf[++buf[0]]=x%10,x/=10;}
	while(buf[0]) putchar(buf[buf[0]--]+48);
	return ;
}
#define stan 222
int n,a[stan],ts,b[stan],f[stan][stan];
signed main(){
	int T=read();
	while(scanf("%d",&n)!=EOF){
		++ts;
		memset(b,0,sizeof(b));
		for(int i=1;i<=n;++i)
			a[i]=read();
		for(int i=1;i<=n;++i)
			b[i]=read();
		memset(f,127,sizeof(f));
		for(int i=0;i<=n+1;++i)
			f[i][i]=f[i][i+1]=0;
		for(int i=n-1;i>=0;--i)
			for(int j=i+2;j<=n+1;++j)
				for(int k=i+1;k<j;++k)
					f[i][j]=min(f[i][j],f[i][k]+f[k][j]+a[k]+b[i]+b[j]);
		printf("Case #%d: ",ts);write(f[0][n+1]);puts("");
	}
	return 0;
}

(3)祖玛(JSOI2007)

都知道数据有误

题面见链接http://www.lydsy.com/JudgeOnline/problem.php?id=1032

这次我们依然枚举区间(i,j),但这次我们先预处理为颜色的联通块(500不压会T得很惨)

同时此次枚举表示处理包括i,j在内该区间的所有联通块

状态还是比较好想的

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
	int i=0,f=1;
	char ch;
	for(ch=getchar();!isdigit(ch);ch=getchar())
		if(ch=='-') f=-1;
	for(;isdigit(ch);ch=getchar())
		i=(i<<3)+(i<<1)+(ch^48);
	return i*f;
}
int buf[1024];
inline void write(int x){
	if(!x){putchar('0');return ;}
	if(x<0){putchar('-');x=-x;}
	while(x){buf[++buf[0]]=x%10,x/=10;}
	while(buf[0]) putchar(buf[buf[0]--]+48);
	return ;
}
#define stan 555
int n,co[stan],lef[stan],righ[stan],f[stan][stan],cnt,x;
signed main(){
	n=read();
	for(int i=1;i<=n;++i){
		x=read();
		if(i==1||x!=co[cnt]){
			co[++cnt]=x;
			lef[cnt]=i;
			righ[cnt-1]=i-1;
		}
	}
	memset(f,127,sizeof(f));
	righ[cnt]=n;
	for(int i=1;i<=cnt;++i)
		f[i][i]=(lef[i]==righ[i])?2:1;
	n=cnt;
	for(int i=n-1;i;--i)
		for(int j=i+1;j<=n;++j){
			for(int k=i;k<j;++k)
				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
			if(co[i]==co[j])
				if(lef[i]==righ[i]&&lef[j]==righ[j]) f[i][j]=min(f[i][j],f[i+1][j-1]+1);
				else f[i][j]=min(f[i][j],f[i+1][j-1]);
		}
	write(f[1][n]);
	return 0;
}


状态压缩DP的水题

现在我们来做状态压缩DP

(1)互不侵犯(SCOI2005)

题面依旧见链接http://www.lydsy.com/JudgeOnline/problem.php?id=1087

这个题我们要做的就是用二进制表示状态

然后我们只需要对相邻行状态进行比较即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#define int long long
using namespace std;
inline int read(){
	int i=0,f=1;
	char ch;
	for(ch=getchar();!isdigit(ch);ch=getchar())
		if(ch=='-') f=-1;
	for(;isdigit(ch);ch=getchar())
		i=(i<<3)+(i<<1)+(ch^48);
	return i*f;
}
int buf[1024];
inline void write(int x){
	if(!x){putchar('0');return ;}
	if(x<0){putchar('-');x=-x;}
	while(x){buf[++buf[0]]=x%10,x/=10;}
	while(buf[0]) putchar(buf[buf[0]--]+48);
	return ;
}
#define stan 512
#define sten 11
#define stin 33
int n,k,f[sten][stan][stin],cntking[stan],ans,t;
signed main(){
	n=read();k=read();
	if(k>25||k>=n*n){
		puts("0");
		return 0;
	}else{
		t=1<<n;
		f[0][0][0]=1;
		cntking[0]=0;
		for(int i=1;i<t;++i)
			cntking[i]=cntking[i>>1]+(i&1);
		for(int i=1;i<=n;++i)
			for(int j=0;j<t;++j)
				if(cntking[j]<=k&&!(j&(j>>1)))
					for(int l=0;l<t;++l)
						if(cntking[l]<=k&&!(l&(l>>1))&&!(l&j)&&!(l&(j>>1))&&!(l&(j<<1)))
							for(int o=cntking[j]+cntking[l];o<=k;++o)
								f[i][j][o]+=f[i-1][l][o-cntking[j]];
		for(int i=0;i<t;++i)
			if(cntking[i]<=k)
				ans+=f[n][i][k];
		write(ans);
	}
	return 0;
}

(2)奖励关(SCOI2008)

题面见链接点击打开链接

首先我们先明确本步期望=(上一步期望+本步得分)/k

然后对于其前置状态,只需要以状态压缩进行check即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
using namespace std;
inline int read(){
	int i=0,f=1;
	char ch;
	for(ch=getchar();!isdigit(ch);ch=getchar())
		if(ch=='-') f=-1;
	for(;isdigit(ch);ch=getchar())
		i=(i<<1)+(i<<3)+(ch^48);
	return i*f;
}
int buf[1024];
inline void write(int x){
	if(!x){putchar('0');return ;}
	if(x<0){putchar('-');x=-x;}
	while(x){buf[++buf[0]]=x%10,x/=10;}
	while(buf[0]) putchar(buf[buf[0]--]+48);
	return ;
}
#define stan 111
#define sten 1<<16
int pos[stan],k,n,val[stan],tmp,need[stan];
double f[stan][sten];
signed main(){
	for(int i=1;i<=16;++i)
		pos[i]=1<<(i-1); 
	k=read();n=read();
	for(int i=1;i<=n;++i){
		val[i]=read();
		tmp=read();
		while(tmp){
			need[i]+=pos[tmp];
			tmp=read();
		}
	}
	for(int i=k;i;--i)
		for(int j=0;j<pos[n+1];++j){
			for(int l=1;l<=n;++l){
				if((need[l]&j)==need[l])
					f[i][j]+=max(f[i+1][j],f[i+1][pos[l]|j]+val[l]);
				else
					f[i][j]+=f[i+1][j];
			}
			f[i][j]/=n;
		}
	printf("%.6lf",f[1][0]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值