线性dp

例题一:最长不下降子序列

思路

可以考虑到\(dp[i]=dp[j]+1,(j<i,a[j]<a[i])\),因此可以使用两层\(for\)循环求出答案。考虑到当前点的最优解一定是基于前一个恰好比当前点小的点的值,因此即可每次二分查找已有的序列,若序列末尾都小于了当前点,就更新当前点,反之,则替换第一个比当前点大的数。

Code

#include<bits/stdc++.h>
using namespace std;

int n, a[100005], dp[100005];

template<typename T>
inline void read(T&x){
	x=0; char q; bool f = 1;
	while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
	while(isdigit(q)){
		x = (x<<1) + (x<<3) + (q^48);
		q = getchar();
	}
	x = f?x:-x;
}

template<typename T>
inline void write(T x){
	if(x < 0){
		putchar('-');
		x = -x;
	}
	if(x > 9)	write(x/10);
	putchar(x%10+'0');
}

int main(){
	read(n);
	for(register int i = 1; i <= n; ++i)	read(a[i]);
	int tmp = 0;
	dp[++tmp] = a[1];
	for(register int i = 2; i <= n; ++i){
		if(a[i] > dp[tmp])	dp[++tmp] = a[i];
		else{
			int p = lower_bound(dp+1, dp+tmp+1, a[i])-dp;
			dp[p] = a[i];
		}
	}
	write(tmp);
	return 0;
}

例题二:最长公共子序列

思路

很容易可以想到\(dp[i][j]=\begin{cases}dp[i-1][j-1]+1,a[i]=b[j]\\\max(dp[i-1][j],dp[i][j-1]),a[i]\neq b[j]\end{cases}\)但是二维的数组以及\(n^2\)的时间复杂度都是不被允许的。所以把他转化为别的问题。

如果我们将串\(A\)编号为\(1,2,\ldots,n\)那么可以得出\(B\)串能与其匹配的就是他的对应编号的最长上升降子序列,。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

int x, b[100001], a[100001], dp[100001];
int n;

template<typename T>
inline void read(T&x){
    x = 0; char q; bool f = 1;
    while(!isdigit(q = getchar()))  if(q == '-')    f = 0;
    while(isdigit(q)){
        x = (x<<1) + (x<<3) + (q^48);
        q = getchar();
    }
    x = f?x:-x;
}

template<typename T>
inline void write(T x){
    if(x < 0){
        putchar('-');
        x = -x;
    }
    if(x > 9)	write(x/10);
    putchar(x%10^48);
}

int main(){
	read(n);
    for(register int i = 1; i <= n; ++i){
    	read(x);
        a[x] = i;
    }
	for(register int i = 1; i <= n; ++i)   read(b[i]);
	memset(dp, 0x3f3f3f3f, sizeof(dp));
	int tmp = 0;
	dp[0] = 0;
	for(register int i = 1; i <= n; ++i){
		int l = 0, r = tmp, mid;
		if(a[b[i]] > dp[tmp])	dp[++tmp] = a[b[i]];
		else{
			while(l<r){	
			    mid = l+r >> 1;
			    if(dp[mid] > a[b[i]])	r = mid;
				else l = mid+1;
			}
			dp[l] = min(a[b[i]],dp[l]);
     	}
    }
    write(tmp);
    return 0;
}

例题三:\(NOIP2008\)传纸条

思路

数据范围\(n,m\leq 50\)很容易就能想到四维\(dp\),考虑到可能重复的问题,我们可以肯定的是因为路径是不会交叉的,所以一条路径肯定在左下,一条在由上,因此只需要保证\(i<j,p>q\)即可不重不漏。方程为

\[dp[i][j][p][q]=max(dp[i-1][j][p-1][q],dp[i-1][j][p][q-1],dp[i][j-1][p-1][q],dp[i][j-1][p][q-1])+a[i][j]+a[p][q] \]

\(O(n^4)\)的时空复杂度即可解决。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

template<typename T>
inline void read(T&x){
    x = 0; char q; bool f = 1;
    while(!isdigit(q = getchar()))  if(q == '-')    f = 0;
    while(isdigit(q)){
        x = (x<<1) + (x<<3) + (q^48);
        q = getchar();
    }
    x = f?x:-x;
}

