7.29 “简单”DP
luogu P2467 [SDOI2010]地精部落
题目传送门
题目大意
一个序列符合要求,当且仅当其是一个$1 \sim n$的排列,且每一个$a[i]$
满足$a[i-1]\le a[i]$且$a[i]\ge a[i+1]$
或者满足$a[i-1]\ge a[i]$且$a[i]\le a[i+1]$
求长度为$n$的合法序列方案数
大体思路
神仙题。。。。
显然有以下性质:
1.当$i$与$i+1$不相邻时,交换$i$与$i+1$仍会得到一个合法的解
如:$3 2 4 1 5 \rightarrow 3 1 4 2 5$
2.一串序列$a[i]$,$i\in [1,n]$满足条件时,则$(i+1)-a[i]$亦满足条件
如:$3 2 4 1 5 \rightarrow 3 4 2 5 1$
3.满足条件的序列具有对称性
如:$3 2 4 1 5 \rightarrow 5 1 4 2 3$
则设$dp[i][j]$表示当前为前$i$个数的排列,且首位是$j$且为山峰的方案数。
综上,$dp[i][j]=dp[i][j-1]+dp[i-1][i-j+1]$
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> using namespace std; int n, p; long long ans; long long dp[2][4300]; int main(){ scanf("%d%d", &n, &p); dp[0][2] = 1; for(int i = 3; i <= n; ++i){ for(int j = 2; j <= i; ++j){ dp[i & 1][j] = (dp[i & 1][j - 1] + dp[(i - 1) & 1][i - j + 1]) % p; } } for(int i = 1; i <= n; ++i){ ans = ans + dp[n & 1][i] % p; } printf("%lld\n", (ans << 1) % p); return 0; }
|
luogu P2519 [HAOI2011]problem a
题目传送门
题目大意
一次考试共有$n$个人参加,第$i$个人说:“有$ai$个人分数比我高,$bi$个人分数比我低。”问最少有几个人没有说真话(可能有相同的分数)
大体思路
先进行补集转化,目标变成求说真话的人数。
有$ai$个人分数比我低,$bi$个人分数比我高,就意味着$[ai+1,n-bi-1]$是相同的数。
问题转化为使$k$段区间不重叠且权值和最大。按照右端点排序之后dp即可。
几点细节:
1.合法解可以有完全重合的区间,但是一段区间最多只能有区间长度个说真话的人。
2.要特判$ai \ge bi$的情况
设$f[i]$表示当前为第$i$位的区间最大值,则
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cctype> using namespace std; #define gc ch = getchar() int read(){ int ret = 0, f = 1; char gc; while(!isdigit(ch) && (ch ^ '-'))gc; if(!(ch ^ '-'))f = -1, gc; while(isdigit(ch)){ret = (ret << 3) + (ret << 1) + (ch ^ '0'), gc;} return ret * f; } #undef gc const int maxn = 1e5 + 10; int n; struct node{ int l, r; bool operator < (const node &a)const{ return r == a.r ? l < a.l : r < a.r; } }p[maxn]; int w[maxn], L[maxn], R[maxn]; int f[maxn]; int a, b, tot = 0; int main(){ #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif n = read(); for(int i = 1; i <= n; ++i){ a = read(), b = read(); p[i].l = a + 1, p[i].r = n - b; } sort(p + 1, p + n + 1); for(int i = 1; i <= n; ++i){ if(p[i].l > p[i].r)continue; if(p[i].l != p[i - 1].l || p[i].r != p[i - 1].r)tot++; w[tot] = min(w[tot] + 1, p[i].r - p[i].l + 1); L[tot] = p[i].l, R[tot] = p[i].r; } int j = 1; for(int i = 1; i <= n; ++i){ f[i] = f[i - 1]; while(j <= tot && R[j] == i){ f[i] = max(f[i], f[L[j] - 1] + w[j]); j++; } } printf("%d\n", n - f[n]); return 0; }
|
CF1016C Vasya And The Mushrooms
题目传送门
题目大意
给定一个$2n$的方格阵,经过一个格子获得的价值为$ai$进入时间,求从左上角出发遍历一次全部格子获得的价值最大值。
大体思路
很明显的一道dp题。。。
稍加思考便能发现能够遍历整个方阵的方案可能是长这样的
或者是长这样的
总之就是先是一个蛇形结构,然后接一个U形结构,很明显这两个部分都是可以预处理的。
设$f[i][0/1]$表示蛇形部分进行到第$i$列,第$0/1$行的权值
则
U形部分的计算方法:
$k$为U形部分开始的位置
设$g[i][0/1]$表示U形部分从0/1列, i行开始的权值,$sum[i][0/1]$表示本行距离结尾的权值和$nxt[i][0/1]$表示距离结尾的权值与列数乘积之和
则
1 2 3 4 5 6 7 8
| if(!(i & 1)){ g[i][1] = nxt[i][1] + (2 * (i - 1) - i) * sum[i][1]; g[i][1] += (2 * (i - 1) + 2 * n - i + 1) * sum[i][0] - nxt[i][0]; } else { g[i][0] = nxt[i][0] + (2 * (i - 1) - i) * sum[i][0]; g[i][0] += (2 * (i - 1) + 2 * n - i + 1) * sum[i][1] - nxt[i][1]; }
|
最后的答案就是每一种合法的蛇形与U形组合取$max$。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| #include <iostream> #include <cstdio> #include <cstring> #include <cctype> #include <algorithm> using namespace std; typedef long long ll; #define gc ch = getchar() int read(){ int ret = 0, f = 1; char gc; while(!isdigit(ch) && (ch ^ '-'))gc; if(ch == '-')f = -1, gc; while(isdigit(ch)){ret = (ret << 3) + (ret << 1) + (ch ^ '0'), gc;} return ret * f; } ll readll(){ ll ret = 0, f = 1; char gc; while(!isdigit(ch) && (ch ^ '-'))gc; if(ch == '-')f = -1, gc; while(isdigit(ch)){ret = (ret << 3) + (ret << 1) + (ch ^ '0'), gc;} return ret * f; } #undef gc const int maxn = 3 * 1e5 + 10; int n; ll ans; ll val[maxn][2]; ll sum[maxn][2], nxt[maxn][2]; ll f[maxn][2], g[maxn][2]; int main(){ #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif n = read(); for(int i = 1; i <= n; ++i){ val[i][0] = readll(); } for(int i = 1; i <= n; ++i){ val[i][1] = readll(); } for(int i = n; i >= 1; --i){ sum[i][0] = sum[i + 1][0] + val[i][0]; sum[i][1] = sum[i + 1][1] + val[i][1]; nxt[i][0] = nxt[i + 1][0] + val[i][0] * i; nxt[i][1] = nxt[i + 1][1] + val[i][1] * i; } for(int i = 1; i <= n; ++i){ if(i & 1){ f[i][0] = f[i - 1][0] + (2 * (i - 1)) * val[i][0]; f[i][1] = f[i][0] + (2 * (i - 1) + 1) * val[i][1]; } else{ f[i][1] = f[i - 1][1] + (2 * (i - 1)) * val[i][1]; f[i][0] = f[i][1] + (2 * (i - 1) + 1) * val[i][0]; } } for(int i = 1; i <= n; ++i){ if(!(i & 1)){ g[i][1] = nxt[i][1] + (2 * (i - 1) - i) * sum[i][1]; g[i][1] += (2 * (i - 1) + 2 * n - i + 1) * sum[i][0] - nxt[i][0]; } else { g[i][0] = nxt[i][0] + (2 * (i - 1) - i) * sum[i][0]; g[i][0] += (2 * (i - 1) + 2 * n - i + 1) * sum[i][1] - nxt[i][1]; } } for(int i = 0; i <= n; ++i){ if(i & 1){ ans = max(ans, f[i][1] + g[i + 1][1]); } else { ans = max(ans, f[i][0] + g[i + 1][0]); } } cout<<ans<<endl; return 0; }
|
CF467C George and Job
题目传送门
题目大意
给出一组有$n$个整数的数列$p_1,p_2,…,p_n$,你需要挑出$k$组长度为$m$的数,要求这些数互不重叠,即:$[l_1,r_1],[l_2,r_2],…,l_k,r_k$ 且使选出的数和值最大
大体思路
直接依照题意dp即可。
设$f[i][j]$为当前在第$i$位,选了$j$个区间时的最大值,$s[i]$为以$i$为起始点的长度为$m$的权值之和
转移为:$f[i][j]=max(f[i-1][j],f[i-m][j-1]+s[i])$
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cctype> using namespace std; const int maxn = 5010; #define gc ch = getchar() int read(){ int ret = 0, f = 1;char gc; while(!isdigit(ch) && (ch ^ '-'))gc; if(!(ch ^'-'))f = -1, gc; while(isdigit(ch)){ret = (ret << 3) + (ret << 1) + (ch ^ '0'), gc;} return ret * f; } long long readll(){ long long ret = 0, f = 1;char gc; while(!isdigit(ch) && (ch ^ '-'))gc; if(!(ch ^'-'))f = -1, gc; while(isdigit(ch)){ret = (ret << 3) + (ret << 1) + (ch ^ '0'), gc;} return ret * f; } #undef gc int n, m, k; long long p[maxn]; long long f[maxn][maxn]; long long sum[maxn]; long long s[maxn]; int main(){ #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif n = read(), m = read(), k = read(); for(int i = 1; i <= n; ++i){ p[i] = readll(); sum[i] = sum[i - 1] + p[i]; } for(int i = m; i <= n; ++i){ s[i] = sum[i] - sum[i - m]; } for(int i = m; i <= n; ++i){ for(int j = 1; j <= k && j * m <= i; ++j){ f[i][j] = f[i - 1][j]; f[i][j] = max(f[i][j], f[i - m][j - 1] + s[i]); } } cout<<f[n][k]<<endl; return 0; }
|