提高组专题 dp4
A [PA2021] Od deski do deski
DP 挺显然的,但我推错了……。
#include<bits/stdc++.h> using namespace std; constexpr int M = 1e9 + 7; #define ll long long int dp[3010][3010][2], n, m, ans; int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin>>n>>m; dp[0][0][0] = dp[0][0][1] = 1; for(int i=0; i<n; ++i) for(int j=0; j<=min(n, m); ++j){ dp[i+1][j][1] = ((ll)dp[i+1][j][1] + (ll)dp[i][j][1] * j % M + (ll)dp[i][j][0] * j % M) % M; dp[i+1][j+1][0] = ((ll)dp[i+1][j+1][0] + (ll)dp[i][j][1] * (m - j) % M) % M; dp[i+1][j][0] = ((ll)dp[i+1][j][0] + (ll)dp[i][j][0] * (m-j) % M) % M; } for(int i=1; i<=n; ++i) ans = ((ll)ans + (ll)dp[n][i][1]) % M; return cout<<ans, 0; }
B [TJOI2019] 甲苯先生的字符串
DP是显然的,矩乘加速也是显然的。
#include<bits/stdc++.h> using namespace std; #define f(i) for(int i=0; i<26; ++i) #define ll long long constexpr int M = 1e9 + 7; ll n; int sum; string s; struct Matrix{ int m[26][26]; }trl, ans; Matrix operator * (const Matrix a, const Matrix b){ Matrix c; memset(c.m, 0, sizeof(c.m)); f(i) f(j) f(k) c.m[i][j] = ((ll)c.m[i][j] + (ll)a.m[i][k] * b.m[k][j] % M) % M; return c; } int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin>>n>>s; int len = s.size(); f(i) f(j) trl.m[i][j] = 1; for(int i=1; i<len; ++i) trl.m[s[i-1] - 'a'][s[i] - 'a'] = 0; f(i) ans.m[0][i] = 1; --n; while(n){ if(n & 1) ans = ans * trl; trl = trl * trl; n >>= 1; } f(i) sum = ((ll)sum + (ll)ans.m[0][i]) % M; return cout<<sum, 0; }
C [ABC213G] Connectivity 2
算是比较经典的图上容斥了吧。上次模拟赛还遇到过。令 表示点集为 的图的个数, 表示点集为 的联通子图个数。答案可以是一部分图和一部分非连通图构成,所以答案是 的一个形式的加和。考虑如何递推求解 。因为 表示图的个数,因此我们只关心这些点内部的边的选择情况如何,每个边都有选和不选两种情况,于是方案数为 。对于 ,可以用全部情况减去所有非法情况组成,非法情况的不联通图一定是一部分连通图和一部分非连通图构成,所以在 钦定一点 使得枚举的连通图 有 ,再乘上相应的 在 的补即可。
#include<bits/stdc++.h> using namespace std; #define ll long long constexpr int M = 998244353; int n, m, p[205], flag[18], tot, g[1<<18], f[1<<18], ans[18]; int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); p[0] = 1; for(int i=1; i<=200; ++i) p[i] = (ll)p[i-1] * 2 % M; cin>>n>>m; tot = (1 << n) - 1; for(int i=1, u, v; i<=m; ++i){ cin>>u>>v; flag[u] |= 1 << (v-1); flag[v] |= 1 << (u-1); } for(int s=0; s<=tot; ++s){ for(int i=1; i<=n; ++i) if(!(s & (1<<(i-1)))){ int tmp = s | (1 << (i-1)); g[tmp] = g[s] + __builtin_popcount(flag[i] & s); } } for(int s=0; s<=tot; ++s) g[s] = p[g[s]]; for(int s=0; s<=tot; ++s){ int i = s & (-s), res = 0; for(int tmp=(s-1)&s; tmp; tmp=(tmp-1)&s) if(tmp & i) res = ((ll)res + (ll)f[tmp] * g[tmp^s] % M) % M; f[s] = (((ll)g[s] - (ll)res) % M + M) % M; } for(int s=0; s<=tot; ++s) if(s & 1){ int res = (ll)f[s] * g[tot^s] % M; for(int i=1; i<=n; ++i) if(s & (1<<(i-1))){ ans[i] = ((ll)ans[i] + (ll)res) % M; } } for(int i=2; i<=n; ++i) cout<<ans[i]<<'\n'; return 0; }
D 某位歌姬的故事
区间问题肯定要考虑重叠,对于重叠来说有个很显然的性质就是:如果一段区间被很多个限制覆盖,那么只考虑限制中最小的那个。对于计算方案数来说,只需要很简单的容斥即可:。然后DP就行了。DP是 stj 的。
#include<bits/stdc++.h> using namespace std; constexpr int B = 1 << 13; char buf[B], *p1 = buf, *p2 = buf, obuf[B], *O = obuf; #define gt() (p1==p2 && (p2=(p1=buf)+fread(buf, 1, B, stdin), p1==p2) ? EOF : *p1++) template <typename T> inline void rd(T &x){ x = 0; int f = 0; char ch = gt(); for(; !isdigit(ch); ch = gt()) f ^= ch == '-'; for(; isdigit(ch); ch = gt()) x = (x<<1) + (x<<3) + (ch^48); x = f ? -x : x; } #define pt(ch) (O-obuf==B && (fwrite(obuf, 1, B, stdout), O=obuf), *O++ = (ch)) template <typename T> inline void wt(T x){ if(x < 0) pt('-'), x = -x; if(x > 9) wt(x / 10); pt(x % 10 ^ 48); } #define fw fwrite(obuf, 1, O - obuf, stdout) #define ll long long constexpr int M = 998244353; int T, n, q, A, ls[1005], cnt, nd[1005], lim[1005], h[505], Long[1005], ans, pos[1005], mn[1005], dp[1005][1005]; struct Que{ int l, r, m; }Q[505]; struct Seg{ int l, r, m; }S[505]; inline int qpow(int a, int k){ int ans = 1; while(k){ if(k & 1) ans = (ll)ans * a % M; a = (ll)a * a % M; k >>= 1; } return ans; } inline void work(){ ans = 1, cnt = 0; rd(n), rd(q), rd(A); for(int i=1; i<=q; ++i){ rd(Q[i].l), rd(Q[i].r), rd(Q[i].m); ls[++cnt] = Q[i].l-1; ls[++cnt] = Q[i].r; h[i] = Q[i].m; } ls[++cnt] = 0, ls[++cnt] = n; sort(ls+1, ls+1+cnt); int len = unique(ls+1, ls+1+cnt) - ls - 1; for(int i=1; i<=len; ++i) lim[i] = A + 1; for(int i=1; i<=q; ++i){ Q[i].l = lower_bound(ls+1, ls+1+len, Q[i].l-1) - ls; Q[i].r = lower_bound(ls+1, ls+1+len, Q[i].r) - ls; for(int j=Q[i].l; j<Q[i].r; ++j) lim[j] = min(lim[j], Q[i].m); } for(int i=1; i<=q; ++i){ int mx = 0; for(int j=Q[i].l; j<Q[i].r; ++j) mx = max(mx, lim[j]); if(mx < Q[i].m) return pt('0'), pt('\n'), void(); } sort(h+1, h+1+q); int num = unique(h+1, h+1+q) - h - 1; for(int i=1; i<=num; ++i){ memset(mn, 0, sizeof(mn)); int tmp = 0; for(int j=1; j<len; ++j){ if(lim[j] == h[i]){ Long[++tmp] = ls[j+1] - ls[j]; pos[tmp] = j + 1; } } for(int j=1; j<=q; ++j){ if(Q[j].m == h[i]){ int po = upper_bound(pos+1, pos+1+tmp, Q[j].r) - pos - 1; mn[po] = max(mn[po], Q[j].l + 1); } } for(int j=0; j<=tmp; ++j) for(int k=0; k<=tmp; ++k) dp[j][k] = 0; dp[0][0] = 1; for(int r=1; r<=tmp; ++r){ int tot = 0, sum = qpow(h[i]-1, Long[r]); for(int s=0; s<r; ++s){ if(!dp[r-1][s]) continue; tot = ((ll)tot + (ll)dp[r-1][s]) % M; if(mn[r] > pos[s]) continue; dp[r][s] = ((ll)dp[r][s] + (ll)dp[r-1][s] * sum % M) % M; } dp[r][r] = (ll)(((ll)qpow(h[i], Long[r]) - (ll)sum) % M + M) % M * tot % M; } int res = 0; for(int j=0; j<=tmp; ++j) res = ((ll)res + (ll)dp[tmp][j]) % M; ans = (ll)ans * res % M; } for(int i=1; i<len; ++i) if(lim[i] == A+1) ans = (ll)ans * qpow(A, ls[i+1]-ls[i]) % M; wt(ans); pt('\n'); } int main(){ rd(T); while(T--) work(); return fw, 0; }
E [ABC134F] Permutation Oddness
很神仙的 DP 思路。不是入能想出来的。考虑什么时候选的数对怪异度有贡献,以及贡献多少,因为是带绝对值的,所以当大的数 对小的位置的时候,贡献的怪异度为 ,当小的数对大的位置的时候,贡献的怪异度为 。由此令 表示当前选到第 个数/位置,前面有 个数匹配到比自己大的位置,当前的怪异度为 。那么有:
转移即可。
#include<bits/stdc++.h> constexpr int M = 1e9 + 7; #define ll long long int n, k, f[55][55][55*55*2]; int main(){ scanf("%d%d", &n, &k); if(k == 0) return printf("1"), 0; f[0][0][n*n] = 1; int tot = n*n; for(int i=1; i<=n; ++i){ for(int j=0; j<=n; ++j){ for(int m=-tot; m<=tot; ++m){ f[i][j][m + tot] = ((ll)f[i][j][m + tot] + (ll)f[i-1][j][m + tot]) % M; if(m + 2*i <= tot && j) f[i][j][m + tot] = ((ll)f[i][j][m + tot] + (ll)f[i-1][j-1][m + 2*i + tot]) % M; if(m - 2*i + tot >= 0) f[i][j][m + tot] = ((ll)f[i][j][m + tot] + (ll)f[i-1][j+1][m - 2*i + tot] * (j+1) % M * (j+1) % M) % M; f[i][j][m + tot] = ((ll)f[i][j][m + tot] + (ll)f[i-1][j][m + tot] * j % M * 2 % M) % M; } } } return printf("%d", f[n][0][k + tot]), 0; }
F [JSOI2018] 潜入行动
属于一眼出 背包DP 了吧~状态也很好弄。令 表示节点 子树内有 个监听器的状态,定义如下:
那么状态 的转移很好搞, 的儿子都必须不放且被看(),那么有:
对于状态 ,能够提供贡献的儿子状态仅为 ,并且儿子里至少有一个有监听器()。看样子很难弄,不过因为背包转移的时候是一个儿子一个儿子地合并起来的,所以维护辅助背包 表示已经合并的 个儿子里是否有状态 ,那么有:
状态 的转移也很好搞, 的儿子都必须不放,所以可以是 两种,直接大力背包即可:
状态 的转移和状态 差不多,儿子的状态四种都是可以的,但是儿子里必须有一个是 或 。还是用一个辅助背包 表示已经合并的儿子里有没有 或 ,那么有:
答案即为 。上述仅供参考,请以代码为准。
#include<bits/stdc++.h> using namespace std; constexpr int B = 1 << 20; char buf[B], *p1 = buf, *p2 = buf, obuf[B], *O = obuf; #define gt() (p1==p2 && (p2=(p1=buf)+fread(buf, 1, B, stdin), p1==p2) ? EOF : *p1++) template <typename T> inline void rd(T &x){ x = 0; int f = 0; char ch = gt(); for(; !isdigit(ch); ch = gt()) f ^= ch == '-'; for(; isdigit(ch); ch = gt()) x = (x<<1) + (x<<3) + (ch^48); x = f ? -x : x; } #define pt(ch) (O-obuf==B && (fwrite(obuf, 1, B, stdout), O=obuf), *O++ = (ch)) template <typename T> inline void wt(T x){ if(x < 0) pt('-'), x = -x; if(x > 9) wt(x / 10); pt(x % 10 ^ 48); } #define fw fwrite(obuf, 1, O - obuf, stdout) #define ll long long constexpr int N = 1e5 + 5, M = 1e9 + 7; int n, k, f[N][4][105], g[N][2][105], siz[N], tmp[4][105]; vector<int> G[N]; inline int add(int a, int b){ return a + b > M ? a + b - M : a + b; } inline int mul(int a, int b){ return (ll)a * b % M; } inline void lets_go(int u, int fa){ siz[u] = 1; f[u][0][0] = f[u][2][1] = 1; for(int v : G[u]) if(v ^ fa){ lets_go(v, u); int mnu = min(siz[u], k); for(int i=0; i<4; ++i) for(int j=0; j<=mnu; ++j) tmp[i][j] = f[u][i][j], f[u][i][j] = 0; for(int z=mnu;z>=0; --z){ for(int j=0; j<=min(siz[v],k-z); ++j){ f[u][0][z+j] = add(f[u][0][z+j], mul(tmp[0][z], f[v][1][j])); f[u][1][z+j] = add(f[u][1][z+j], mul(tmp[0][z], f[v][3][j])); f[u][1][z+j] = add(f[u][1][z+j], mul(tmp[1][z], f[v][1][j])); f[u][1][z+j] = add(f[u][1][z+j], mul(tmp[1][z], f[v][3][j])); f[u][2][z+j] = add(f[u][2][z+j], mul(tmp[2][z], f[v][0][j])); f[u][2][z+j] = add(f[u][2][z+j], mul(tmp[2][z], f[v][1][j])); f[u][3][z+j] = add(f[u][3][z+j], mul(tmp[2][z], f[v][2][j])); f[u][3][z+j] = add(f[u][3][z+j], mul(tmp[2][z], f[v][3][j])); f[u][3][z+j] = add(f[u][3][z+j], mul(tmp[3][z], f[v][0][j])); f[u][3][z+j] = add(f[u][3][z+j], mul(tmp[3][z], f[v][1][j])); f[u][3][z+j] = add(f[u][3][z+j], mul(tmp[3][z], f[v][2][j])); f[u][3][z+j] = add(f[u][3][z+j], mul(tmp[3][z], f[v][3][j])); } } siz[u] += siz[v]; } } int main(){ rd(n), rd(k); for(int i=1, u, v; i<n; ++i){ rd(u), rd(v); G[u].emplace_back(v); G[v].emplace_back(u); } lets_go(1, 0); int ans = ((ll)f[1][1][k] + (ll)f[1][3][k]) % M; wt(ans); return fw, 0; }
G [[TJOI2018] 游园会]([P4590 TJOI2018] 游园会 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
dp of dp。名字挺高级。
对于这道题来说,我们需要构造一个字符串 使得:
-
长度为 ,且仅有
N
,O
,I
三种字符:这个限制很好满足啊,每次往后添一个字符即可。 -
不包含
NOI
子串:也很好满足啊,开个状态 0/1/2 表示当前匹配到了那一位,决定下一位要不要填I
即可。 -
与题目给的模式串 的最长公共子序列长度为 :这个限制是不好满足的。但是如果只求最长共同子序列是简单的:
想办法结合起来。可以发现的是,对于 每一位最多差 ,那么就可以差分一下,然后状压存到 dp状态里,然后每次添加一个字符的时候,就将旧的状态解包,然后更新新的状态并压到 dp里去。
#include<bits/stdc++.h> using namespace std; constexpr int N = 1e3 + 5, B = (1<<15) + 1, M = 1e9 + 7; int n, k, dp[2][B][3], tot, lst, now, f2[16], f1[16], ans[16]; string str; inline int add(initializer_list<int> Add){ int res = 0; for(int v : Add) res = res + v > M ? res + v - M : res + v; return res; } inline void dhsh(int* a, int s){ for(int i=0; i<k; ++i) a[i+1] = !!(s & (1<<i)); for(int i=1; i<=k; ++i) a[i] += a[i-1]; } inline int hsh(int *a){ int s = 0; for(int i=0; i<k; ++i) s |= (a[i+1]-a[i]) << i; return s; } inline void DP(int s, int typ2, char ch, int typ1){ dhsh(f1, s); for(int i=1; i<=k; ++i) f2[i] = max({f2[i-1], f1[i], f1[i-1] + (ch == str[i-1])}); int s2 = hsh(f2); dp[now][s2][typ2] = add({dp[now][s2][typ2], dp[lst][s][typ1]}); } int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin>>n>>k>>str; tot = (1 << k) - 1; dp[0][0][0] = 1; for(int i=0; i<n; ++i){ lst = i & 1, now = (~i) & 1; for(int j=0; j<=tot; ++j) dp[now][j][0] = dp[now][j][1] = dp[now][j][2] = 0; for(int j=0; j<=tot; ++j){ if(dp[lst][j][0]){ DP(j, 1, 'N', 0); DP(j, 0, 'O', 0); DP(j, 0, 'I', 0); } if(dp[lst][j][1]){ DP(j, 1, 'N', 1); DP(j, 2, 'O', 1); DP(j, 0, 'I', 1); } if(dp[lst][j][2]){ DP(j, 1, 'N', 2); DP(j, 0, 'O', 2); } } } for(int i=0; i<=tot; ++i){ int cnt = __builtin_popcount(i); ans[cnt] = add({ans[cnt], dp[now][i][0], dp[now][i][1], dp[now][i][2]}); } for(int i=0; i<=k; ++i) cout<<ans[i]<<'\n'; return 0; }
本文作者:XiaoLe_MC
本文链接:https://www.cnblogs.com/xiaolemc/p/18494321
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步