template<typename T>
inline void write(T x){
    if(x < 0){
        putchar('-');
        x = -x;
    }
    if(x > 9)	write(x/10);
    putchar(x%10^48);
}

int n, m;
int dp[55][55][55][55];
int a[55][55];

int main(){
	read(m), read(n);
	for(register int i = 1; i <= m; ++i)
		for(register int j = 1; j <= n; ++j)
			read(a[i][j]);
	for(register int i = 1; i <= m; ++i)
		for(register int j = 1; j <= n; ++j)
			for(register int p = i+1; p <= m; ++p)
				for(register int q = 1; q < j; ++q)
					dp[i][j][p][q] = max(dp[i-1][j][p-1][q], max(dp[i-1][j][p][q-1], max(dp[i][j-1][p-1][q], dp[i][j-1][p][q-1])))+a[i][j]+a[p][q];
    write(dp[m-1][n][m][n-1]);
	return 0;
}

例题四:\(CF837D:Round Subset\)

思路

*什么情况下会有\(0\)

可以通过\(1\)年级数学知识得知若设该数 \(2\) 的因子个数为 \(two\)\(5\) 的因子个数为 \(five\),可知 \(0\) 的个数为 \(\min(two,five)\)

如何得到最优解

我们可以设 \(dp[j][k]\) 表示当前已经到了 \(j\) 并且有 \(k\) 个因子 \(5\) 时最大的因子 \(2\) 的个数。

所以,我们可以推出式子:

\(dp[j][k]=max(dp[j][k],dp[j-1][k-five[i]]+two[i])\)

\(i\) 为当前枚举到的数,\(j\) 为以选择个数,\(k\)\(5\) 因子数。

for(register int i = 1; i <= n; ++i)
	for(register int j = i; j >= 1; --j)
		for(register int p = 6200; p >= five[i]; --p)
			dp[j][p] = max(dp[j][p], dp[j-1][p-five[i]]+two[i]);

如何查询

\(6200\)\(1\) 查询,\(ans = \max(dp[k][i],i)\),若 \(ans>i\),则直接输出 \(ans\),因为此后 \(i\) 只会越来越小直到找不到就输出 \(ans\)


Q1:为何 \(p\)\(6200\) -> \(five_i\):有 \(200\) 个数,每个数大概最多是 \(30\) 因子左右,所以最多也不超过 \(6200\)

Q2:为何 \(p\) 是从大到小枚举:如果从小到大会选多次(可参考完全背包)。

Q3:为何要初始化 \(dp\) 数组:部分状态可能存在非法情况(根本无法到达),如果不 \(memset\) 就会让他更新别的状态,导致错误答案。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

int n, k, ans;
int dp[205][6205];

long long num[205];
int two[205], five[205];

template<typename T>									//快读快写 
inline void read(T&x){
	x=0; char q; bool f=1;
	while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
	while(isdigit(q)){
		x = (x<<1)+(x<<3)+(q^48);
		q = getchar();
	}
	x = f?x:-x;
}

template<typename T>
inline void write(T x){
	if(x<0){
		putchar('-');
		x = -x;
	}
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}

int main(){
	read(n), read(k);
	for(register int i = 1; i <= n; ++i){
		read(num[i]);									//输入 
		while(num[i] % 2 == 0){							//循环找two[i] 
			++two[i];
			num[i] /= 2;
		}
		while(num[i] % 5 == 0){							//循环找five[i] 
			++five[i];
			num[i] /= 5;
		}
	}
	memset(dp, -0x3f, sizeof(dp)); 						//初始化dp数组 
	dp[0][0] = 0; 
	for(register int i = 1; i <= n; ++i)
		for(register int j = i; j >= 1; --j)
			for(register int p = 6200; p >= five[i]; --p)
				dp[j][p] = max(dp[j][p], dp[j-1][p-five[i]]+two[i]);
	for(register int i = 6200; i >= 1; --i){
		ans = max(ans, min(i, dp[k][i]));							//寻找ans 
		if(ans > i){
			write(ans);
			return 0;
		}
	}
	write(ans);											//输出 
	return 0;
}
posted @ 2024-10-09 18:34  Zzzzzzzm  阅读(7)  评论(0)    收藏  举报