• 动态规划学习


    动态规划学习

    前言

    以前我也算是接触过一点DP,陆陆续续学了一些背包问题,线性动规和区间动规。现在我再次发现了动规的重要性,决定在暑假里专门刷一些动规题。这篇blog主要记录我刷过的一些DP题。

    引用 _皎月半撒花 大佬的一段话

    动态规划自古以来是(dalao)凌虐萌新的分水岭,但有些(OIer)认为并没有这么重要——会打暴力,大不了记忆化。但是其实,动态规划学得好不好,可以彰显出一个(OIer)的基本素养——能否富有逻辑地思考一些问题,以及更重要的——能否将数学、算筹学(决策学)、数据结构合并成一个整体并且将其合理运用(qwq)

    推荐 (大佬的blog)

    正文 (这是我的qwq

    PS:考完后一定记得来写题解



    ([序号].) ( exttt{[ 标题 ]})

    题目链接

    ( exttt{题解})

    [ 内容 ]

    ( exttt{Code})

    
    
    


    (1.) ( exttt{P1040 加分二叉树})

    题目链接

    ( exttt{题解})

    暂无

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 37
    using namespace std;
    int n;
    int val[N],f[N][N],root[N][N];
    void print(int l,int r)
    {
    	if(l > r) return ;
    	if(l == r) {printf("%d ",l); return ;}
    	printf("%d ",root[l][r]);
    	print(l,root[l][r]-1);
    	print(root[l][r]+1,r);
    }
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)
    		scanf("%d",&val[i]) ,f[i][i] = val[i] ,f[i][i-1] = 1;
    	for(int i=n;i>=1;--i)
    		for(int j=i+1;j<=n;++j)
    			for(int k=i;k<=j;++k)
    				if(f[i][j] < f[i][k-1]*f[k+1][j]+f[k][k])
    				{
    					f[i][j] = f[i][k-1]*f[k+1][j]+f[k][k];
    					root[i][j] = k;
    				}
    	printf("%d
    ",f[1][n]);
    	print(1,n);
    	return 0;
    }
    


    (2.) ( exttt{P1133 教主的花园})

    题目链接

    ( exttt{题解})

    定义状态 :$f[i][j][k] $

    是第 (i) 棵树

    (j)表示(i)前面这棵树的前面这棵 是否比 前面这棵 高,高为1,矮为0

    k表示前面这棵树种的多高的树

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 100007
    #define M 5
    #define Max(a,b,c,d) max(max(max(a,b),c),d)
    using namespace std;
    int n,ans;
    int t[N][M];
    int f[N][M][M];
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=3;++j)
    			scanf("%d",&t[i][j]);
    	for(int j=1;j<=3;++j)
    	{
    	for(int i=1;i<=3;++i)
    		for(int k=0;k<=1;++k)
    			f[1][k][i] = 0;
    	f[1][1][j] = f[1][0][j] = t[1][j];	//种j 
    	for(int i=2;i<=n;++i)
    	{
    		f[i][0][2] = f[i-1][1][1] + t[i][2];	//前矮种2 ,前一位是1,再前高 
    		f[i][0][3] = max(f[i-1][1][1],f[i-1][1][2]) + t[i][3];	//前矮种3 ,前一位是1或2,再前高 
    		f[i][1][1] = max(f[i-1][0][2],f[i-1][0][3]) + t[i][1];	//前高种1 ,前一位是2或3,再前矮 
    		f[i][1][2] = f[i-1][0][3] + t[i][2];	//前高种2 ,前一位是3,再前矮 
    	}
    	for(int i=1;i<j;++i)
    		ans = max(ans,f[n][1][i]);
    	for(int i=3;i>j;--i)
    		ans = max(ans,f[n][0][i]);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    


    (3.) ( exttt{P1122 最大子树和})

    题目链接

    ( exttt{题解})

    简单树形(DP),只是把<最大子段和>放到树上来了而已。

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 16007
    using namespace std;
    int n,cnt,ans;
    int val[N],f[N];
    int head[N];
    struct Edge {
    	int next,to;
    }edge[N<<1];
    void add(int u,int v)
    {
    	edge[++cnt].next = head[u];
    	edge[cnt].to = v;
    	head[u] = cnt;
    }
    void Dfs(int fa,int u)
    {
    //	printf("Now u= %d
    ",u);
    //	f[u] = val[u];
    	for(int i=head[u];i;i=edge[i].next)
    	{
    		int v = edge[i].to;
    		if(v != fa) {
    			Dfs(u,v);
    			f[u] = max(f[u],f[u]+f[v]);
    		}
    	}
    //	printf("f = %d
    ",f[u]);
    	ans = max(ans,f[u]);
    	return ;
    }
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)
    		scanf("%d",&val[i]) ,f[i] = val[i];
    	for(int i=1,u,v;i<=n-1;++i)
    		scanf("%d%d",&u,&v) ,add(u,v) ,add(v,u);
    	Dfs(0,1);
    	printf("%d
    ",ans);
    	return 0;
    }
    


    (4.) ( exttt{P1336 最佳课题选择})

    题目链接

    ( exttt{题解})

    绿题难度疑似评高?

    定义状态 :(f[i][j]) 表示(i)及之前所以课题写了(j)篇的最大值

    状态转移方程 : (f(i,j) = min f(i,j) ,f(i-1,j-k)+val)

    枚举k ; val = ai*k^bi

    有点背包的味道。emm。

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define N 207
    #define int long long
    using namespace std;
    int n,m;
    int a[N],b[N];
    int f[N][N];
    signed main()
    {
    	scanf("%lld%lld",&n,&m);
    	for(int i=1;i<=m;++i)
    		scanf("%lld%lld",&a[i],&b[i]);
    //	printf("OK");
    	for(int i=1;i<=m;++i)
    		for(int j=1;j<=n;++j)
    			for(int k=0;k<=j;++k)
    			{
    				int val = a[i] * pow(k,b[i]);
    				if(f[i][j]==0 || i==1) f[i][j] = f[i][j] = f[i-1][j-k] + val;
    				else f[i][j] = min(f[i][j],f[i-1][j-k]+val);
    			}
    	printf("%lld
    ",f[m][n]);
    	return 0;
    }
    


    (5.) ( exttt{P1353 [USACO08JAN]跑步Running})

    题目链接

    ( exttt{题解})

    DP大水题

    定义状态 (f[i][j])(i)时刻(j)疲劳度最大跑步米数。

    但是直接做我WA了,因此使用刷表法

    刷表法做的题不多,但他也是DP中的一种重要方法。刷表法是通过已知得到未知,顾名思义,就是通过已知数据把空白表填满。

    • 就例如我现在有1万元,我还能工作10年,如果每年可以赚2万,我可以通过刷表预先得知10年后我有21万元。

    • 但一般的动规是,我去年有10万,今年能赚5万,所以我今年有15万。具体的不同还是要靠自己慢慢体会。

    边界 (f[1][1] = D[1])

    状态转移 (有max就对后面的数取max的意思,都看得懂吧

    已经是(0),只能休息 (f(i,0) = max f(i,0) , f(i-1,0))

    不是(0),选择休息 (f(i+j,0) = max f(i+j,0) ,f(i,j))

    选择跑步 (f(i+1,j+1) = max f(i+1,j+1) ,f(i,j)+D[i+1])

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 20007
    using namespace std;
    int n,m;
    int d[N];
    int f[N][N];
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i)
    		scanf("%d",&d[i]);
    	f[1][1] = d[1];
    	for(int i=1;i<=n;++i)
    		for(int j=0;j<=min(i,m);++j)	//防止数组越界 
    		{
    			if(j==0) f[i][0] = max(f[i][0],f[i-1][0]);
    			else f[i+j][0] = max(f[i+j][0],f[i][j]);	//休息 刷上去 
    			f[i+1][j+1] = max(f[i+1][j+1],f[i][j]+d[i+1]); //跑步 刷上去 
    		}
    	printf("%d
    ",f[n][0]);
    	return 0;
    }
    
    


    (6.) ( exttt{P1982 小朋友的数字})

    题目链接

    ( exttt{题解})

    这道题又综合了<最大子段和>的思想。

    题意有点难懂,首先翻译一下题意:

    先把(n)个小朋友抽象为(n)个点,每个点上有3个数值。

    手牌值:题目会输入进来,这里用(a_i)表示

    特征值:这个点前面(包括这个点)连续若干个(最少有一个)点的手牌值之和的最大值。实际上就是最大子段和。这里用(t_i)表示。

    分数值(用(f_i)表示):这个点 (i) 前面(不包含(i)) 任意一个点(设为点(j)) 使得(f_j)+(t_j) 的值最大,(f_j)+(t_j)的最大值就是 (i)点分数。

    总结一下

    (a_i) 从题目中输入

    (t_i) 最大子段和

    (f_i) 由我们来(DP)

    最后的答案是要(f_i)最大的那个数,我们可以通过(DP),边取(max)得出

    先来看一下(t_i)怎么求(一开始我DP方程就推对了,就是因为这个求错坑了两天

    for(int i=1;i<=n;++i)
    {
    	b[i] = max(a[i],a[i]+b[i-1]);
    	mx = max(mx,b[i]);
    	t[i] = mx;
    }
    

    定义状态 (f[i])(i) 的最大分数。

    那么 (f[i-1]) 一定是 (i-1) 前最大 (f_j+t_j)

    (f[i-1] + t[i-1]) 我们还没试过

    所以动态转移方程为 $f[i] = max ( f[i-1] , f[i-1]+t[i-1]) $

    预处理 (f[1] = t[1] , f[2] = f[1] + t[1])

    (f[2]) 一定要预处理!

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 1000007
    #define INF 0x7fffffff
    #define int long long
    using namespace std;
    int n,p,maxn;
    int a[N],b[N],t[N],f[N];
    signed main()
    {
    	scanf("%lld%lld",&n,&p);
    	for(int i=1;i<=n;++i)
    		scanf("%lld",&a[i]);
    	int mx = -INF;
    	for(int i=1;i<=n;++i)
    	{
    		b[i] = max(a[i],a[i]+b[i-1]);
    		mx = max(mx,b[i]);
    		t[i] = mx;
    	}
    	f[1] = t[1] ,f[2] = f[1] + t[1];				//预处理f[1],f[2] 
    	maxn = max(f[1],f[2]);							//预处理maxn 
    	bool flag = 0;
    	for(int i=3;i<=n;++i)
    	{
    		if(f[i-1]+t[i-1]<0 && f[i-1]>0) flag = 1;	//爆longlong了, 
    		if(flag) f[i] = f[i-1] % p + t[i-1] % p;	//特殊处理,中间取模 
    		else f[i] = max(f[i-1],f[i-1]+t[i-1]);		//没有爆就正常操作 
    		maxn = max(maxn,f[i]) % p;					//不影响取maxn 
    	}
    	printf("%lld
    ",maxn % p);
    	return 0;
    }
    
    


    (7.) ( exttt{P2733 家的范围 Home on the Range})

    题目链接

    ( exttt{题解})

    是一道好题,思路新奇。

    定义状态:(f[i][j]) 表示以(i,j)作为结尾的矩阵中最大的正方形边长

    如果(i,j)(1) ,那么动态转移方程为 (f[i][j] = min(f[i-1][j],f[i][j-1],f[i-1][j-1]) + 1)

    你可能会问:为什么是这样?不要判断(i,j)的上、左方是否为1吗?

    其实你只要画图想想,如果(i,j)上方不为(1),那么(f[i-1][j]=0) ,其他同理。只要(i,j)的左上哪里最小,他就只能继承哪里

    那么这个(f[i][j])有什么用呢?

    我们每知道一个矩阵里最大的正方形是多少,就把 $num[f[i][j]]++ (,记录下来。但我只记录最大的,**不过大的里面包含小的**。例如我记录了)f[i][j]$的最大正方形是(5,num[5]++),那么其实这里面肯定有大小为4,3,2的正方形。最后我们要用叠加的方式加入(num[4]),(num[3])(num[2])。所以(num[4]+=num[5],num[3]+=num[4]),同理(num[i]+=num[i+1])

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 257
    using namespace std;
    int n,m;
    int map[N][N],f[N][N];
    int num[N];
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=n;++j)
    			scanf("%1d",&map[i][j]);	//控制读入一个数字
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=n;++j)
    			if(map[i][j] == 1)
    			{
    				f[i][j] = min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1]) + 1;
    				num[f[i][j]]++;
    			}
    	for(int i=n;i>=2;--i)
    		num[i] += num[i+1];
    	for(int i=2;i<=n;++i)
    		if(num[i]) printf("%d %d
    ",i,num[i]);
    	return 0;
    }
    
    


    (8.) ( exttt{P2734 游戏 A Game})

    题目链接

    ( exttt{题解})

    区间DP题

    状态很好推,只有选右和选左两种情况

    定义状态 :(f[i][j])为选(i,j)段的最大得分

    玩家1的得分肯定是(f[i][j])

    因为是一轮一轮来,如果玩家1的选左边,玩家2的得分就是(f[i+1,j]),所以:

    如果玩家1 选了左边,总得分就是:(sum[i+1,j]-f[i+1,j]+a[i])

    如果玩家1 选了右边,总得分就是:(sum[i,j-1]-f[i,j-1]+a[j])

    动态转移方程: (f[i,j] = max(sum[i+1,j]-f[i+1,j]+a[i] , sum[i,j-1]-f[i,j-1]+a[j]))

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 207
    using namespace std;
    int n;
    int a[N],sum[N];
    int f[N][N];
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)
    		scanf("%d",&a[i]) ,sum[i] = sum[i-1] + a[i] ,f[i][i] = a[i];	//记得初始化 
    //	倒序枚举才能使区间从小到大 
    	for(int i=n;i>=1;--i)
    		for(int j=i+1;j<=n;++j)
    			f[i][j] = max(sum[j]-sum[i-1]-f[i+1][j],sum[j]-sum[i-1]-f[i][j-1]);
    //	与之前这个动态转移方程是等价的 
    	printf("%d %d
    ",f[1][n],sum[n]-f[1][n]);
    	return 0;
    }
    
    


    (9.) ( exttt{P2736 “破锣摇滚”乐队 Raucous Rockers})

    题目链接

    ( exttt{题解})

    二维费用背包DP

    ( exttt{Code})

    
    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 27
    #define max(a,b,c) max(max(a,b),c)
    using namespace std;
    int n,T,M;
    int a[N];
    int f[N][N];
    int main()
    {
    	scanf("%d%d%d",&n,&T,&M);
    	for(int i=1;i<=n;++i)
    		scanf("%d",&a[i]);
    	for(int k=1;k<=n;++k)	
    		for(int i=M;i>=1;--i)
    			for(int j=T;j>=a[k];--j)
    				f[i][j] = max(f[i][j],f[i-1][T]+1,f[i][j-a[k]]+1);
    	printf("%d
    ",f[M][T]);
    	return 0;
    }
    
    


    (10.) ( exttt{P2854 [USACO06DEC]牛的过山车Cow Roller Coaster})

    题目链接

    ( exttt{题解})

    又是一道背包

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define N 10007
    #define MAXN 1007
    using namespace std;
    int n,Len,B;
    int f[MAXN][MAXN];
    struct Node {
    	int l,L,r,F,C;
    	bool operator < (const Node &a) {
    		return l < a.l;
    	}
    }a[N];
    int main()
    {
    	scanf("%d%d%d",&Len,&n,&B);
    	for(int i=1;i<=n;++i)
    		scanf("%d%d%d%d",&a[i].l,&a[i].L,&a[i].F,&a[i].C) ,a[i].r = a[i].l + a[i].L;
    	sort(a+1,a+1+n);
    	memset(f,-1,sizeof(f));
    	f[0][0] = 0;
    	for(int i=1;i<=n;++i)
    		for(int j=0;j<=B-a[i].C;++j)
    			if(f[a[i].l][j] != -1)
    				f[a[i].r][j+a[i].C] = max(f[a[i].r][j+a[i].C],f[a[i].l][j]+a[i].F);
    	int ans = -1;
    	for(int i=0;i<=B;++i)
    		ans = max(ans,f[Len][i]);
    	printf("%d
    ",ans);
    	return 0;
    }
    
    
    


    (11.) ( exttt{P1005 矩阵取数游戏})

    题目链接

    ( exttt{题解})

    第一道蓝题DP,方程一遍推对,好开心!

    其实只要区间DP每行就好了,更矩阵没有多大关系。跟<P2734 游戏 A Game> 这个题目有点像,(我之前写了笔记)。

    代码没打高精,只能拿60pts。

    ( exttt{Code})

    
    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define N 107
    #define int long long
    using namespace std;
    int n,m,ans;
    int a[N][N],f[N][N];
    signed main()
    {
    	scanf("%lld%lld",&n,&m);
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			scanf("%lld",&a[i][j]);
    	for(int line=1;line<=n;++line)
    	{
    		memset(f,sizeof(f),0);
    		for(int i=m;i>=1;--i)
    			for(int j=i;j<=m;++j)
    			{
    				int k = m - (j-i);
    //				printf("i=%d,j=%d,k=%d
    ",i,j,k);
    				if(i==j) f[i][j] = a[line][i]*pow(2,k);
    				else f[i][j] = max(f[i+1][j]+a[line][i]*pow(2,k),f[i][j-1]+a[line][j]*pow(2,k));
    //				printf("### f[%d][%d] = %d
    ",i,j,f[i][j]);
    			}
    //		printf("f = %d
    ",f[1][m]);
    		ans += f[1][m];
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    
    


    (12.) ( exttt{P1352 没有上司的舞会})

    题目链接

    ( exttt{题解})

    简单树形DP,只有这个点来和不来两种状态,方程应该很好推了吧。QwQ

    ( exttt{Code})

    
    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 6007
    using namespace std;
    int n,cnt;
    int a[N],head[N];
    int f[N][2];
    struct Edge {
    	int next,to;
    }edge[N<<1];
    void add(int u,int v)
    {
    	edge[++cnt].next = head[u];
    	edge[cnt].to = v;
    	head[u] = cnt;
    }
    void Dfs(int u,int fa)
    {
    	f[u][1] = a[u];
    	for(int i=head[u];i;i=edge[i].next)
    	{
    		int v = edge[i].to;
    		if(v != fa) {
    			Dfs(v,u);
    			f[u][1] = max(f[u][1],f[u][1]+f[v][0]);
    			f[u][0] = max(f[u][0],(f[u][0]+=max(f[v][1],f[v][0])));
    		}
    	}
    }
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)
    		scanf("%d",&a[i]);
    	for(int i=1,u,v;i<=n-1;++i)
    		scanf("%d%d",&u,&v) ,add(u,v) ,add(v,u);
    	Dfs(1,0);
    	printf("%d
    ",max(f[1][1],f[1][0]));
    	return 0;
    }
    
    


    (13.) ( exttt{P2014 选课})

    题目链接

    ( exttt{题解})

    (0)也算成一个节点,就变成一颗树了,考虑树形DP

    定义状态 (f[i,j])(i)个点为根的子树 ,(j)课程最大学分

    动态转移方程 (f[i,j] = f[i,j] , f[i,j-k]+f[ch,k])

    (ch)儿子节点,意思是我选k门儿子的课程获得最大值

    初始化每个点是自己的分值

    注意枚举范围每个点必须先选自己,不然选不了儿子

    ( exttt{Code})

    
    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 307
    using namespace std;
    int n,m,cnt;
    int s[N],head[N];
    int f[N][N];
    struct Edge {
    	int next,to;
    }edge[N<<1];
    void add(int u,int v)
    {
    	edge[++cnt].next = head[u];
    	edge[cnt].to = v;
    	head[u] = cnt;
    }
    void Dfs(int u,int fa)
    {
    //	printf("u=%d  
    ",u);
    	for(int i=1;i<=m;++i)
    		f[u][i] = s[u];
    	for(int i=head[u];i;i=edge[i].next)
    	{
    		int v = edge[i].to;
    		if(v != fa)
    		{
    			Dfs(v,u);
    			if(u!=0)
    			for(int j=m;j>=1;--j)
    				for(int k=1;k<j;++k)
    					f[u][j] = max(f[u][j],f[u][j-k]+f[v][k]);
    //			printf("f[%d][m] = %d
    ",u,f[u][m]);
    		}
    	}
    //	printf("u=%d  
    ",u);
    }
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1,v;i<=n;++i)
    		scanf("%d%d",&v,&s[i]) ,add(i,v) ,add(v,i);
    	Dfs(0,0);
    	for(int i=head[0];i;i=edge[i].next)
    	{
    		int v = edge[i].to;
    		for(int j=m;j>=1;--j)
    			for(int k=1;k<=j;++k)
    				f[0][j] = max(f[0][j],f[0][j-k]+f[v][k]);
    	}
    	printf("%d
    ",f[0][m]);
    	return 0;
    }
    
    


    (14.) ( exttt{P1854 花店橱窗布置})

    题目链接

    ( exttt{题解})

    简单题,自己看吧。

    ( exttt{Code})

    
    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define N 107
    #define INF 0x3f3f3f
    using namespace std;
    int n,m,ans,I;
    int pre[N][N];
    int a[N][N],f[N][N];
    void print(int i,int j)
    {
    	if(i > 1) print(i-1,pre[i][j]);
    	printf("%d ",j);
    }
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			scanf("%d",&a[i][j]);
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    		{
    			f[i][j] = -INF;
    			for(int k=i-1;k<j;++k)
    				if(f[i][j] < f[i-1][k]+a[i][j])
    				{
    					f[i][j] = f[i-1][k]+a[i][j];
    					pre[i][j] = k;
    				}
    		}
    	for(int i=n;i<=m;++i)
    		if(ans < f[n][i])
    			ans = f[n][i] ,I = i;
    	printf("%d
    ",ans);
    	print(n,I);
    	return 0;
    }
    
    


    (15.) ( exttt{P2732 商店购物 Shopping Offers})

    题目链接

    ( exttt{题解})

    发现还是更一下题解好一点。

    看到 <=5 考虑直接五维DP。

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 107
    using namespace std;
    int s,b,cnt;
    int id[1007];
    int T[6],V[6],P[N],K[N][6];
    int f[6][6][6][6][6];
    int main()
    {
    	int n,c,k,p;
    	scanf("%d",&s);
    	for(int i=1;i<=s;++i)
    	{
    		scanf("%d",&n);
    		for(int j=1;j<=n;++j)
    		{
    			scanf("%d%d",&c,&k);
    			if(!id[c]) id[c] = ++cnt;
    			K[i][id[c]] = k;
    		}
    		scanf("%d",&p);	P[i] = p;
    	}
    	scanf("%d",&b);
    	for(int i=1;i<=b;++i)
    	{
    		scanf("%d%d%d",&c,&k,&p);
    		if(!id[c])	id[c] = ++cnt;
    		T[id[c]] = k;
    		V[id[c]] = p;
    	}
    	for(int i=0;i<=T[1];++i)
    		for(int j=0;j<=T[2];++j)
    			for(int k=0;k<=T[3];++k)
    				for(int l=0;l<=T[4];++l)
    					for(int m=0;m<=T[5];++m)
    					{
    						int &t = f[i][j][k][l][m] = i*V[1] + j*V[2] + k*V[3] + l*V[4] + m*V[5];
    						for(int d=1;d<=s;++d)
    							if(i-K[d][1]>=0 && j-K[d][2]>=0 && k-K[d][3]>=0 && l-K[d][4]>=0 && m-K[d][5]>=0)
    								t = min(t,f[i-K[d][1]][j-K[d][2]][k-K[d][3]][l-K[d][4]][m-K[d][5]]+P[d]);
    					}
    	printf("%d
    ",f[T[1]][T[2]][T[3]][T[4]][T[5]]);
    	return 0;
    }
    
    


    (16.) ( exttt{P2948 [USACO09OPEN]滑雪课Ski Lessons})

    题目链接

    ( exttt{题解})

    对于雪坡是没有必要DP的,我们需要设立一个数组(ti[])来储存i能力值滑雪一次的最小时间

    我们需要对每一节课DP,因为每一节课分为上和不上两种状态,两节课之间滑雪几次我们可以将两节课之间的时间除以ti[k] (k为能力值)

    那么定义状态: (f[i][j])(i)时刻 (j)能力值 的最大滑雪次数

    状态转移方程:

    • 学习这节课,学习完课后的能力值为k,上一节课的结束为l ,这一节课的开始为s,看一下能课程与课程中间能滑雪多少次,对哪个状态发生了改变(方程自己想)。

    • 不学习这节课,上一节课的能力为k,上一节课的结束为l,这一节课的结束为s,(同上)。

    自己想吧!

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    int T,n,m;	//有T时间 n个课程 m个坡 
    int ti[107];	//有i能力滑雪的最小时间 
    int f[10007][107];
    struct Node {
    	int St,End,capa;	//capacity
    	bool operator < (const Node &a) {
    		return End < a.End;
    	}
    }les[107];
    int main()
    {
    	memset(ti,127,sizeof(ti));
    	scanf("%d%d%d",&T,&n,&m);
    	for(int i=1,st,Len,ai;i<=n;++i) {
    		scanf("%d%d%d",&st,&Len,&ai);
    		les[i] = (Node){st,st+Len,ai};
    	}
    	for(int i=1,di,ci;i<=m;++i) {
    		scanf("%d%d",&ci,&di);
    		for(int j=ci;j<=100;++j)
    			ti[j] = min(ti[j],di);
    	}
    	sort(les+1,les+1+n);
    	les[0] = (Node){0,0,1};
    	/*
    	for(int i=0;i<=n;++i,putchar('
    '))
    		printf("test : s=%d E=%d capacity=%d",les[i].St,les[i].End,les[i].capa);
    	for(int i=1;i<=T;++i)
    		printf("test : ti[%d]=%d
    ",i,ti[i]);
    	*/
    	for(int i=0;i<=n;++i)
    		for(int j=0;j<i;++j) {
    			int x = les[j].End ,y = les[i].St ,y1 = les[i].End;
    			int k = les[j].capa ,k1 = les[i].capa;
    			f[y1][k] = max(f[y1][k],f[x][k]+((y1-x)/ti[k]));
    			if(x < y) f[y1][k1] = max(f[y1][k1],f[x][k]+((y-x)/ti[k]));
    		}
    	int ans = -1;
    	for(int i=0;i<=n;++i) {
    		int x = les[i].End ,k = les[i].capa;
    		f[T][k] = max(f[T][k],f[x][k]+((T-x)/ti[k]));
    		ans = max(ans,f[T][k]);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    
    


    (17.) ( exttt{P2530 [SHOI2001]化工厂装箱员})

    题目链接

    ( exttt{题解})

    这是一道好题,不过有点困不想讲思路了,给大家看一下别人的吧。

    (注:以下均为转载)

    这里提供一种新的思路:

    状态:f[i][j][k][m] : 前i个物品 当前手中有j个"A" k个"B" m个"C"时的最小卸货次数

    很明显对于第i个物品可以

    1:)只取出来 暂时不装进去 前提就是当前手中货物数量<10

    2:)取出来后 装进去 没有前提

    所以转移方程就出来了:

    f[i][j][k][m] = f[i-1][j-1][k][m] if(ob[i]=='A' && j)

    f[i][j][k][m] = f[i-1][j][k-1][m] if(ob[i]=='B' && k)

    f[i][j][k][m] = f[i-1][j][k][m-1] if(ob[i]=='C' && m)

    以上三个均是 只取出来

    f[i][0][k][m] = f[i][j][k][m] + 1 ;

    f[i][j][0][m] = f[i][j][k][m] + 1 ;

    f[i][j][k][0] = f[i][j][k][m] + 1 ;

    这三个就是卸货啦qwq 还有一个大前提:j+k+m<=10

    初值:f[0][0][0][0]=0 ; 其他均为+oo

    目标:f[n][0][0][0]

    ( exttt{Code})

    
    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define N 107
    using namespace std;
    int n;
    int f[N][11][11][11];
    char s[N];
    int main()
    {
    	memset(f,127,sizeof(f));
    	f[0][0][0][0] = 0;
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)
    		cin >> s[i];
    	for(int i=1;i<=n;++i)
    		for(int a=0;a<=10;++a)
    			for(int b=0;b<=10;++b)
    				for(int c=0;c<=10;++c) {
    					if(a+b+c > 10) continue;
    					if(s[i] == 'A' && a) f[i][a][b][c] = f[i-1][a-1][b][c];
    					if(s[i] == 'B' && b) f[i][a][b][c] = f[i-1][a][b-1][c];
    					if(s[i] == 'C' && c) f[i][a][b][c] = f[i-1][a][b][c-1];
    					f[i][0][b][c] = min(f[i][0][b][c],f[i][a][b][c]+1);
    					f[i][a][0][c] = min(f[i][a][0][c],f[i][a][b][c]+1);
    					f[i][a][b][0] = min(f[i][a][b][0],f[i][a][b][c]+1);
    				}
    	printf("%d
    ",f[n][0][0][0]);
    	return 0;
    }
    
    


    (18.) ( exttt{P2915 [USACO08NOV]奶牛混合起来Mixed Up Cows})

    题目链接

    ( exttt{题解})

    是一道状压DP的好题。

    // 思想很巧妙!设f[i][j] 为i状态压缩,j奶牛结尾。

    // 每个序列不定奶牛结尾,只要满足差值就可以由上一个奶牛转移过来

    太困不想写了。

    ( exttt{Code})

    
    //	思想很巧妙!设f[i][j] 为i状态压缩,j奶牛结尾。
    //	每个序列不定奶牛结尾,只要满足差值就可以由上一个奶牛转移过来 
    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 20
    #define MAXN 67000
    #define int long long
    using namespace std;
    int n,K,maxn,ans;
    int s[N];
    int f[MAXN][N];
    bool check(int x,int y) {
    	return abs(s[x]-s[y]) > K;
    }
    signed main()
    {
    	scanf("%lld%lld",&n,&K);
    	for(int i=1;i<=n;++i)
    		scanf("%lld",&s[i]);
    	maxn = (1<<n) - 1;
    	for(int i=1;i<=n;++i)
    		f[(1<<(i-1))][i] = 1;
    	for(int i=1;i<=maxn;++i)
    		for(int j=1;j<=n;++j)
    		{
    			if(f[i][j])	continue;	//避免覆盖预处理
    			if(!(i & (1<<(j-1))))	continue;
    			int state = i ^ (1<<(j-1));
    			for(int l=1;l<=n;++l) {
    				if(l == j)	continue;
    				if(check(j,l)) f[i][j] += f[state][l];
    			}
    		}
    	for(int i=1;i<=n;++i)
    		ans += f[maxn][i];
    	printf("%lld
    ",ans);
    	return 0;
    }
    
    


    (19.) ( exttt{P4744 [Wind Festival]Iron Man})

    题目链接

    ( exttt{题解})

    这道题是ygt大佬推给我的

    方程一遍推对好开心!

    题意:给出一段环状序列,即认为(A_1)(A_n)是相邻的,选出其中连续不重叠且非空的K段使得这K段和最大。

    环形分段最大子段和

    定义状态: (f[i][j][1/0]) 表示 i位置 分j段 当前位置选不选 的最大和

    转移方程: (f[i][j][1] = max{ f[i-1][j-1][1] , f[i-1][j-1][0] , f[i-1][j][1] }) (&& j>=1)

    (f[i][j][0] = max{ f[i-1][j][1] , f[i-1][j][0] })

    环形处理:两次DP,一次不连接,一次强制选 1,n.

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define Max(a,b,c) max(max(a,b),c)
    using namespace std;
    const int N = 2e5+7;
    int n,K=2,ans;
    int a[N];
    int f[N][57][2];	//f[i][j][val]表示 i位置 分j段 当前选不选 
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)
    		scanf("%d",&a[i]);
    	memset(f,128,sizeof(f));	//初始极小化
    	f[0][0][0] = f[0][0][1] = 0;	//不强制选1,n
    	for(int i=1;i<=n;++i)
    		for(int j=0;j<=K;++j)
    		{
    			if(j>0)	f[i][j][1] = Max(f[i-1][j][1],f[i-1][j-1][1],f[i-1][j-1][0])+a[i];
    			f[i][j][0] = max(f[i-1][j][1],f[i-1][j][0]);
    		}
    	ans = max(f[n][K][1],f[n][K][0]);
    	memset(f,128,sizeof(f));
    	f[1][0][1] = a[1];	//选1,且不能单独列为一段,强制与n结合 
    	for(int i=2;i<=n;++i)
    		for(int j=0;j<=K;++j)
    		{
    			if(j>0)	f[i][j][1] = max(f[i-1][j-1][1],f[i-1][j-1][0])+a[i];
    			f[i][j][1] = max(f[i][j][1],f[i-1][j][1]+a[i]);
    			f[i][j][0] = max(f[i-1][j][0],f[i-1][j][1]);
    		}
    	ans = max(ans,f[n][K][1]);
    	printf("%d
    ",ans);
    	return 0;
    }
    
    


    (20.) ( exttt{P1108 低价购买})

    题目链接

    ( exttt{题解})

    是一道好题目,求最长下降子序列和方案总数

    显然不是最长下降子序列那么简单

    看到 n<=5000 的数据范围可以考虑 O(n2) 的DP求最长下降子序列,因为这样我们可以使用一些DP过程中的可利用条件

    (f[i])为以 (i) 最长下降子序列的长度 ,(t[i])为以 (i) 结尾且长度为(f[i])下降子序列方案总数

    思考:

    我们求(f[i])的时候是每一次从前面的状态转移,如果(f[i]) 是由 (f[j])转移而来 ((j<i)) 那么(t[i])是不是可以由(t[j])转移过来呢? 可以,即 if(f[i]==f[j]+1&&a[i]<a[j]) t[i]+=t[j] (j<i)

    当一个最长下降子序列长1时,也就是说只包含他自己,方案为数也为1 ,即 if(f[i]==1) t[i] = 1;

    考虑两个子序列相同,如序列:

    编号 01 02 03 04 05 06 07

    (a_i)[ ] 68 69 98 74 64 68 64

    3,4,5 和 3,4,7 组成的序列一样,编号却不同,如何避免重复计算呢?

    例如在求7这里 ,我们可以把 t[5] = 0; 方案数清空,删除这个序列。t[7]又能从t[4]这里继承方案数 即 if(f[i]==f[j]&&a[i]==a[j]) t[j]=0; (j<i)

    ( exttt{Code})

    
    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 5007
    using namespace std;
    int n,maxx,sum;
    int a[N],f[N],t[N];	//t[i]存以i结尾的最长下降子序列的方案 
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i)
    		scanf("%d",&a[i]);
    	for(int i=1;i<=n;++i) {
    		f[i] = 1;
    		for(int j=1;j<i;++j)
    			if(a[i]<a[j]) f[i] = max(f[i],f[j]+1);
    		if(f[i]==1)	t[i] = 1;	//自己也算一种方案 
    		maxx = max(maxx,f[i]);
    		for(int j=1;j<i;++j) {
    			if(f[i]==f[j] && a[i]==a[j])	//相当于同一种序列 
    				t[j] = 0;					//前一个序列没有了 , 这一个方案又可以用相同的方式加回来 
    			else if(f[i]==f[j]+1 && a[i]<a[j])
    				t[i] += t[j];				//可以从上一个状态加上来 
    		}
    	}
    	for(int i=1;i<=n;++i)
    		if(f[i]==maxx)	sum += t[i];	//加最长的方案
    	printf("%d %d
    ",maxx,sum);
    	return 0; 
    }
    
    


    (21.) ( exttt{P1136 迎接仪式})

    题目链接

    ( exttt{题解})

    一次交换即j变z ,z变j

    定义f[i][a][b] 表示i位置把a个j变z b个z变j

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define N 507
    #define M 107
    #define INF 0x3f3f3f
    using namespace std;
    int n,K;
    int f[N][M][M];	//有a个j变z ,b个z变j 
    char s[N];
    int main()
    {
    	scanf("%d%d",&n,&K);
    	cin>>s+1;
    	memset(f,128,sizeof(f));						//赋极小值,取max 
    	f[0][0][0] = f[1][0][0] = f[1][s[1]=='j'][s[1]=='z'] = 0;	//显然1不变为0 ,变也为0
    	for(int i=2;i<=n;++i) {
    		for(int a=0;a<=K;++a)
    			for(int b=0;b<=K;++b) {
    				f[i][a][b] = f[i-1][a][b];
    				if(s[i-1]=='j' && s[i]=='z')
    					f[i][a][b] = max(f[i][a][b],f[i-2][a][b]+1);
    				if(a && s[i]=='j' && s[i-1]=='j')
    					f[i][a][b] = max(f[i][a][b],f[i-2][a-1][b]+1);
    				if(b && s[i]=='z' && s[i-1]=='z')
    					f[i][a][b] = max(f[i][a][b],f[i-2][a][b-1]+1);
    				if(a && b && s[i-1]=='z' && s[i]=='j')
    					f[i][a][b] = max(f[i][a][b],f[i-2][a-1][b-1]+1);
    			}
    	}
    	int ans = -INF;
    	for(int i=0;i<=K;++i)
    		ans = max(ans,f[n][i][i]);
    	printf("%d
    ",ans);
    	return 0;
    }
    


    (22.) ( exttt{P1169 [ZJOI2007]棋盘制作})

    题目链接

    ( exttt{题解})

    悬线法

    ( exttt{Code})

    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #define N 2007
    using namespace std;
    int n,m,ans1,ans2;
    int le[N][N],ri[N][N],up[N][N];
    bool map[N][N];
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j) {
    			scanf("%d",&map[i][j]);
    			le[i][j] = ri[i][j] = j;
    			up[i][j] = 1;
    		}
    	for(int i=1;i<=n;++i)
    		for(int j=2;j<=m;++j) {
    			if(map[i][j-1] != map[i][j])
    				le[i][j] = le[i][j-1];	//Ïò×óÑÓ³¤ 
    		}
    	for(int i=1;i<=n;++i)
    		for(int j=m-1;j>=1;--j) {
    			if(map[i][j+1] != map[i][j])
    				ri[i][j] = ri[i][j+1];
    		}
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j) {
    			if(i>1 && map[i-1][j]!=map[i][j]) {
    				up[i][j] = up[i-1][j] + 1;
    				le[i][j] = max(le[i-1][j],le[i][j]);
    				ri[i][j] = min(ri[i-1][j],ri[i][j]);
    			}
    			int a = ri[i][j] - le[i][j] + 1;
    			int b = up[i][j];
    			int c = min(a,b);
    			ans1 = max(ans1,c*c);
    			ans2 = max(ans2,a*b);
    		}
    	printf("%d
    %d
    ",ans1,ans2);
    	return 0;	
    }
    
    

    未完待续...

  • 相关阅读:
    Linux内核分析第七周学习笔记——Linux内核如何装载和启动一个可执行程序
    <深入理解计算机系统>第七章读书笔记
    Linux内核分析第六周学习笔记——分析Linux内核创建一个新进程的过程
    linux内核分析第3章&第18章读书笔记
    Linux内核分析第五周学习总结——分析system_call中断处理过程
    课本第五章读书笔记
    Linux内核分析第四周学习总结——系统调用的工作机制
    课本第一二章读书笔记
    20162328蔡文琛 实验二 树
    20162328蔡文琛 大二week07
  • 原文地址:https://www.cnblogs.com/BaseAI/p/11257723.html
Copyright © 2020-2023  润新知