DP 题合集

背包 DP

[NOIP 2018 提高组] 货币系统

注意到,\(b\) 必然是 \(a\) 的子集。\(dp_j\) 表示凑出 \(j\) 的方案数。

code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=110,M=25010;
int t,n,a[N],dp[M],ans;
int main(){
	scanf("%d",&t);
	while(t--){
		ans=0;
		memset(dp,~0x3f,sizeof(dp));
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		dp[0]=0;
		for(int i=1;i<=n;i++)
			for(int j=a[i];j<=25000;j++)
				dp[j]=max(dp[j],dp[j-a[i]]+1);
		for(int i=1;i<=n;i++)
			if(dp[a[i]]==1)//唯一一种表示方法
				ans++;
		printf("%d\n",ans);
	}
	return 0;
}

Arpa's weak amphitheater and Mehrdad's valuable Hoses

并查集+分组背包。
对于每个连通块分为一组,每组中有组内的所有人单独一个和组内所有人之和。

code
#include<iostream>
#include<vector>
using namespace std;
const int N=1010,M=1e5+10;
int n,m,W,fa[N],w[M],b[M],sumw[N],sumb[N],dp[N],cnt;
int find(int x){
	if(fa[x]==x)
		return x;
	return fa[x]=find(fa[x]);
}
bool vis[N];
vector<int> p[N];
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m>>W;
	cnt=n;
	for(int i=1;i<=n;i++)
		fa[i]=i;
	for(int i=1;i<=n;i++)
		cin>>w[i];
	for(int i=1;i<=n;i++)
		cin>>b[i];
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		int a=find(x),b=find(y);
		fa[a]=b;
	}
	for(int i=1;i<=n;i++){
		int x=find(i);
		p[x].push_back(i);
		sumw[x]+=w[i];
		sumb[x]+=b[i];
	}
	for(int i=1;i<=n;i++){
		if(p[i].size()>1){
			p[i].push_back(++cnt);
			w[cnt]=sumw[i];
			b[cnt]=sumb[i];
		}
	}
	for(int i=1;i<=n;i++){
		if(p[i].empty())
			continue;
		for(int j=W;j>=0;j--)
			for(int k:p[i])
				if(j>=w[k])
					dp[j]=max(dp[j],dp[j-w[k]]+b[k]);
	}
	cout<<dp[W];
	return 0;
}

Tak and Cards

\(dp_{k,j}\) 表示选择了 \(k\) 个物品,总和为 \(j\) 时的情况数,统计合法答案即可。

code
#include<cstdio>
template<typename T>
void read(T &x){
	bool f=0;
	x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')
			f=1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	if(f)
		x=~x+1;
	return;
}
template<typename T1,typename...T2>
void read(T1 &x,T2 &...args){
	read(x);
	read(args...);
	return;
}
typedef long long ll;
const int N=55;
int n,a,x[N],sum;
ll dp[N][N*N],ans;
int main(){
	read(n,a);
	for(int i=1;i<=n;i++)
		read(x[i]),sum+=x[i];
	dp[0][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=sum;j>=x[i];j--)
			for(int k=n;k>=1;k--)
				dp[k][j]+=dp[k-1][j-x[i]];
	for(int i=1;i<=n;i++)
		ans+=dp[i][a*i];
	printf("%lld\n",ans);
	return 0;
}

Max Sum Counting

\(a\) 升序排序,\(dp_j\) 表示 \(b\) 的和为 \(j\) 的情况数。

code
#include<cstdio>
#include<algorithm>
#define mod 998244353
template<typename T>
void read(T &x){
	bool f=0;
	x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')
			f=1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	if(f)
		x=~x+1;
	return;
}
template<typename T1,typename...T2>
void read(T1 &x,T2 &...args){
	read(x);
	read(args...);
	return;
}
const int N=5010;
int n,dp[N],ans;
struct node{
	int a,b;
	bool operator<(const node &x)const{
		return a<x.a;
	}
}p[N];
int main(){
	read(n);
	for(int i=1;i<=n;i++)
		read(p[i].a);
	for(int i=1;i<=n;i++)
		read(p[i].b);
	std::sort(p+1,p+1+n);
	dp[0]=1;
	for(int i=1;i<=n;i++)
		for(int j=5000;j>=p[i].b;j--){
			dp[j]=(dp[j]+dp[j-p[i].b])%mod;
			if(p[i].a>=j)
				ans=(ans+dp[j-p[i].b])%mod;
		}
	printf("%d\n",ans);
	return 0;
}

消失之物

\(dp_{j,0}\) 表示容积为 \(j\) 时的方案数。\(dp_{j,1}\) 表示移除某物品后容积为 \(j\) 时的方案数。

