线性dp

P1725 琪露诺

一道线性dp的题目
状态设置:f[i]:表示到达位置i时的最大价值
状态转移:f[i] = max(f[i], f[j] + a[i])(i - r =< j <= i - l)
这样做显然枚举状态是O(n)转移也需要O(n),所以时间复杂度是O(n^2)的显然会T
状态我们是一定要枚举的,我们能优化的只有转移的计算量, 我们需要快速找到i - r =< j <= i - l的f[j]的最大值,
我们发现当i+1, 我们要求的是 i - r + 1=< j <= i - l + 1中的最大值,也就是说只新增了一个元素,减少了一个元素我们要求的是区间的最大值,这样很明显我们可以用滑动窗口优化,来维护这样一个长度为r - i + 1的窗口的最大值
这样这道题就解决了

#include <bits/stdc++.h> 

using namespace std;

const int N = 2e5 + 10;
int f[N], a[N], ans = -0x3f3f3f3f;
int q[N], tt = -1, hh = 0;

void solve()
{
	int n, l, r; scanf("%d%d%d", &n, &l, &r);
	for(int i = 0; i <= n; ++ i) scanf("%d", &a[i]);
	memset(f, 0xcf, sizeof (f));
	f[0] = 0;
	for(int i = l; i <= n; ++ i)
	{
		while(hh <= tt && q[hh] < i - r)	++ hh;
		while(hh <= tt && f[q[tt]] < f[i - l]) -- tt;
		q[++ tt] = i - l;
		f[i] = f[q[hh]] + a[i];
		if(i + r > n)	ans = max(ans, f[i]);
	}
	printf("%d\n", ans);
}

int main()
{
//	freopen("1.in", "r", stdin);
//	int t; scanf("%d", &t);
//	while(t --) solve();
	solve();
	return 0;
}

P1874 快速求和

这道题目可以dp也可以搜索,整体思路上不是很难,但是细节比较难搞
dp的思路:
状态:f[i][j]表示前i个数组合成j的最小加号
转移:f[i][j] = min(f[k][j - v] + 1, f[i][j]),
时间复杂度:len(s)* len(s)* m

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e5 + 10;
int f[50][N], a[50], n, m; 
char c[50];

void pre()
{
	for(int i = 1; i <= n; ++ i)	a[i] = c[i] - '0';
}

int val(int l, int r)
{
	int res = 0;
	for(int i = l; i <= r; ++ i)	
	{
		res = res * 10 + a[i];
		if(res > 1e7)	return 1e9;
	}
	return res;
}

void solve()
{	
	memset(f, 0x3f, sizeof f);
	f[0][0] = 0;
	for(int i = 1; i <= n; ++ i)
		for(int j = 0; j <= m; ++ j)
			for(int k = i - 1; k >= 1; -- k)
			{
				int v = val(k + 1, i);
				cout << i << ' ' << j << ' ' << v << endl;
				if(v <= j)	f[i][j] = min(f[k][j - v] + 1, f[i][j]);		
			}
		
//	for(int i = 0; i <= n; ++ i)
//		for(int j = 0; j <= m; ++ j)
//			cout << i << ' ' << j << ' ' << f[i][j] << endl;
	if(val(1, n) == m)	cout << 0 << endl;
	else	if(f[n][m] > n)	cout << -1 << endl;			
	else	cout << f[n][m] << endl;
}

int main()
{
// 	freopen("1.in", "r", stdin);
	scanf("%s", c + 1);
	scanf("%d", &m);
	n = strlen(c + 1);
	pre();
	solve(); 
	return 0;
}

搜索:

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e5 + 10;
int a[50], n, m; 
char c[50];
int ans = 1e9;
void pre() 
{
	for(int i = 1; i <= n; ++ i)	a[i] = c[i] - '0';
}

int val(int l, int r)
{
	int res = 0;
	for(int i = l; i <= r; ++ i)	res = res * 10 + a[i];
	return res;
}


void dfs(int u, int k, int cnt)
{
    // cout << u << ' ' << k << ' ' << cnt << endl;
	int tmp = u + val(k + 1, n);
	if(tmp < m)	return;
	if(cnt > ans)	return; 
	if(tmp == m)	
	{
	    ans = min(ans, cnt);
	    return;
	}
	for(int i = k + 1; i <= n; ++ i)	dfs(u + val(k + 1, i), i, cnt + 1);
}

int main()
{

	scanf("%s", c + 1);
	cin >> m;
	n = strlen(c + 1);
	pre();
	dfs(0, 0, 0);
	if(ans == 1e9)   cout << -1 << endl;
	else    cout << ans << endl;
	return 0;
}

方格取数

#include <bits/stdc++.h> 

using namespace std;

