acm-动态规划题目集合

引言

动态规划一直以来是acm中的一大热点,而且由于难度
高的缘故,总是能结合其它算法出成一道综合难题。本
文主要记录关于动态规划的各种题目,并给出详细地分
析。

动态规划有许多的类型,本文将题目分成若干个类型进行讲解


序列dp

例题一
题目来源:2020上海高校程序设计竞赛暨第18届上海大学程序设计联赛夏季赛(同步赛)G题:选择
题面:
例题一page1
题解:设dp[i]表示从前i个数中挑选 ⌊ i 2 ⌋ \mathbf{\lfloor \frac i2 \rfloor} 2i个数的最大和。设sm[i]表示前i个数中下标与i奇偶性相同的数之和。初始时由于a[x]必须选,所以不妨将a[x]加上一个大整数,这样能保证a[x]必定被选中,dp完以后再减去即可。
大致的dp思路是分奇偶性讨论,而转移的依据是按照a[i]是否选择来考虑。这种思想非常类似于01背包,即选或不选,只是这里还要分一下奇偶性来讨论。
当i为奇数的时候,dp[i]可以由dp[i-1]转移而来,表示不选择a[i],这时候必定要在前i-1个数中选择 i − 1 2 \mathbf{\frac{i-1}{2}} 2i1(i-1为偶数)个数使得和最大。此外,dp[i]还可以由dp[i-2]转移而来,这时候就必须要选择a[i]
当i为偶数的时候,必须要注意到dp[i]不能直接由dp[i-1]转移而来,这是由于 ⌊ i − 1 2 ⌋ < i 2 \mathbf{\lfloor \frac{i-1}{2}\rfloor < \frac i2} 2i1<2i,因此当a[i]不选的话,就必须选择a[i-1],a[i-2],a[i-4]…,否则不满足选择 i 2 \mathbf{\frac i2} 2i的条件,于是在a[i]不选的情况下a[i]必定从sm[i-1]转移而来。当a[i]选择的时候,dp[i]必定可以从dp[i-2]转移过来。
综上所述,转移方程如下:
d p [ i ] = { m a x { d p [ i − 1 ] , d p [ i − 2 ] + a [ i ] } , 如果  i  是 奇数 m a x { s m [ i − 1 ] , d p [ i − 2 ] + a [ i ] } , 如果  i  是 偶数 \mathbf{dp[i] = \begin{cases} max\{dp[i-1],dp[i-2]+a[i]\}, & \text{如果 }i\text{ 是 奇数} \\ max\{sm[i-1],dp[i-2]+a[i]\}, & \text{如果 }i\text{ 是 偶数} \end{cases}} dp[i]={max{dp[i1],dp[i2]+a[i]},max{sm[i1],dp[i2]+a[i]},如果 i  奇数如果 i  偶数

代码实现:

#include <bits/stdc++.h>
#define FOR(i,a,b) for(register int i=(a);i<(b);++i)
#define ROF(i,a,b) for(register int i=(a);i>=(b);--i)
#define pi pair<int,int>
#define mk(a,b) make_pair(a,b)
#define mygc(c) (c)=getchar()
#define mypc(c) putchar(c)
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef double db;
const int maxn = 200005;
const int maxm = 100;
const int inf = 2147483647;
typedef long long ll;
const double eps = 1e-9;
const long long INF = 9223372036854775807ll;
ll qpow(ll a,ll b,ll c){ll ans=1;while(b){if(b&1)ans=ans*a%c;a=a*a%c;b>>=1;}return ans;}
inline void rd(int *x){int k,m=0;*x=0;for(;;){mygc(k);if(k=='-'){m=1;break;}if('0'<=k&&k<='9'){*x=k-'0';break;}}for(;;){mygc(k);if(k<'0'||k>'9')break;*x=(*x)*10+k-'0';}if(m)(*x)=-(*x);}
inline void rd(ll *x){int k,m=0;*x=0;for(;;){mygc(k);if(k=='-'){m=1;break;}if('0'<=k&&k<='9'){*x=k-'0';break;}}for(;;){mygc(k);if(k<'0'||k>'9')break;*x=(*x)*10+k-'0';}if(m)(*x)=-(*x);}
inline void rd(db *x){scanf("%lf",x);}
inline int rd(char c[]){int i,s=0;for(;;){mygc(i);if(i!=' '&&i!='\n'&&i!='\r'&&i!='\t'&&i!=EOF) break;}c[s++]=i;for(;;){mygc(i);if(i==' '||i=='\n'||i=='\r'||i=='\t'||i==EOF) break;c[s++]=i;}c[s]='\0';return s;}
inline void rd(int a[],int n){FOR(i,0,n)rd(&a[i]);}
inline void rd(ll a[],int n){FOR(i,0,n)rd(&a[i]);}
template <class T, class S> inline void rd(T *x, S *y){rd(x);rd(y);}
template <class T, class S, class U> inline void rd(T *x, S *y, U *z){rd(x);rd(y);rd(z);}
template <class T, class S, class U, class V> inline void rd(T *x, S *y, U *z, V *w){rd(x);rd(y);rd(z);rd(w);}
inline void wr(int x){if(x < 10) putchar('0' + x); else wr(x / 10), wr(x % 10);}
inline void wr(int x, char c){int s=0,m=0;char f[10];if(x<0)m=1,x=-x;while(x)f[s++]=x%10,x/=10;if(!s)f[s++]=0;if(m)mypc('-');while(s--)mypc(f[s]+'0');mypc(c);}
inline void wr(ll x, char c){int s=0,m=0;char f[20];if(x<0)m=1,x=-x;while(x)f[s++]=x%10,x/=10;if(!s)f[s++]=0;if(m)mypc('-');while(s--)mypc(f[s]+'0');mypc(c);}
inline void wr(db x, char c){printf("%.15f",x);mypc(c);}
inline void wr(const char c[]){int i;for(i=0;c[i]!='\0';i++)mypc(c[i]);}
inline void wr(const char x[], char c){int i;for(i=0;x[i]!='\0';i++)mypc(x[i]);mypc(c);}
template<class T> inline void wrn(T x){wr(x,'\n');}
template<class T, class S> inline void wrn(T x, S y){wr(x,' ');wr(y,'\n');}
template<class T, class S, class U> inline void wrn(T x, S y, U z){wr(x,' ');wr(y,' ');wr(z,'\n');}
template<class T> inline void wra(T x[], int n){int i;if(!n){mypc('\n');return;}FOR(i,0,n-1)wr(x[i],' ');wr(x[n-1],'\n');}
ll dp[maxn],a[maxn],sm[maxn];
const ll ad = 1e16;
int main(){
	int n,x;
	rd(&n,&x);rd(a+1,n);
	a[x]+=ad;sm[1]=a[1];FOR(i,2,n+1)sm[i]=sm[i-2]+a[i];
	FOR(i,2,n+1){
		if(i&1)dp[i]=max(dp[i-2]+a[i],dp[i-1]);
		else dp[i]=max(dp[i-2]+a[i],sm[i-1]);
	}
	printf("%lld\n",dp[n]-ad);
}

例题二
题目来源:AtCoder Beginner Contest 176F题:Brave CHAIN
题面:例题二page1
例题二page2
题解:每次操作会留下两张牌,同时也会加入新的三张牌,方便起见这里把留下的两种牌叫旧元,新的三张牌叫新元。首先,如果遇到了三张新元都是相同的,就让ans+1即可,这样一定是最优的,因为这三张牌的贡献最大是1,所以提前拿走这三张牌并不不会比留下更差。然后考虑去掉所有满足三个相同的新元,在剩下的序列上做dp。先考虑一个简单dp,设 d p [ i ] [ j ] [ k ] \mathbf{dp[i][j][k]} dp[i][j][k]表示第i次操作后旧元为jk对应的最大分数。那么很容易想到它的转移方程(具体见代码),不过这个dp是 O ( n 3 ) \mathbf{O(n^3)} O(n3)的,显然不能通过本题,考虑如何优化这个dp呢,可以从去除无用的更新状态的角度入手。先砍掉一维,也就是第一维,每次更新的时候对需要更新的jk进行更新。也就是说只有当dp值发生变动的时候再考虑更新,比如考虑将新元舍去的情况,这时候旧元原封不动,所以dp值是不会边的,也就不需要更新;而当新元中有两个相同的时候,一旦旧元中出现一个元素也与这两个相同,那么就要考虑+1的情况,不过稍微想一下能明白这是 O ( n ) \mathbf{O(n)} O(n)的,此外还有一些更新情况(总共4种,详见代码)。因此总复杂度为 O ( n 2 ) \mathbf{O(n^2)} O(n2)的,能够通过本题。具体细节的话详见代码(有点难描述QAQ)。

代码实现:

#include <bits/stdc++.h>
#define FOR(i,a,b) for(register int i=(a);i<(b);++i)
#define ROF(i,a,b) for(register int i=(a);i>=(b);--i)
#define pi pair<int,int>
#define mk(a,b) make_pair(a,b)
#define mygc(c) (c)=getchar()
#define mypc(c) putchar(c)
#define fi first
#define se second
#define ls(x) t[x].lson
#define rs(x) t[x].rson
#define d(a,b) dp[min(a,b)][max(a,b)]
using namespace std;
typedef long long ll;
typedef double db;
const int maxn = 2005;
const int maxm = 1000005;
const int inf = 2147483647;
typedef long long ll;
const double eps = 1e-9;
const long long INF = 9223372036854775807ll;
ll qpow(ll a,ll b,ll c){ll ans=1;while(b){if(b&1)ans=ans*a%c;a=a*a%c;b>>=1;}return ans;}
inline void rd(int *x){int k,m=0;*x=0;for(;;){mygc(k);if(k=='-'){m=1;break;}if('0'<=k&&k<='9'){*x=k-'0';break;}}for(;;){mygc(k);if(k<'0'||k>'9')break;*x=(*x)*10+k-'0';}if(m)(*x)=-(*x);}
inline void rd(ll *x){int k,m=0;*x=0;for(;;){mygc(k);if(k=='-'){m=1;break;}if('0'<=k&&k<='9'){*x=k-'0';break;}}for(;;){mygc(k);if(k<'0'||k>'9')break;*x=(*x)*10+k-'0';}if(m)(*x)=-(*x);}
inline void rd(db *x){scanf("%lf",x);}
inline int rd(char c[]){int i,s=0;for(;;){mygc(i);if(i!=' '&&i!='\n'&&i!='\r'&&i!='\t'&&i!=EOF) break;}c[s++]=i;for(;;){mygc(i);if(i==' '||i=='\n'||i=='\r'||i=='\t'||i==EOF) break;c[s++]=i;}c[s]='\0';return s;}
inline void rd(int a[],int n){FOR(i,0,n)rd(&a[i]);}
inline void rd(ll a[],int n){FOR(i,0,n)rd(&a[i]);}
template <class T, class S> inline void rd(T *x, S *y){rd(x);rd(y);}
template <class T, class S, class U> inline void rd(T *x, S *y, U *z){rd(x);rd(y);rd(z);}
template <class T, class S, class U, class V> inline void rd(T *x, S *y, U *z, V *w){rd(x);rd(y);rd(z);rd(w);}
inline void wr(int x){if(x < 10) putchar('0' + x); else wr(x / 10), wr(x % 10);}
inline void wr(int x, char c){int s=0,m=0;char f[10];if(x<0)m=1,x=-x;while(x)f[s++]=x%10,x/=10;if(!s)f[s++]=0;if(m)mypc('-');while(s--)mypc(f[s]+'0');mypc(c);}
inline void wr(ll x, char c){int s=0,m=0;char f[20];if(x<0)m=1,x=-x;while(x)f[s++]=x%10,x/=10;if(!s)f[s++]=0;if(m)mypc('-');while(s--)mypc(f[s]+'0');mypc(c);}
inline void wr(db x, char c){printf("%.15f",x);mypc(c);}
inline void wr(const char c[]){int i;for(i=0;c[i]!='\0';i++)mypc(c[i]);}
inline void wr(const char x[], char c){int i;for(i=0;x[i]!='\0';i++)mypc(x[i]);mypc(c);}
template<class T> inline void wrn(T x){wr(x,'\n');}
template<class T, class S> inline void wrn(T x, S y){wr(x,' ');wr(y,'\n');}
template<class T, class S, class U> inline void wrn(T x, S y, U z){wr(x,' ');wr(y,' ');wr(z,'\n');}
template<class T, class S, class U,class H> inline void wrn(T x, S y, U z,H h){wr(x,' ');wr(y,' ');wr(z,' ');wr(h,'\n');}
template<class T> inline void wra(T x[], int n){int i;if(!n){mypc('\n');return;}FOR(i,0,n-1)wr(x[i],' ');wr(x[n-1],'\n');}

int b[maxn*3],a[maxn*3],tot,dp[maxn][maxn],mx[maxn],mxas;
//mx[i]代表dp[min(i,j)][max(i,j)](1<=j<=n)中的最大值,mxas代表所有dp项中的最大值 

struct Node{
	int u,v,w;
};
vector<Node>g;
void up(int u,int v,int w){//dp的更新函数(这样写代码更简洁好看,方便能处理) 
	d(u,v)=max(d(u,v),w);
	mxas=max(mxas,w);
	mx[u]=max(mx[u],w);
	mx[v]=max(mx[v],w);
}
void rec(int u,int v,int w){//先记录需要更新的dp项,最后统一更新 
	g.push_back(Node{u,v,w});
}
int pre(int n){//这部分是预处理,目的是去掉所有的重复的三个元素(直接让ans++),让后将剩下的元素填到a中 
	int tot=0;//a中元素的个数 
	sort(b+1,b+6); 
	bool fg=1;
	FOR(i,1,4){//前5个元素特殊处理一下,看里面是否有三个相同的元素,有的话直接ans++,剩下的都放a中 
		if(b[i]==b[i+1] && b[i+1]==b[i+2]){
			ans++;
			FOR(j,1,i)a[++tot]=b[j];
			FOR(j,i+3,6)a[++tot]=b[j];
			fg=0;
			break;
		}
	}
	if(fg)FOR(i,1,6)a[++tot]=b[i];//前5个元素中没有重复的三个元素的话就全放a中 
	
	for(int i=6;i<3*n;i+=3){//从第6个元素开始三个三个的考虑,一旦遇到相同的三个就从b中剔除,然后ans++ 
		if(b[i]==b[i+1] && b[i+1]==b[i+2]){
			ans++;
		}else{
			a[++tot]=b[i],a[++tot]=b[i+1],a[++tot]=b[i+2];
		}
	} 
	a[++tot]=b[3*n];
	FOR(i,1,n+1)FOR(j,i,n+1)dp[i][j]=-inf/2,mx[i]=-inf/2,mxas=-inf/2;//初始化dp数组 
	up(a[1],a[2],0);//一开始有a[1]和a[2],直接更新为0即可。 
	return tot;
} 
int main(){
	int n,ans=0;
	rd(&n);rd(b+1,3*n);
	if(n==1)return wrn(b[1]==b[2] && b[2]==b[3]),0;
	
	pre(n);
	
	FOR(i,3,tot){//该部分是dp更新,也是程序的核心,方便起见设之前留下来的两个元素是旧元,新加入的三个元素是新元 
		g.clear();
		
		if(a[i]==a[i+1])swap(a[i],a[i+2]);//这一步将相同的元素放在i+1,i+2这两个位置,方便后面讨论。 
		else if(a[i]==a[i+2])swap(a[i],a[i+1]);
		
		if(a[i+1]==a[i+2])FOR(j,1,n+1)rec(j,a[i],d(j,a[i+1])+1);//更新一:当新元中的两个相同的时候
		 
		rec(a[i],a[i+1],mxas),rec(a[i],a[i+2],mxas),rec(a[i+1],a[i+2],mxas);//更新二:新元中的任意两个组合可以等于mxas
		 
		rec(a[i],a[i+1],d(a[i+2],a[i+2])+1),rec(a[i],a[i+2],d(a[i+1],a[i+1])+1),rec(a[i+1],a[i+2],d(a[i],a[i])+1);//更新三:当之两个旧元都与新元中的其中一个相同的时候,另两个新元组合的dp项可以被更新 
		
		FOR(j,1,n+1)rec(j,a[i],mx[j]),rec(j,a[i+1],mx[j]),rec(j,a[i+2],mx[j]);//更新四: 新元的任意一个都可以与旧元中的一个组合,更新自然是取mx[旧元] 
		
		FOR(j,0,g.size())up(g[j].u,g[j].v,g[j].w);//现在统一处理要更新的所有dp项(这样可以避免dp项在更新的时候被覆盖而使得其它dp项更新错误的值) 
		i+=2;
	}
	
	int as=0;
	as=max(mxas,d(a[tot],a[tot])+1);
	ans+=as;
	wrn(ans);
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值