code
#include<cstdio>
const int N=2010;
int n,m,w[N],dp[N][2];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",w+i);
	}
	dp[0][0]=dp[0][1]=1;
	for(int i=1;i<=n;i++)
		for(int j=m;j>=w[i];j--)
			dp[j][0]=(dp[j][0]+dp[j-w[i]][0])%10;
	for(int i=1;i<=n;i++,printf("\n"))
		for(int j=1;j<=m;j++){
			if(j-w[i]>=0)
				dp[j][1]=(dp[j][0]-dp[j-w[i]][1]+10)%10;//移除物品i
			else
				dp[j][1]=dp[j][0];
			printf("%d",dp[j][1]);
		}
	return 0;
}

区间 DP

Pre-Order

实在无法理解题解的转移,感觉很不自然。
\(dp_{l,r}\) 表示区间 \([l,r]\) 的方案数。枚举断点 \(k\)。若 \(p_l\le p_k\) 并且 \(p_{k+1}\le p_r\),说明合法,则 \(dp_{l,r}\) 就加上 \(dp_{l,k}\times dp_{k+1,r}\)。当前点的 dfn 大于之前的点就说明可以作为之前点的子树中的点。

code
#include<cstdio>
template<typename T>
void read(T &x){
	x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')
		ch=getchar();
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return;
}
#define mod 998244353
const int N=510;
int n,p[N],dp[N][N];
int main(){
	read(n);
	for(int i=1;i<=n;i++)
		read(p[i]);
	for(int i=1;i<=n;i++)
		dp[i][i]=1;
	for(int len=2;len<=n;len++)
		for(int l=1,r=l+len-1;r<=n;l++,r++)
			for(int k=l;k<r;k++)
				if(p[l]<=p[k]&&p[k+1]<=p[r])
					dp[l][r]=(dp[l][r]+1ll*dp[l][k]*dp[k+1][r])%mod;
	printf("%d\n",dp[1][n]);
	return 0;
}

数据结构优化 DP

Linear Kingdom Races

\(dp_i\) 表示前 \(i\) 条道路的最大利润。
使用线段树维护区间 \([1,j]\) 的最大利润。

  • 不修复道路 \(i\)\(dp_i=dp_{i-1}\)
  • 修复道路 \(i\)\(dp_i=\max_{0 \le j<i}(dp_j+val(j+1,i)-cost(j+1,i))\)
code
#include<cstdio>
#include<vector>
using namespace std;
template<typename T>
void read(T &x){
	x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')
		ch=getchar();
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return;
}
template<typename T1,typename...T2>
void read(T1 &x,T2 &...args){
	read(x);
	read(args...);
	return;
}
template<typename T>
T Max(const T &a,const T &b){
	return a<b?b:a;
}
typedef long long ll;
#define ls u<<1
#define rs u<<1|1
const int N=2e5+10;
int n,m,w[N];
ll tree[N*4],tag[N*4];
void push_up(int u){
	tree[u]=Max(tree[ls],tree[rs]);
	return;
}
void push_down(int u){
	tree[ls]+=tag[u],tree[rs]+=tag[u];
	tag[ls]+=tag[u],tag[rs]+=tag[u],tag[u]=0;
	return;
}
void modify(int u,int l,int r,int x,int y,ll k){
	if(x<=l&&y>=r){
		tree[u]+=k;
		tag[u]+=k;
		return;
	}
	push_down(u);
	int mid=(l+r)/2;
	if(x<=mid)
		modify(ls,l,mid,x,y,k);
	if(y>mid)
		modify(rs,mid+1,r,x,y,k);
	push_up(u);
	return;
}
ll query(int u,int l,int r,int x,int y){
	if(x<=l&&y>=r)
		return tree[u];
	ll res=0;
	int mid=(l+r)/2;
	push_down(u);
	if(x<=mid)
		res=Max(res,query(ls,l,mid,x,y));
	if(y>mid)
		res=Max(res,query(rs,mid+1,r,x,y));
	return res;
}
vector<pair<int,int>> a[N];
ll dp[N];
int main(){
	read(n,m);
	for(int i=1;i<=n;i++)
		read(w[i]);
	for(int i=1,l,r,p;i<=m;i++){
		read(l,r,p);
		a[r].push_back(make_pair(l,p));
	}
	for(int i=1;i<=n;i++){
		modify(1,0,n,0,i-1,-w[i]);
		for(auto x:a[i])
			modify(1,0,n,0,x.first-1,x.second);
		dp[i]=Max(dp[i-1],query(1,0,n,0,i-1));
		modify(1,0,n,i,i,dp[i]);
	}
	printf("%lld\n",dp[n]);
	return 0;
}

Digital Wallet