const int N = 10;
int f[N * 2][N][N], a[N][N], n; 
void solve()
{	
	scanf("%d", &n);
	while(1)
	{
		int x, y, w;
		scanf("%d%d%d", &x, &y, &w);
		if(x == 0)	break; 
		a[x][y] = w;
	}
	memset(f, 0xcf, sizeof f);
	f[0][0][0] = 0;
	for(int k = 1; k <= 2 * n; ++ k)
		for(int i = 1; i <= min(n, k); ++ i)
			for(int j = 1; j <= min(n, k); ++ j)
			{
				int t1 = max(f[k - 1][i - 1][j - 1], f[k - 1][i][j]);
				int t2 = max(f[k - 1][i - 1][j], f[k - 1][i][j - 1]);
				f[k][i][j] = max(t1, t2) + a[i][k - i] + a[j][k - j];
				if(i == j)	f[k][i][j] -= a[i][k - i];
			}
	printf("%d", f[2 * n][n - 1][n]);
}

int main()
{
 	freopen("1.in", "r", stdin);
	solve(); 
	return 0;
}

P2679 [NOIP2015 提高组] 子串

状态表示:f[i][j][k]:表示考虑a中前i个字符,b中前j个字符,并且已经使用了k个字符串的方案数
转移的话需要分类讨论:
当a[i] != b[j]时,f[i][j][k] = f[i - 1][j][k]
当a[i] == b[j]并且a[i - 1] != b[j - 1]时, f[i][j][k] = f[i - 1][j][k] + f[i - 1][j - 1][k - 1]
当a[i] == b[j]并且a[i - 1] == b[j - 1], 并且a[i - 2] != b[j - 2]时 f[i][j][k] = f[i - 1][j][k] + f[i - 1][j - 1][k - 1] + f[i - 2][j - 2][k - 1]
当a[i - t] != b[j - t]时 f[i][j][k] = f[i - 1][j][k] + f[i - 1][j - 1][k - 1] + f[i - 2][j - 2][k - 1] + ... + f[i - t][j - t][k - 1];
我们可以用sum[i][j][k]表示前缀f[i - 1][j - 1][k - 1] + f[i - 2][j - 2][k - 1] + ... + f[i - t][j - t][k - 1];
而sum[i][j][k] = f[i - 1][j - 1][k - 1] + s[i - 1][j - 1][k],递推可以边做边更换新

#include <bits/stdc++.h> 

using namespace std;

const int N = 1010, M = 210, mod = 1000000007;
int f[2][M][M], n, m, d, sum[2][M][M];
char a[N], b[M]; 
void solve()
{	
	scanf("%d%d%d", &n, &m, &d);
	scanf("%s", a + 1);
	scanf("%s", b + 1);
	f[0][0][0] = 1;
	f[1][0][0] = 1;
	for(int i = 1; i <= n; ++ i)
		for(int j = 1; j <= m; ++ j)
			for(int k = 1; k <= min(d, j); ++ k)
			{
				if(a[i] != b[j])	sum[i & 1][j][k] = 0;
				else	sum[i & 1][j][k] = (f[(i - 1) & 1][j - 1][k - 1] + sum[(i - 1) & 1][j - 1][k]) % mod;
				f[i & 1][j][k] = (f[(i - 1) & 1][j][k] + sum[i & 1][j][k]) % mod;
			}
	printf("%d", f[n & 1][m][d]);
}

int main()
{
// 	freopen("1.in", "r", stdin);
	solve(); 
	return 0;
}

P1435 [IOI2000] 回文字串

题目的含义是求原串和翻转串的最长公共子序列,直接套用最长公共子序列模版即可

#include <bits/stdc++.h> 

using namespace std;

const int N = 1010;
int f[N][N];
char a[N], b[N];
int n;
 
void solve()
{	
	scanf("%s", a + 1);
	n = strlen(a + 1);
	for(int i = 1; i <= n; ++ i)	b[n - i + 1] = a[i];
	f[0][0] = 0;
	for(int i = 1; i <= n; ++ i)
		for(int j = 1; j <= n; ++ j)
		{
			f[i][j] = max(f[i - 1][j], f[i][j - 1]); 
			if(a[i] == b[j])	f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
		}
	printf("%d\n", n - f[n][n]);
}

int main()
{
// 	freopen("1.in", "r", stdin);
	solve(); 
	return 0;
}

P1854 花店橱窗布置

这道题目有两种思路,状态设置的不同,转移方程也会不一样
先说一下我最开始的思路,最开始考虑的是n^2的做法,
f[i][j]表示考虑前i朵花放在前j个花瓶中的最大值
转移的话就是考虑第i朵花放不放在第j个花瓶就会有
f[i][j] = max(f[i][j - 1], f[i - 1][j - 1] + w[i][j]);

