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;
}

浙公网安备 33010602011771号