步骤:
1.动态规划的哪种类型(1.背包(选和不选) ,2.区间dp(小区间推大区间) 3.线性dp , 4.树形dp)
+普通dp or 判定dp 哪个好实现(复习时若该题用判定,思考为啥不用普通)
2.状态定义(用几维数组能准确表达其状态)(也可联想最终状态来辅助)
(2,3步骤)可以颠倒,有时推出从谁转移可以得到状态表示
3.当前状态从谁转移而来 / 当前状态可以推导那些状态 ->转移方程
4.处理边界+初始化
记搜都可以转化为dp,所以写dp时可以先用记搜想一想,而dp不一定都能转化为记搜
两种写法记搜(递归)dp(递推)
dp初始化为负极大值大多是因为避免无效转移,参考失衡天平,,dp也有初始化为极大值的
关于状态压缩:如果当前状态从前i-1转移而来,可以直接压掉表示i的那一维
如果当前状态只从第i-1位转移而来,第一维要保留,但可以滚动数组优化
01背包模板,这个也看一下吧,选前i个的写法,不能忘其根本***(但是做题不建议加上那一维,除非题目一定要严格维护第i-1种状态,而不是前i-1种状态)若硬要加,先看(洛谷)NASA的食物计划
补充:
01背包原本的状态转移方程式:
dp[i][j]=dp[i-1][j],(所以dp[i][j]本质上是从前i-1转移而来)
,dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]]+c[i])
易发现当前状态只与上一个(本质上是前i-1个)有关,所以可以用滚动数组优化,用一维数组存储上一个(i-1)的值
然后用i来更新上一个的值,但我们更新时当前物品a[i]只能选一次,正序可能会导致物品选多次,逆序则保证前面的dp[j]存储的是上个状态最大值,最多用一次i
但是01背包精髓还是在于二维的01背包的状态转移方程式(对于前i个的理解)
(洛谷)采药(01背包模板)
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
int t,m,w[101],c[101],f[1001];
int main()
{
scanf("%d%d",&t,&m);
for(int i=1;i<=m;i++)scanf("%d%d",&w[i],&c[i]);
for(int i=1;i<=m;i++)
{
for(int j=t;j>=w[i];j--)
{
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
}
printf("%d",f[t]);
return 0;
}
1.kkksc03考前临时抱佛脚(01背包)
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <stdlib.h>
using namespace std;
int s[5],sub[21],f[21][1200],t1,v;
long long tot;
int main()
{
scanf("%d%d%d%d",&s[1],&s[2],&s[3],&s[4]);
for(int i=1;i<=4;i++)
{
v=0;t1=0;
memset(f,0,sizeof(f));//
memset(sub,0,sizeof(sub));
for(int j=1;j<=s[i];j++)
{
scanf("%d",&sub[j]);
v+=sub[j];//总共需要花多少时间
}
for(int j=1;j<=s[i];j++)//物品个数
{
for(int k=1;k<=v/2;k++)//背包容量,,使背包容量尽量==v/2,使得左右脑所耗时间基本相等
{
if(k<sub[j])//模板
f[j][k]=f[j-1][k];
else
{
f[j][k]=max(f[j-1][k-sub[j]]+sub[j],f[j-1][k]);//价值与重量相等,背包容量为时间
t1=max(t1,f[j][k]);//算出背包容量为v/2的时候最多的价值,,即最多的时间
}
}
}
tot+=max(t1,v-t1);
}
cout<<tot;
return 0;
}
2.(洛谷)5倍经验日
#include <iostream>
using namespace std;
long long lose[2000],win[2000],use[2000];
long long f[2000];
int main()
{
int n,x;
scanf("%d%d",&n,&x);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&lose[i],&win[i],&use[i]);
}
for(int i=1;i<=n;i++)
{
for(int j=x;j>=0;j--)
{
long long tem=f[j];//如果所剩药的数量够,比较(打败,输掉,之前f[j]的值)的情况
if(j>=use[i]) //如果药不够,比较(打败,之前f[j]的值)的情况
f[j]=max(f[j],f[j-use[i]]+win[i]);
f[j]=max(f[j],tem+lose[i]);
}
}
printf("%lld",5*f[x]);
return 0;
}
(牛客)牛牛的旅游纪念品(基于01背包的背包dp)(理解选前i个和选第i个区别)***(求最值)
(牛客)音量调节(01背包变形+判定dp)(严格从第i-1种状态转移,不能压维度,可用滚动数组) (求最值)
(牛客)简单的烦恼(01背包+贪心)(求最值)
(牛客)美味菜肴(01背包+贪心:推公式)(求最值)
(牛客)codeforces(跟上一题一样)
(洛谷)最大约数和(很隐蔽的01背包)(求最值)
(洛谷)有线电视网(树形dp+分组背包+01背包)
(洛谷)集合 Subset Sums(背包求方案数)(方案数**)
(洛谷)找啊找啊找GF(三重费用dp,一遍ac)(求最值)
(洛谷)搭配购买(01背包+并查集)(求最值)
(洛谷)yyy2015c01 的 U 盘(01背包+二分答案)(求最值)
完全背包(跟01差不多,只是第i件物品能选无限次)
(洛谷)疯狂的采药(完全背包模板)
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
ll t,m,w[20000],c[20000];
ll f[20000000];
int main()
{
scanf("%lld%lld",&t,&m);
for(int i=1;i<=m;i++)
{
scanf("%lld%lld",&w[i],&c[i]);
}
for(int i=1;i<=m;i++)
{
for(int j=w[i];j<=t;j++)
{
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
}
printf("%lld",f[t]);
return 0;
}
(洛谷)四方定理(背包dp(完全背包)) (求方案数**)
(洛谷)A+B Problem(再升级)(隐蔽的完全背包思想)(求方案数*)
(洛谷)质数和分解
(牛客)货币系统(完全背包变形+思维+判定dp)
(洛谷)投资的最大效益
(洛谷)Buying Hay S(题目理解,细节处理***)
(洛谷)纪念品(经典题目+状态压缩+倒序的完全背包)
(洛谷)飞扬的小鸟(细节题+难想的完全背包+二维完全背包写法)(需要再看看)
(洛谷)Cut Ribbon(完全背包+要求背包刚好装满)
线性dp
(牛客)舔狗舔到最后一无所有
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
const int N=100010,mod=1e9+7;
int f[3][N];
// f[0/1/2][i]前i天且第i天去第0/1/2家购买方案
// f[0][i]=(f[1][i-1]+f[2][i-1]+f[1][i-2]+f[2][i-2])
// f[1][i]=(f[0][i-1]+f[2][i-1]+f[0][i-2]+f[2][i-2])
// f[2][i]=(f[1][i-1]+f[0][i-1]+f[1][i-2]+f[0][i-2])
signed main()
{
int T;
cin>>T;
f[0][0]=f[1][0]=f[2][0]=0;
f[0][1]=f[1][1]=f[2][1]=1;
f[0][2]=f[1][2]=f[2][2]=3;
for(int i=3;i<N;i++)
{
f[0][i]=(f[1][i-1]+f[2][i-1]+f[1][i-2]+f[2][i-2])%mod;
f[1][i]=(f[0][i-1]+f[2][i-1]+f[0][i-2]+f[2][i-2])%mod;
f[2][i]=(f[1][i-1]+f[0][i-1]+f[1][i-2]+f[0][i-2])%mod;
}
while(T--)
{
int n;
cin>>n;
cout<<(f[0][n]%mod+f[1][n]%mod+f[2][n]%mod)%mod<<endl;
}
}
我们发现第i天去哪一家都是一样的,3家关系是相同的,因此可以缩小至一维,只要在初始值乘3,得到的结果也是原来的三倍
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
const int N=100010,mod=1e9+7;
int f[N];
signed main()
{
int T;
cin>>T;
f[0]=0;
f[1]=3;
f[2]=9;
for(int i=3;i<N;i++)
f[i]=(f[i-1]*2%mod+f[i-2]*2%mod)%mod;
while(T--)
{
int n;
cin>>n;
cout<<f[n]<<endl;
}
}
(牛客)乌龟棋
(牛客)失衡天平(经典)
(牛客)购物
(牛客)队伍配置(经典)
(洛谷)守望者的逃离(非传统dp)
(洛谷)摆花(方案数)
(洛谷)线段(特殊的定义状态方法)
(牛客)钉子和小球 (由当前状态推导其他状态)
(牛客)可爱の星空(很像区间dp的dfs)
(洛谷)尼克的任务(线性dp,由当前状态推导其他状态,初始化为负极大值)
(牛客)打鼹鼠(特殊的定义状态方法)
(牛客)被3整除的子序列(线性dp)
(牛客)删括号(字符串dp不算很懂)
(洛谷)编辑距离(字符串dp,体现动态规划无后效性,很妙)
(洛谷)子串 (字符串dp+滚动数组)(似懂非懂)
(洛谷)字串距离(字符串dp)(有点启发)
(洛谷)传球游戏(提示:循环顺序即递推顺序,要严谨!!!)
(洛谷)雷涛的小猫(严谨循环顺序,一遍ac)
(洛谷)最大正方形(题解的:很妙,但特殊,自己写的:线性dp+稍微有点复杂的前缀和)
(洛谷)最大正方形II (跟上一题的差不多,特殊)
(牛客)禅(简单线性dp)
(洛谷)逆序对数列(前缀和+dp) 错因:对逆序对不熟)
(洛谷)英雄联盟(循环顺序,循环方向因遵循推导方向,特别是压缩前i-1状态时,不能忘了推导方向)
(牛客)斗地主(取模,需要注意的地方)
(牛客)美丽序列(精准状态定义)
(牛客)小红的子序列(通过上一题有感而发,写出来)
(洛谷)覆盖墙壁(蓝桥杯原题)
#include <iostream>
using namespace std;
#define ll long long
ll n,f[2000000],sum[2000000];//f[i]为填满前2*i个方格的总方案数,sum为前缀和
int main()
{
cin>>n;
f[0]=f[1]=1;f[2]=2,f[3]=5;
sum[0]=1,sum[1]=2,sum[2]=4,sum[3]=9;//初始化
for(int i=4;i<=n;i++)
{
f[i]=sum[i-1]+sum[i-3];
f[i]%=100000;
sum[i]=(sum[i-1]+f[i])%100000;
}
cout<<f[n]%10000;
return 0;
}
/*考虑放在最后那一部分
1.放2*1砖块(竖放)方案数:f[i-1]
2.放2个2*1砖块(横放)方案数:f[i-2]//这里不考虑竖放是因为会重复:f[i-1]最后一个本来就是竖放然后f[i]也竖放
3.放一个L型砖块(因为可翻转,所以要乘2)
{
1.再放一个L型砖块(刚好互补)方案数:f[i]=f[i-3]
2.放一个2*1型砖块(竖放)再放一个L型砖块 方案数:f[i]=f[i-4]
3.2×1 的砖块可以交替着放下去,再补上一个 L 型砖块,从而消去这个突出,直到 2*1 砖块和 L 型砖块恰好填满墙壁(f[0])
}
所以总方案数:f[i-1]+f[i-2]+2*sum[i-3]=sum[i-1]+sum[i-3]
滚动数组模板(牛客)音量调节
#include <iostream>
#include<cstring>
#include <algorithm>
using namespace std;
#define inf 0x3f3f3f3f
int n,be,maxx;
bool dp[2][2000];
int main()
{
// memset(dp,-inf,sizeof dp);
// cout<<dp[1];
cin>>n>>be>>maxx;
dp[0][be]=1;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
bool flag=false;
for(int j=0;j<=maxx;j++)
{
if(dp[i&1^1][j])//i&1只有奇和偶两种情况
{
if(j>=x)dp[i&1][j-x]=1,flag=true;
if(j+x<=maxx)dp[i&1][j+x]=1,flag=true;
dp[i&1^1][j]=false;//要把之前的i-1初始化回去,(如果下一次能把上上一次的覆盖掉则没关系)
}
}
if(!flag){cout<<-1;return 0;}
}
int ans=-1;
for(int j=0;j<=maxx;j++)
{
if(dp[n&1][j])ans=j;
}
cout<<ans;
return 0;
}
(双线程)方格取数(牛客)
#include <iostream>
#include <algorithm>
using namespace std;
int ma_p[11][11],dp[11][11][11][11];
long long ans=0;
int main()
{
int n;cin>>n;
while(1)//输入
{getchar();
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(x==y&&y==z&&z==0)break;
else ma_p[x][y]=z;
}
for(int a=1;a<=n;a++)//到点(a,b),(c,d)的路径的和
for(int b=1;b<=n;b++)
for(int c=1;c<=n;c++)
for(int d=1;d<=n;d++)
{
if(a==c&&b==d)//若两点重合
{
dp[a][b][c][d]=dp[a-1][b][c][d-1]+ma_p[a][b];
}
else
{
int tem=0;
if(dp[a-1][b][c-1][d]>tem)tem=dp[a-1][b][c-1][d];//四种情况,因为到某个点的两条路径步数一定相等
if(dp[a-1][b][c][d-1]>tem)tem=dp[a-1][b][c][d-1];//所以两个点必须同时动
if(dp[a][b-1][c-1][d]>tem)tem=dp[a][b-1][c-1][d];
if(dp[a][b-1][c][d-1]>tem)tem=dp[a][b-1][c][d-1];
dp[a][b][c][d]=tem+ma_p[a][b]+ma_p[c][d];
}
}
printf("%d",dp[n][n][n][n]);
return 0;
}
(洛谷)传纸条
拓扑+dp
(洛谷)旅行计划
(洛谷)绿豆蛙的归宿(期望dp+拓扑)
最长公共子序列
(洛谷)最长公共子序列(直接看代码)
朴素算法;
当s1==s2,dp[i][j]=dp[i-1][j-1]+1
当s1[i]!=s2[j],dp[i][j]=max(dp[i-1][j],dp[i][j-1])
朴素算法o(n^2)超时。。。
#include<iostream>
using namespace std;
int dp[20000][20000],arr1[200000],arr2[200000];
int main()
{
int n,maxn=0;scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&arr1[i]);
for(int i=1;i<=n;i++)scanf("%d",&arr2[i]);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(arr1[i]==arr2[j])
dp[i][j]=dp[i-1][j-1]+1;
else
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
if(dp[i][j]>maxn)maxn=dp[i][j];
}
}
printf("%d",maxn);
return 0;
}
离散化,转化为最长上升子序列o(nlogn),只能用于A串各元素互不相同的情况
给它们重新标个号:把3标成a,把2标成b,把1标成c,,于是变成:
A: a b c d e
B: c b a d e
出现一个性质,只要这个子序列在B中单调,则它就是A的子序列,所以问题转化成求最长上升子序列
#include <iostream>
#include <unordered_map>
#include <algorithm>
using namespace std;
unordered_map<int,int>mark;
int n,arr2[200000],d[200000],len=1;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
mark.insert(pair<int,int>(x,i));//用一个数组记录一下每个数字变成了什么,相当于离散化了一下,3-1;2-2;1-3;4-4;5-5
}
for(int i=1;i<=n;i++)scanf("%d",&arr2[i]);
for(int i=1;i<=n;i++)
{
arr2[i]=mark[arr2[i]];
}
d[len]=arr2[1];
for(int i=2;i<=n;i++)
{
if(arr2[i]>d[len])d[++len]=arr2[i];
else
{
int x=lower_bound(d+1,d+1+len,arr2[i])-d;
d[x]=arr2[i];
}
}
printf("%d",len);
return 0;
}
多重背包
朴素版本(将所有物品看成单独的物品,转化成01背包)O(n^3)
二进制优化版本(因为任何一个数都可以由二进制数转化而成,eg:选1-9个该物品可一转化成从1,2,4,6,8里的数字组合,从而转化成01背包)O(n^2logn)
void multi_knapsack2(int n,int W)//二进制拆分
{//W为背包容量,,w[i],c[i],p[i]为第i件物品的重量,价值,数量
for(int i=1;i<=n;i++)
{
if(p[i]*w[i]>=W)//转化成完全背包,因为装不完
for(int j=w[i];j<=W;j++)
dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
else
{
for(int k=1;p[i]>0;k<<=1)//k=2的0 1 2 3.。。次方,p[i]是物品一开始的数量也是余数
{
int x=min(k,p[i]);//选较小的数, eg 8=1+2+4+1
for(int j=W;j>=w[i]*x;j--)//转化成01背包
dp[j]=max(dp[j],dp[j-w[i]*x]+x*c[i]);
p[i]-=x;//余数
}
}
}
}
(洛谷)樱花
洛谷)砝码称重
(洛谷)Space Elevator 太空电梯(排序+多重背包)
分组背包(洛谷:通天之分组背包)
#include<iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int t,n,m,k,dp[1111],ans,w[1111],z[1111],g[1111][1111],b[1111];
int main()
{
cin>>m>>n;
for(int i=1;i<=n;i++)
{
int x;
cin>>w[i]>>z[i]>>x;
b[x]++;//记录该组的物品数
t=max(t,x);//记录一共有多少组
g[x][b[x]]=i;//记录第x组中第b[x]个物品的编号
}
for(int i=1;i<=t;i++)
{
for(int j=m;j>=0;j--)//枚举容量,巧妙避开了物品冲突
{
for(int k=1;k<=b[i];k++)//枚举第i组的物品
{
if(j>=w[g[i][k]])
dp[j]=max(dp[j],dp[j-w[g[i][k]]]+z[g[i][k]]);
}
}
}
cout<<dp[m];
return 0;
}
(洛谷)有线电视网(树形dp+分组背包+01背包)
最长(上升和非上升)公共子序列,,直接看代码
(洛谷)导弹拦截
求最长非上升公共子序列和最长上升公共子序列
记住允许=的(非上升,非下降)用upper,不允许(递增,递减)用lower,刚好反过来
下降的用greater<int>(),上升的则不用加
upper_bound和lower_bound中的参数(d+1,d+len+1,a[i],..) - d (千万别把d写成a了)
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
#define ll long long
int arr[100007],d1[100007],d2[100007],len1=1,len2=1;
ll ans;
inline int read()
{
int a=0,b=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
b*=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
a=(a<<3)+(a<<1)+(ch^48);
ch=getchar();
}
return a*b;
}
int main()
{
int n=0;
while(scanf("%d",&arr[++n])!=EOF);n--;//这里虽然是先++的,但最后还要--
d1[1]=arr[1],d2[1]=arr[1];//用d1来存最长不上升序列
for(int i=2;i<=n;i++)
{
if(d1[len1]>=arr[i])d1[++len1]=arr[i];//如果a[i] <= d[len],说明a[i]可以接在d后面(而整个d还是有序的),那就简单粗暴地把a[i]丟进d
else //如果a[i]>d1[len1],在d中找到第一个小于a[i]的数(原序),删了,用a[i]替代它 ,就类似前面不选该数
{
int x=upper_bound(d1+1,d1+len1+1,arr[i],greater<int>())-d1;//greater<int>()把序列变成递增
d1[x]=arr[i];//存放最长非降序公共子序列
//不会影响到最终答案
}
if(d2[len2]<arr[i])d2[++len2]=arr[i];
else
{
int x=lower_bound(d2+1,d2+len2+1,arr[i])-d2;
d2[x]=arr[i];//存放最长上升公共子序列
}
}
cout<<len1<<endl<<len2;
return 0;
}
(牛客)合唱队形(最长递增和最长递减)
(洛谷)木棍加工(最长递增子序列+贪心,且有点小特殊)
(洛谷)友好城市(很隐晦的最长上升子序列)
(天梯赛)L2-014 列车调度 (最长上升子序列)
#include <bits/stdc++.h>
using namespace std;
#define LL __int128
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
const ll lmax=2e18;
const ll mod =1e9+7;
const ll N=1e5 + 10;
const ll M=2e5 + 10;
typedef pair<int,int> PII;
int d[N],n,a[N],len=0;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)
{
if(!len||d[len]<a[i])
{
d[++len]=a[i];
}
else
{
int x = upper_bound(d+1,d+1+len,a[i])-d;
d[x]=a[i];
}
}
cout<<len;
return 0;
}
区间dp
规律:区间dp状态定义 一定是要开两个以上维度维护左区间和右区间,eg;dp[i][j]维护(i,j)区间
dp[i1][j1][i2][j2]同时维护(i1,j1),(i2,j2)区间
(洛谷)石子合并(弱化版)
区间dp,,一般版本O(n^3),一般不会被卡时
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int arr[302],f[302][302],sum[302];
int main()
{
int n;scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",arr+i);
sum[i]=sum[i-1]+arr[i];
}
memset(f,0x7f,sizeof(f));//把初始值设为无穷大***
for(int len=1;len<=n;len++)//枚举区间长度 ,,因为不知道区间长度多少合适(有点类似迭代加深搜索)
{
for(int i=1;i+len-1<=n;i++)//左端
{
int j=i+len-1;//右端
if(len==1)f[i][j]=0;
else
{
for(int k=i;k<j;k++)//枚举中间值,通过小区间推大区间(k不能==j,否则k+1>j)
{
int tem=f[i][k]+f[k+1][j]+sum[j]-sum[i-1];//合并i-k和k+1-j和[i-k]-[(k+1)-j]石子的费用
if(f[i][j]>tem)f[i][j]=tem;
}
}
}
}
printf("%d",f[1][n]);
return 0;
}
(牛客)取数游戏2(线性dp+区间dp思想:大区间推小区间)
(牛客)合并回文子串(线性dp+区间dp思想+字符串dp+判定dp)
(洛谷)乘积最大
(洛谷)删数(区间dp思想)
(洛谷)玩具取名(区间dp(稀有的判定性),且有点难想)
(洛谷)加分二叉树(区间dp+二叉树的中序遍历转前序遍历)
环形区间dp
(牛客)石子合并
(牛客)凸多边形的划分
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define ll long long
ll arr[300],sum[300];
ll dp1[300][300],dp2[300][300];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>arr[i],sum[i]=sum[i-1]+arr[i];
for(int i=n+1;i<=2*n-1;i++)sum[i] = sum[i - 1] + arr[i - n];
memset(dp1,0x3f,sizeof dp1);
for(int len=1;len<=n;len++)
{
for(int i=1;i+len-1<=2*n-1;i++)
{
int j=i+len-1;
if(len==1)dp2[i][j]=dp1[i][j]=0;
else
{
for(int k=i;k<j&&j<=2*n-1;k++)
{
dp1[i][j]=min(dp1[i][j],dp1[i][k]+dp1[k+1][j]+sum[j]-sum[i-1]);
dp2[i][j]=max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum[j]-sum[i-1]);
}
}
}
}
ll maxn=-1000000;
ll minn=0x3f3f3f3f;
for(int i=1;i<=n;i++)
{
maxn=max(maxn,dp2[i][i+n-1]);
minn=min(minn,dp1[i][i+n-1]);
}
cout<<minn<<endl<<maxn;
return 0;
}
树形dp
先dfs子节点的状态,在从下往上推根节点状态
1.(牛客)没有上司的舞会
#include <iostream>
#include <vector>
using namespace std;
int n,root=-1,myroot[7000],happy[7000],dp[7000][2];//dp[i][j],i表示节点序号,j为0或者1,表示选或不选该节点
vector<int>son[7000];
void tree_dfs(int u)
{
dp[u][0]=0;dp[u][1]=happy[u];
for(int i=0;i<son[u].size();i++)
{
int v=son[u][i];
tree_dfs(v);
dp[u][1]+=dp[v][0];//若选该结点,则不选其子节点
dp[u][0]+=max(dp[v][0],dp[v][1]);//若不选该结点
}
return;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",happy+i);
}
for(int i=1;i<=n;i++)
{
int l,k;cin>>l>>k;
if(l==0&&k==0)break;
myroot[l]=1;//有父节点
son[k].push_back(l);
}
for(int i=1;i<=n;i++)//找没有父节点的,即根节点
{
if(!myroot[i]){root=i;break;}
}
tree_dfs(root);
cout<<max(dp[root][1],dp[root][0]);
return 0;
}
(洛谷)最大子树和(很简单的树形dp)
(洛谷)联合权值(不一样的树形dp***)
(洛谷)有线电视网(树形dp+分组背包(01))经典模板
(牛客)Treepath(树形dp)
(牛客)月之暗面(树形dp+后置维护)(没想出来)
状压dp
(牛客)互不侵犯king
状压DP-互不侵犯_哔哩哔哩_bilibili(直接看代码也可以。。。)
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
ll dp[10][1<<9][1000];//第i行,第i行为st的摆放方式,放了k个国王的摆放方案数
int n,k;
int c(ll st)//返回放了st代表的国王数
{
int sum=0;
while(st)
{
if(st%2)sum++;
st/=2;
}
return sum;
}
bool check1(ll st)//判断当行是否合法
{
for(int i=0;i+1<n;i++)
{
if(st & (1<<i) && (st & (1<<(i+1))))return false;//&两边为真则为真
}
return true;
}
bool check2(ll st,ll st2)//判断当行于其上一行间关系是否合法
{
for(int i=0;i<n;i++)
{
if(st & (1<<i))
{
if(st2 & (1<<i))return false;
else if(i+1<n && (st2 & (1<<(i+1)))) return false;
else if(i-1<n && (st2 & (1<<(i-1)))) return false;
}
}
return true;
}
int main()
{
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)
{
for(int st=0;st<(1<<n);st++)//从0开始,为什么st不是<=?因为0-n-1就有9个数了
{
if(!check1(st))continue;//若该行摆法方式违法
if(i==1)dp[i][st][c(st)]=1;
else
{
for(int j=c(st);j<=k;j++)//枚举1~i行一共放了的国王数
{
for(int st2=0;st2<(1<<n);st2++)//枚举上一行放国王位置情况
{
if(check2(st,st2)&&check1(st2))
{
dp[i][st][j]+=dp[i-1][st2][j-c(st)];
}
}
}
}
}
}
ll ans=0;
for(int i=0;i<(1<<n);i++)ans+=dp[n][i][k];//
cout<<ans;
return 0;
}