另一个思路是f[i][j]表示第i朵花放在第j个花瓶中的最大值,这种方法的复杂度是n^3需要枚举转移即枚举第i-1朵花是放在哪个花瓶
f[i][j] = max(f[i][j], f[i - 1][k]);

#include <cstdio>

using namespace std;

int map[101][101],f[101][101]={0},path[101][101]={0};

int fl,v;

void print(int x,int y)
{
    if(x<1||y<1)return;
    if(path[x][y])
    {
        print(x-1,y-1);
        printf("%d ",y);
    }
    else
    {
        print(x,y-1);
    }
}

int main()
{
    scanf("%d%d",&fl,&v);
    for(int i=1;i<=fl;i++)
    {
        for(int j=1;j<=v;j++)
        {
            scanf("%d",&map[i][j]);
        }
    }
    for(int i=1;i<=fl;i++)
    {
        for(int j=1;j<=v;j++)
        {
            if(f[i][j-1]<f[i-1][j-1]+map[i][j]||i>=j)
            {
                f[i][j]=f[i-1][j-1]+map[i][j];
                path[i][j]=1;//记录对于每个状态是怎么到达的
            }
            else
            {
                f[i][j]=f[i][j-1];
            }
        }
    }
    printf("%d\n",f[fl][v]);
    print(fl,v);//递归方式打印解
    return 0;
}
#include <bits/stdc++.h> 
#define first x
#define second y
using namespace std;

typedef pair<int, int> PII;
const int N = 110;
int f[N][N], w[N][N], pre[N][N];
int n, m;


void print(int x, int y)
{
	if(x == 0)	return;
	print(x - 1, pre[x][y]);
	printf("%d ", y);
}

void solve()
{	
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++ i)
		for(int j = 1; j <= m; ++ j)
			scanf("%d", &w[i][j]);
	memset(f, -127, sizeof(f));
	for(int i = 0; i <= m; ++ i)	f[0][i] = 0;
	for(int i = 1; i <= n; ++ i)
		for(int j = i; j <= m; ++ j)
			for(int k = j - 1; k >= 1; -- k)
			{
				
				if(f[i][j] < f[i - 1][k] + w[i][j])
				{
					pre[i][j] = k;
					f[i][j] = f[i - 1][k] + w[i][j];
				}
			}
	
	int t = -1e9, pos = 0;
	for(int i = 1; i <= m; ++ i)
	{
		if(f[n][i] > t)
		{
			pos = i;
			t = f[n][i];	
		}	
	}
	printf("%d\n", f[n][pos]);
	print(n, pos);
}

int main()
{
// 	freopen("1.in", "r", stdin);
	solve(); 
	return 0;
}

P1833 樱花

这道题目是一道混合背包问题,分别处理就好

#include <bits/stdc++.h> 

using namespace std;

const int N = 1010, M = 1e5 + 10;
int f[N], w[M], v[M];
int n, m, cnt, hh1, hh2, mm1, mm2;
 
void solve()
{	
	scanf("%d:%d", &hh1, &mm1);
	scanf("%d:%d", &hh2, &mm2);
	scanf("%d", &n);
	m = hh2 * 60 + mm2 - hh1 * 60 - mm1;
	for(int i = 1; i <= n; ++ i)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		if(!c)
		{
			c = (m + 1) / a;
		}
		int k = 1;
		while(k <= c)
		{
			w[++ cnt] = k * b;
			v[cnt] = k * a;
			c -= k;
			k *= 2;
		}
		if(c)
		{
			w[++ cnt] = c * b;
			v[cnt] = c * a;
		}
	}
	for(int i = 1; i <= cnt; ++ i)
		for(int j = m; j >= v[i]; --  j)
			f[j] = max(f[j], f[j - v[i]] + w[i]);
			
	printf("%d", f[m]); 
}

int main()
{
// 	freopen("1.in", "r", stdin);
	solve(); 
	return 0;
}

P2340 [USACO03FALL] Cow Exhibition G
e97c136c9e2311cc396b448d4559098.jpg

这道题目是有限制的选择问题思路上并不难,但是有一些特殊和细节的处理需要特别关注
第一点就是因为这里的贡献有负数,可能会导致状态表示的时候下标出现负数,所以需要进行一个偏移量的处理,即给所有的第二维都增加一个偏移量M,偏移量取多少合适?只要满足所有的状态的下标都大于0就是合适的
第二点就是需要进行空间优化,滚动数组或者直接把第一位省略都行,这里空间优化可以参考01背包的优化

#include <bits/stdc++.h> 

using namespace std;

const int N = 410, M = 4e5;
int f[M * 2 + 10] , iq[N], eq[N];
int n, m;
 
