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
算是比較經典的圖上容斥了吧。上次模擬賽還遇到過。令 \(g(S)\) 表示點集為 \(S\) 的圖的個數,\(f(S)\) 表示點集為 \(S\) 的聯通子圖個數。答案可以是一部分圖和一部分非連通圖構成,所以答案是 \(f * g\) 的一個形式的加和。考慮如何遞推求解 \(f,g\)。因為 \(g(S)\) 表示圖的個數,因此我們只關心這些點內部的邊的選擇情況如何,每個邊都有選和不選兩種情況,於是方案數為 \(2^{cnt}\)。對於 \(f(S)\),可以用全部情況減去所有非法情況組成,非法情況的不聯通圖一定是一部分連通圖和一部分非連通圖構成,所以在 \(S\) 欽定一點 \(u\) 使得列舉的連通圖 \(f(V)\) 有 \(u\in V\),再乘上相應的 \(V\) 在 \(S\) 的補即可。
#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 某位歌姬的故事
區間問題肯定要考慮重疊,對於重疊來說有個很顯然的性質就是:如果一段區間被很多個限制覆蓋,那麼只考慮限制中最小的那個。對於計算方案數來說,只需要很簡單的容斥即可:\(m^{len}-(m-1)^{len}\)。然後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 思路。不是入能想出來的。考慮什麼時候選的數對怪異度有貢獻,以及貢獻多少,因為是帶絕對值的,所以當大的數 \(i\) 對小的位置的時候,貢獻的怪異度為 \(+i\),當小的數對大的位置的時候,貢獻的怪異度為 \(-i\)。由此令 \(f_{i,j,k}\) 表示當前選到第 \(i\) 個數/位置,前面有 \(j\) 個數匹配到比自己大的位置,當前的怪異度為 \(k\)。那麼有:
\(\mathcal{O}(N^4)\) 轉移即可。
#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 了吧~狀態也很好弄。令 \(f_{u,k,0/1/2/3}\) 表示節點 \(u\) 子樹內有 \(k\) 個監聽器的狀態,定義如下:
那麼狀態 \(0\) 的轉移很好搞,\(u\) 的兒子都必須不放且被看(\(1\)),那麼有:
對於狀態 \(1\),能夠提供貢獻的兒子狀態僅為 \(1,3\),並且兒子裡至少有一個有監聽器(\(3\))。看樣子很難弄,不過因為揹包轉移的時候是一個兒子一個兒子地合併起來的,所以維護輔助揹包 \(g_{z,0/1,k}\) 表示已經合併的 \(z\) 個兒子裡是否有狀態 \(3\),那麼有:
狀態 \(2\) 的轉移也很好搞,\(u\) 的兒子都必須不放,所以可以是 \(0,1\) 兩種,直接大力揹包即可:
狀態 \(3\) 的轉移和狀態 \(1\) 差不多,兒子的狀態四種都是可以的,但是兒子裡必須有一個是 \(2\) 或 \(3\)。還是用一個輔助揹包 \(g_{z,0/1,k}\) 表示已經合併的兒子裡有沒有 \(2\) 或 \(3\),那麼有:
答案即為 \(f_{root,k,1}+f_{foot,k,3}\)。上述僅供參考,請以程式碼為準。
#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。名字挺高階。
對於這道題來說,我們需要構造一個字串 \(S\) 使得:
-
長度為 \(n\),且僅有
N
,O
,I
三種字元:這個限制很好滿足啊,每次往後添一個字元即可。 -
不包含
NOI
子串:也很好滿足啊,開個狀態 0/1/2 表示當前匹配到了那一位,決定下一位要不要填I
即可。 -
與題目給的模式串 \(str\) 的最長公共子序列長度為 \(0\sim k\):這個限制是不好滿足的。但是如果只求最長共同子序列是簡單的:
\[dp_{i,j}=max(dp_{i-1,j},dp_{i,j-1},dp_{i-1,j-1}+(A_i==b_j)) \]想辦法結合起來。可以發現的是,對於 \(dp_{1\sim i,j}\) 每一位最多差 \(1\),那麼就可以差分一下,然後狀壓存到 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;
}