关于《算法竞赛进阶指南》动态规划的笔记

1.AcWing 271. 杨老师的照相排列271. 杨老师的照相排列 - AcWing题库

这道题最重要的是想到开一个f[a][b][c][d][e](注意数据范围)

为什么要开一个五维的呢?因为每一行的信息是无法压缩到一起(我不会这样的技巧),而且我们(蒟蒻)一般的认为三行就是f[a][b][c],这是一个很自然的想法,然而当我问自己f[a][b][c]和f[a][b][c][0][0]有什么区别后,我无法回答。所以这道题给我的一个教训是做DP要从全局想共同点(统一的状态来表示),再分布想每个状态如何转移。

确定了状态后,如何转移呢

显然人是要一个一个放的,这个人可以放到1-5中的任意一行。

这里需要分类(以及思考转移时的条件,这道题条件就是后排到前排递减所以绝对不能出现前排的个数比后排多),最后,因为我们是从小到大放进去的,所以不用管横向的递减。

当a > 0 && a - 1 >= b时,最后一个同学可能被安排在第1排,方案数是f[a - 1][b][c][d][e];
当b > 0 && b - 1 >= c时,最后一个同学可能被安排在第2排,方案数是f[a][b - 1][c][d][e];
当c > 0 && c - 1 >= d时,最后一个同学可能被安排在第3排,方案数是f[a][b][c - 1][d][e];
当d > 0 && d - 1 >= e时,最后一个同学可能被安排在第4排,方案数是f[a][b][c][d - 1][e];
当e > 0时,最后一个同学可能被安排在第5排,方案数是f[a][b][c][d][e - 1];

换一个角度来思考DP我认为这就是一个函数,而状态就是他的横轴,状态存的数就是纵轴。

DP必须是有一个连续的(无论何时都适用的规则),我们利用计算机来求出每一层状态的值。

最后是代码

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 31;
int n;
LL f[N][N][N][N][N];

int main()
{
    while(cin >> n,n)
    {
        int s[5] = {0};
        for(int i = 0;i < n;i ++)cin >> s[i];
        memset(f,0,sizeof f);
        f[0][0][0][0][0] = 1;

        for(int a = 0;a <= s[0];a ++)
            for(int b = 0;b <= min(a,s[1]);b ++)
                for(int c = 0;c <= min(b,s[2]);c ++)
                    for(int d = 0;d <= min(c,s[3]);d ++)
                        for(int e = 0;e <= min(d,s[4]);e ++)
                        {
                            LL &x = f[a][b][c][d][e];
                            if(a && a - 1 >= b)x += f[a - 1][b][c][d][e];
                            if(b && b - 1 >= c)x += f[a][b - 1][c][d][e];
                            if(c && c - 1 >= d)x += f[a][b][c - 1][d][e];
                            if(d && d - 1 >= e)x += f[a][b][c][d - 1][e];
                            if(e) x += f[a][b][c][d][e-1];

                        }
        cout << f[s[0]][s[1]][s[2]][s[3]][s[4]] << endl;


    }
}

2.最长公共子序列(二分法)

【模板】最长公共子序列 - 洛谷

解释:

A:3 2 1 4 5

B:1 2 3 4 5

我们不妨给它们重新标个号:把3标成a,把2标成b,把1标成c……于是变成:

A: a b c d e
B: c b a d e

这样标号之后,LCS长度不会改变。但是出现了一个性质:

两个序列的公共子序列,一定是A的子序列。而A本身必然是单调递增的。
因此这个公共子序列是单调递增的。所以这个公共子序列在B中也必须是单调递增。

倘若在B中是单调递增的,那么在A中也一定能找到这样的子序列。

核心就是不管A序列是什么,只把它变为单调递增的12345形式,然后让B序列用A代换。

那么为什么可以这样代换呢?

这真的科学吗

这其实就是重新定义了一下大小关系

我只粗浅的想象一下:题目说上升子序列,并没说一定是12345.我们可以定义一个共同的规则,设定一个大小关系。也就是说只要存在一段数字(可能不连续),共同满足一个关系,就可以把它看成一个《上升子序列》。实践证明这不会出错。

所以题目转化为:把A看成一个单调上升的子序列,求B中的最大的上升子序列。

最大的上升子序列中为什么可以替换掉原数列中的值:

考虑一个数列5 2 3 1 4

首先,把5加入答案序列中,然后加2,发现2<5所以显然2替换5不会使结果更差,

那么答案序列就是{2},然后加3,发现3>2,所以直接把3加到答案序列中:{2,3}

然后加1,我们发现1<3,于是我们找到一个最小的但是比1大的数字2,然后把1替换2,为什么这么做不会影响结果呢?你可以这么想,我们当前已经求出了一个当前最优的序列,如果我们用1替换2,然后后面来一个数字替换了3,那么我们就可以得到一个更优的序列,而如果没有数字替换3,那么这个1替换2也就是没有贡献的,不会影响我们结果的最优性。

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=1e5+1;
int n,a[N],b[N],number[N],f[N];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		number[a[i]]=i;
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&b[i]);
		f[i]=inf;
	}
	f[0]=0;
	int len=0;
	for(int i=1;i<=n;i++)
	{
		int l=0,r=len,mid;
		if(number[b[i]]>f[len])f[++len]=number[b[i]];
		else{
			while(l<r)
			{
				mid=l+r>>1;
				if(f[mid]>number[b[i]])r=mid;
				else l=mid+1;
			}
			f[l]=min(f[l],number[b[i]]);
		}
	}
	cout<<len;
	return 0;
}

posted @ 2022-09-24 21:16  zyc_xianyu  阅读(22)  评论(0编辑  收藏  举报