void solve()
{	
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i)	scanf("%d%d", &iq[i], &eq[i]);
	memset(f, 0xcf, sizeof(f));
	f[M] = 0;
	for(int i = 1; i <= n; ++ i)
	{
		//0 <= j - eq[i] <= 2*M  && j >= 0,这里最开始没想明白
		if(eq[i] >= 0)	for(int j = M + M + eq[i]; j >= eq[i]; --  j)	f[j] = max(f[j], f[j - eq[i]] + iq[i]);
		else	for(int j = 0; j <= M + M + eq[i]; ++ j)	f[j] = max(f[j], f[j - eq[i]] + iq[i]);
	}
	//找答案的时候需要枚举	
	int ans = 0;		
	for(int i = 0; i <= M; ++ i)
        //注意题目中说了合法的答案,iq,eq都需要>=0,所以i,发f[i + M]都需要>=0
		if(f[M + i] >= 0)	ans= max(ans, f[M + i] + i);
	printf("%d", ans);
}

int main()
{
// 	freopen("1.in", "r", stdin);
	solve(); 
	return 0;
}

P4310 绝世好题
首先想到的就是LIS直接o(n ^ 2)去dp但是看了数据范围是1e5肯定过不了,然后没思路,看题解,后来理解了

这道题目是从转移的条件处考虑,然后通过设置状态进行优化的
首先我们考虑转移是怎么转移x可以向y转移要求x&y != 0,如果从位运算的角度考虑,x和y至少有一个公共的位都为1,所以我们可以根据这一位去转移。(这一点很关键)
我们这里先说一下我们如何去dp
状态表示:bit[i][j],表示考虑前i个数,选出来组成满足条件的序列,结尾x的第j位为1的最长长度.
状态转移:maxx = max(maxx, bit[i][j] + 1);(a[i] & j != 0)
当a[i]的第j位为1时就可以进行转移
每次转移完之后,我们就需要维护bit数组,因为下一次转移任然需要bit数组
所以我们需要考虑维护bit数组,那么如何维护bit数组呢?这时我们就考虑bit数组的用途和含义,bit[k]表示以前i个数中某一个数x结尾,并且x的第k位为1的最大长度如果可以转移的话那么maxx就被之前的bit[k]更新过,这时啊a[i]的第k位为1,我们就需要将bit[k]更新成当前的最大长度,而那些为0的位不能转移所以直接沿用之前的bit[k]而不用进行更新。
至此我的所有疑惑就都解释完了,代码如下,暴力的就不贴了

#include <bits/stdc++.h> 

using namespace std;

const int N = 1e5 + 10, M = 32;
int a[N], bit[32], n, ans;
 
void solve()
{	
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i)	scanf("%d", &a[i]);
	
	for(int i = 1; i <= n; ++ i)
	{
		int maxx = -1;
		for(int j = 0; j <= 31; ++ j)	if(a[i] & (1 << j))	maxx = max(maxx, bit[j] + 1);
		for(int j = 0; j <= 31; ++ j)	if(a[i] & (1 << j))	bit[j] = max(bit[j], maxx);
		ans = max(ans, maxx);
	}
	printf("%d", ans);
}

int main()
{
// 	freopen("1.in", "r", stdin);
	solve(); 
	return 0;
}

P3147 [USACO16OPEN] 262144 P

本来这道题目是在洛谷线性动态规划提单里面出现的,但是看完题感觉这种合并的题目很像区间dp,但是数据范围又很劝退,看了题解又理解了一会才明白
首先这道题目是一道区间dp,里面有很像倍增的思想
状态:f[i][j]:表示左端点位j能合并出来i的区间的右端点的位置,右端点这里是不被包括在合并的区间内的
转移:f[i][j] = f[i - 1][f[i - 1][j]], f[i - 1][j]是以j为左端点合并出来i - 1的右端点,这个右端点可以作为右半边合并出来i - 1的左端点,这样左右两个i - 1,就可以合并出来i了,i的取值范围是多少?也就是我们最大值的上限是多少?两两合并的话最多合并log2(n)次,也就是最多合并18次,所以最终能得到的数的最大值是58.


#include <bits/stdc++.h> 

using namespace std;

const int N = 3e5 + 10;
int f[57][N], n, ans;
 
void solve()
{	
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i)
	{
		int x; scanf("%d", &x);
		f[x][i] = i + 1;
	}
	
	for(int i = 2; i <= 56; ++ i)
		for(int j = 1; j <= n; ++ j)
		{
			if(!f[i][j])	f[i][j] = f[i - 1][f[i - 1][j]];
			if(f[i][j])	ans = i;
		}
	printf("%d", ans);
}

int main()
{
// 	freopen("1.in", "r", stdin);
	solve(); 
	return 0;
}
posted @ 2023-05-20 23:11  cxy8  阅读(8)  评论(0编辑  收藏  举报