线性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;
}
这道题目有两种思路,状态设置的不同,转移方程也会不一样
先说一下我最开始的思路,最开始考虑的是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;
}
这道题目是一道混合背包问题,分别处理就好
#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
这道题目是有限制的选择问题思路上并不难,但是有一些特殊和细节的处理需要特别关注
第一点就是因为这里的贡献有负数,可能会导致状态表示的时候下标出现负数,所以需要进行一个偏移量的处理,即给所有的第二维都增加一个偏移量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;
}