带有技巧的动态规划(洛谷)

ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
动态规划:https://blog.csdn.net/weixin_39778570/article/details/87014343

P1541 乌龟棋

给定一个1*n数组,现在你有m张牌,牌的大小为1~4,即你有4种牌,当你使用面值为
i的牌,你就可以走i步,走到数组上的某个点,获取数组的分数。问怎么走分数最大。
--------------------------------------------------------------------------
显然不同的顺序分数不一样。
一共只有4张牌,我们可以使用4维dp,dp每次使用哪一种牌。
当数据量大时,可以使用滚动数组滚动到3个维度。
#include<bits/stdc++.h>
#define ll long long 
#define fo(i,j,n) for(register int i=j; i<=n; ++i)

using namespace std;
int n,m,num[400],r[5],dp[50][50][50][50];
void solve(){
	dp[0][0][0][0] = num[1];
	fo(a,0,r[1]){
		fo(b,0,r[2]){
			fo(c,0,r[3]){
				fo(d,0,r[4]){
					int t = 1+a+b*2+c*3+d*4;
					if(a) dp[a][b][c][d] = max(dp[a][b][c][d], dp[a-1][b][c][d]+num[t]);
					if(b) dp[a][b][c][d] = max(dp[a][b][c][d], dp[a][b-1][c][d]+num[t]);
					if(c) dp[a][b][c][d] = max(dp[a][b][c][d], dp[a][b][c-1][d]+num[t]);
					if(d) dp[a][b][c][d] = max(dp[a][b][c][d], dp[a][b][c][d-1]+num[t]);
				}
			}
		}
	}
	printf("%d\n",dp[r[1]][r[2]][r[3]][r[4]]);
} 
int main(){
	scanf("%d%d",&n,&m);
	fo(i,1,n)scanf("%d",&num[i]);
	int x;
	fo(i,1,m)scanf("%d",&x),r[x]++;
	solve();
	return 0;
}

滚动掉i这个维度

#include<bits/stdc++.h>
using namespace std;
long long dp[41][41][41],n,m;
int a[1000],b[5],c;
int main(){
    cin>>n>>m;//输入
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=m;i++){cin>>c;b[c]++;}//记录牌
    memset(dp,0xf3,sizeof(dp));//初始化
    dp[0][0][0]=0;
    for(int i=0;i<=b[1];i++)
       for(int j=0;j<=b[2];j++)
          for(int k=0;k<=b[3];k++)
             for(int l=0;l<=b[4];l++){//和前面一样的暴力
                 int t=a[i+2*j+3*k+4*l+1];//算出现在在的点
                 // 滚动
                 if(j)dp[j][k][l]=max(dp[j-1][k][l],dp[j][k][l]);//也和前面一样
                 if(k)dp[j][k][l]=max(dp[j][k-1][l],dp[j][k][l]);
                 if(l)dp[j][k][l]=max(dp[j][k][l-1],dp[j][k][l]);
                 // 结束
                 dp[j][k][l]+=t;
             }
    cout<<dp[b[2]][b[3]][b[4]];//输出
    return 0;
}

P1026 统计单词个数

给定一段字符,(每行20个字符, 拼成一串),k个单词,把字符串分为k串,问最多有多少个单词。
对于两个单词,不能使用同一个首字符,例如this,可以包含this和is单词,但this和th单词自能二选一。
--------------------------------------------------------------------------------------
先预处理出,sum[i][j]表示i到j有多少个单词
dp[i][j]表示结尾为i,分为j份,最多有多少个单词
k = [j-1,i-1]
dp[i][j] = max(dp[i][j], dp[k][j-1]+sum[k+1][i])
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
// dp[i][j] 表示断点在i,分为j段总单词数 
int p,k,n,m,dp[250][50],sum[250][250];
char s[300],a[10][10];
// 判断s[l,r]开头是否有单词 
bool check(int l, int r){
	fo(i,1,n){
		if(a[i][1]==s[l]){
			bool flag = 1;
			int len = strlen(a[i]+1);
			fo(j,1,len)if(l+j-1>r || a[i][j]!=s[l+j-1])flag = 0;
			if(flag)return 1;
		}
	}
	return 0;
}
// 预处理出s[i,j]间有多少个单词 
void pre(){
	m = strlen(s+1);
	for(int i=m; i>=1; i--){ // 头个字母不能多次使用,故倒叙枚举 
		for(int j=i; j>=1; j--){ // 区间由小到大,倒叙枚举 
			sum[j][i] = sum[j+1][i];
			if(check(j,i))sum[j][i]++;
		}
	}
}
void solve(){
	pre();
	// dp, dp[i][j] 由 dp[k][j-1]+sum[k+1][i]转移过来, k=[j-1,i-1] 
	fo(i,1,m){
		for(int j=1; j<=k&&j<=m; j++){
			for(int k=j-1; k<i; k++)
				dp[i][j] = max(dp[i][j], dp[k][j-1]+sum[k+1][i]);
		}
	}
	printf("%d\n",dp[m][k]);
}
int main(){
	scanf("%d%d",&p,&k);
	fo(i,0,p-1)scanf("%s",s+1+(i*20));
	scanf("%d",&n);
	fo(i,1,n)scanf("%s",a[i]+1);
	solve();
	return 0;
} 