code
#include<cstdio>
#include<vector>
using namespace std;
template<typename T>
T Max(const T &a,const T &b){
    return a<b?b:a;
}
typedef long long ll;
const int N=1e5+10;
int n,m,k,a[15][N];
ll dp[N];
int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
            for(int t=i;t>=Max(i-k+1,1);t--)
                dp[t]=Max(dp[t],dp[t-1]+a[j][i]);
    printf("%lld\n",dp[m-k+1]);
    return 0;
}

状压 DP

[CCO 2015] 路短最

状压 DP 的特点一般就是某个数据范围小于 \(20\)。比如本题的 \(n\),可以直接将点状压表示访问过的城市。转移比较平凡。

code
#include<cstdio>
#include<cstring>
template<typename T>
void read(T &x){
	x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')
		ch=getchar();
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){
	read(x);
	read(args...);
	return;
}
template<typename T>
T Max(const T &a,const T &b){
	return a<b?b:a;
}
const int N=20;
int n,m,dp[N][1<<18|1],ans,e[N][N];
int main(){
	memset(dp,0xcf,sizeof(dp));
    read(n,m);
	for(int i=1,s,d,l;i<=m;i++){
		read(s,d,l);
		e[s][d]=l;
	}
	dp[0][1]=0;
	for(int i=1;i<(1<<n);i+=2)//小优化,0作为起点必然被访问过
		for(int j=0;j<n;j++)
			if(i>>j&1)
				for(int k=1;k<n;k++){
					if((i>>k&1)&&e[j][k])
						dp[k][i]=Max(dp[k][i],dp[j][1<<k^i]+e[j][k]);
					if(k==n-1)
						ans=Max(ans,dp[k][i]);
				}
	printf("%d\n",ans);
    return 0;
}

[GDOI2014] 拯救莫莉斯

注意到 \(m\le 7\)。维护 \(dp_{i,j,k}\) 表示第 \(i\) 行,第 \(i-1\) 行状态为 \(j\),第 \(i\) 行状态为 \(k\)。可以很容易地同时维护最小代价和最小油库个数。预处理每种状态的代价和油库个数。
转移时,枚举第 \(i-2\),第 \(i-1\) 和第 \(i\) 行状态,但仅需判断第 \(i-1\) 行合法即可转移。在最终统计答案时再判第 \(n\) 行合法。

code
#include<cstdio>
#include<cstring>
template<typename T>
void read(T &x){
    x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')
		ch=getchar();
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
    return;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){
    read(x);
    read(args...);
    return;
}
int getbit(int x){
	int res=0;
	while(x){
		res++;
		x^=x&-x;
	}
	return res;
}
int n,m,f[55][55],dp1[55][1<<8][1<<8],dp2[55][1<<8][1<<8];
int sum[55][1<<8],ans1,ans2,bit[1<<8];
int main(){
	memset(dp1,0x3f,sizeof(dp1));
	memset(dp2,0x3f,sizeof(dp2));
	ans1=ans2=0x3f3f3f3f;
    read(n,m);
    for(int i=1;i<=n;i++)
		for(int j=0;j<m;j++)
			read(f[i][j]);
	for(int i=0;i<(1<<m);i++)
		bit[i]=getbit(i);
	for(int i=1;i<=n;i++)
		for(int j=0;j<(1<<m);j++)
			for(int k=0;k<m;k++)
				if(j>>k&1)
					sum[i][j]+=f[i][k];
	for(int i=0;i<(1<<m);i++)
		dp1[1][0][i]=sum[1][i],dp2[1][0][i]=bit[i];
	for(int i=2;i<=n;i++)
		for(int j=0;j<(1<<m);j++)
			for(int k=0;k<(1<<m);k++)
				for(int l=0;l<(1<<m);l++){
					if(((j|k|(k<<1)|(k>>1)|l)&((1<<m)-1))!=(1<<m)-1)
						continue;
					int val=dp1[i-1][j][k]+sum[i][l];
					int bitsum=dp2[i-1][j][k]+bit[l];
					if(dp1[i][k][l]>val){
						dp1[i][k][l]=val;
						dp2[i][k][l]=bitsum;
					}
					else if(dp1[i][k][l]==val&&dp2[i][k][l]>bitsum)
						dp2[i][k][l]=bitsum;
				}
	for(int i=0;i<(1<<m);i++)
		for(int j=0;j<(1<<m);j++){
			if(((i|j|(j<<1)|(j>>1))&((1<<m)-1))!=(1<<m)-1)
				continue;
			if(dp1[n][i][j]<ans2){
				ans2=dp1[n][i][j];
				ans1=dp2[n][i][j];
			}
		}
	printf("%d %d\n",ans1,ans2);
    return 0;
}
posted @ 2025-04-28 17:32  headless_piston  阅读(24)  评论(0)    收藏  举报