my_动态规划

 

步骤:

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值