P1063 能量项链

(2,3)(3,5)(5,10)(10,2) 表示1,2,3,4四个珠子,现在要把珠子合成一串,如下表示组大合成顺序。
((4⊕1)⊕2)⊕3)= 10×2×3 + 10×3×5 + 10×5×10 = 710。释放710能量。
求最大合成释放的能量。
----------------------------------------------------------------------------------
裸环形区间dp
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n;
ll a[250],dp[250][250];
ll calc(int i, int k, int j){
	return a[i]*a[k+1]*a[j+1];
}
void solve(){
	for(int r=2; r<=n; r++){
		for(int i=1; i+r-1<2*n; i++){ // 这里枚举左端点,判断右端点不越界(不是i<=n....) 
			int j=i+r-1;
			for(int k=i; k<j; k++){ // 枚举左断点 
				dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j]+calc(i,k,j));			
			}
		}
	}
	ll ans = 0;
	fo(i,1,n)ans = max(ans, dp[i][i+n-1]);
	printf("%lld\n",ans);
}
int main(){
	scanf("%d",&n);
	fo(i,1,n)scanf("%lld",&a[i]),a[i+n]=a[i];
	solve();
	return 0;
} 

P1156 垃圾陷

	背包类型,好题
题意:奶牛在井底生命值为10,从上面丢g个垃圾下来,丢垃圾的时间为Ti,吃了增加生命值Li,垃圾高度为Hi
	  问奶牛最快踩这垃圾出去的时间?如果出不去输出奶牛最长活多就(生命值最大多少)
	  ----------------------------------------------------------------------------------
类似01背包
这里有3个变量,垃圾掉落的时间(-->生命值),奶牛所在高度,垃圾增加的时间(价值-->生命值)
	垃圾掉落的时间用来判断奶牛能否活到该垃圾掉落
	增加时间是累加的答案
	高度则是状态
	所以我们dp高度,dp[i][j] 表示在第i个垃圾时,高度为j时,奶牛能生存到时刻dp[i][j]
	可以使用一维数组倒叙枚举高度....
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i) 
using namespace std;
struct node{
	int t,l,h;
	bool operator < (const node &p)const{
		return t<p.t;
	}
}a[105];
int d,g,f[105];
// 一维 f[i]表示目前在i高度,最多活到时刻f[i] 
void solve(){
	sort(a+1,a+1+g); // 按垃圾丢下的时间排序 
	f[0] = 10; // 高度为0一开始拥有10生命 
	fo(i,1,g){ // 枚举垃圾 
		for(int j=d; j>=0; j--){ // 01背包倒序枚举高度 
			if(f[j]>=a[i].t){ // 能活到第i个垃圾掉落 
				if(j+a[i].h>=d){
					printf("%d\n",a[i].t);
					return;
				}
				f[j+a[i].h] = max(f[j+a[i].h], f[j]); // 不进食,由前面的增高过来,活着的时刻一样多 
				f[j] += a[i].l; // 进食不增高 
			} 
		}
	}
	cout<<f[0]<<endl; // 出不去,则活最长的是在0高度的生命值 
} 
// 二维 
int dp[105][105];
void solve2(){
	sort(a+1,a+1+g);
	dp[0][0]=10;
	int ans = 10;
	fo(i,1,g){
		for(int j=0; j<=d; j++){
			if(j>=a[i].h && dp[i-1][j-a[i].h]>=a[i].t){ // 上一个阶段能活到下一个阶段 
				if(j>=d){ // 能跳出井口 
					printf("%d\n",a[i].t);
					return;
				}
				dp[i][j] = max(dp[i][j], dp[i-1][j-a[i].h]); // 不吃 
				dp[i][j-a[i].h] = max(dp[i][j-a[i].h], dp[i-1][j-a[i].h]+a[i].l); // 吃 
				ans = max(ans, max(dp[i][j], dp[i][j-a[i].h])); // 计算存活最长时间 
			}
		}
	}
	cout<<ans<<endl; // 出不去,则活最长的是在0高度的生命值 
} 
int main(){
	scanf("%d%d",&d,&g);
	fo(i,1,g)scanf("%d%d%d",&a[i].t,&a[i].l,&a[i].h);
	solve2();
	return 0;
}

P1052 过河

一只青蛙起始位置在0,河道长度为1—L,有些点有石头,青蛙每次能条[S,T]长度,问到达L
或超过L最少踩多少个石头
-----------------------------------------------------------------------------
DP方程很容易写,但是数组(L)太大,路径可以求模进行压缩
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
ll L,S,T,M,a[200];
map<ll,int> mp;
ll f[200005];
void solve(){
	sort(a+1,a+1+M); // 注意要对石头位置排序.... 
	// 路径压缩
	ll cnt = 0;
	fo(i,1,M){
		//两点间的距离d大于t时,一定可以由d%t跳过来
		//所以最多只需要t+d%t种距离的状态就可以表示这两个石子之间的任意距离关系
		if(a[i]-a[i-1]>T){
			cnt += (a[i]-a[i-1])%T+T; // 保留一段循环节,作为dp过程否则会WA ,cnt最长2*M*T
			// 上面的过程同下,以防求模之后小于S 
		//	if((a[i]-a[i-1])%T<=S)cnt += (a[i]-a[i-1])%T+T; 
		//	else cnt += (a[i]-a[i-1])%T;
		}else{
			cnt += a[i]-a[i-1]; 
		}
		mp[cnt] = 1;
	} 
	fo(i,1,cnt+T)f[i] = M;
	f[0]=0;
	fo(i,1,cnt+T){
		fo(j,S,T){
			if(i-j>=0)
				f[i] = min(f[i], f[i-j]+mp[i]);
		}
	}
	ll ans = M;
	fo(i,cnt,cnt+T)ans = min(ans,f[i]);
	cout<<ans<<endl; 
} 
int main(){
	cin>>L>>S>>T>>M;
	fo(i,1,M)cin>>a[i];
	solve();
	return 0;
} 

使用单调上升列队

#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
ll L,S,T,M,a[200];
map<ll,int> mp;
ll f[200005],q[200005];
void solve(){
	sort(a+1,a+1+M); // 注意要对石头位置排序.... 
	// 路径压缩
	ll cnt = 0;
	fo(i,1,M){
		//两点间的距离d大于t时,一定可以由d%t跳过来
		//所以最多只需要t+d%t种距离的状态就可以表示这两个石子之间的任意距离关系
		if(a[i]-a[i-1]>T){
			cnt += (a[i]-a[i-1])%T+T; // 保留一段循环节,作为dp过程否则会WA ,cnt最长2*M*T
			// 上面的过程同下,以防求模之后小于S 
		//	if((a[i]-a[i-1])%T<=S)cnt += (a[i]-a[i-1])%T+T; 
		//	else cnt += (a[i]-a[i-1])%T;
		}else{
			cnt += a[i]-a[i-1]; 
		}
		mp[cnt] = 1;
	} 
	fo(i,1,cnt+T)f[i] = M;
	f[0]=0;
	// T长度的单调列队 
	int l,r;
	q[l=1]=q[r=1]=0;
	fo(i,1,cnt+T){
		while(l<=r&&(i-q[l])>T)l++;
		if(l<=r&&(i-q[l])>=S&&(i-q[l])<=T)f[i] = min(f[i],f[q[l]]+mp[i]);
		else{
			fo(j,S,T)if(i-j>=0)
				f[i] = min(f[i], f[i-j]+mp[i]);
		}
		while(l<=r&&f[q[r]]>=f[i])r--; // 可能会使得选择区间全丢失,而进入的i下次选择不可用 
		q[++r]=i;
	}
	ll ans = M;
	fo(i,cnt,cnt+T)ans = min(ans,f[i]);
	cout<<ans<<endl; 
} 
int main(){
	cin>>L>>S>>T>>M;
	fo(i,1,M)cin>>a[i];
	solve();
	return 0;
} 
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页