DP做题记录(三)(2025.4.5 - 2025.4.19)
orange_new · · 算法·理论
P11626 [迷宫寻路 Round 3] 七连击
对于一段区间的划分,很容易想到 DP。
我们设
我们再考虑划分出的这一段对答案的贡献。如果们固定了一段区间
观察可以发现,
完整代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 9, LOGN = 19, MOD = 998244353;
int st[N][LOGN], LOG2[N], a[N], g[N][9], f[N][9], sumg[N][9], sumf[N][9], n;
void build(){
LOG2[1] = 0;
for(int i = 2; i <= n; i++)
LOG2[i] = LOG2[i >> 1] + 1;
for(int i = 1; i <= n; i++)
st[i][0] = a[i];
for(int j = 1; j <= LOG2[n]; j++)
for(int i = 1; i + (1 << j) - 1 <= n; i++)
st[i][j] = __gcd(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
int ask(int l, int r){
int k = LOG2[r - l + 1];
return __gcd(st[l][k], st[r - (1 << k) + 1][k]);
}
int find(int l, int r){
int val = ask(l, r), res = l, tmp = r;
while(l <= r){
int mid = (l + r) >> 1;
if(ask(mid, tmp) == val){
l = mid + 1;
res = mid;
} else
r = mid - 1;
}
return res;
}
signed main(){
scanf("%lld", &n);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
build();
g[0][0] = sumg[0][0] = 1;
for(int i = 1; i <= n; i++){
g[i][0] = 1, sumg[i][0] = 1;
for(int j = 1; j <= 7; j++){
g[i][j] = sumg[i - 1][j - 1];
sumg[i][j] = (sumg[i - 1][j] + g[i][j]) % MOD;
}
}
for(int i = 1; i <= n; i++){
f[i][1] = ask(1, i);
sumf[i][1] = (sumf[i - 1][1] + f[i][1]) % MOD;
for(int l = 1, r; l <= i; l = r + 1){
r = find(l, i);
for(int j = 2; j <= 7; j++){
if(!f[i][j]) f[i][j] = sumf[i - 1][j - 1];//wa *2
f[i][j] = (f[i][j] + ask(l, i) * (sumg[r - 1][j - 1] - sumg[l - 2][j - 1] + MOD) % MOD) % MOD;
sumf[i][j] = (sumf[i - 1][j] + f[i][j]) % MOD;
}
}
}
printf("%lld", sumf[n][7]);
return 0;
}
P11173 「CMOI R1」We Want To Run / Nilpotent
套用非常经典的线性代数结论,一张图的邻接矩阵的
注意到这张图中左右边权非负,那么在矩乘时,如果两个位置都不为
那么现在,
由于我们要统计方案数,因此我们考虑枚举贡献。如果这张图中最长链上的点数为
我们设
其次,这一层的点必须至少与上一层的
最后,这一层的点可以与之前层的点随便连边,之前的点一共有
我们其实可以发现,和我一样),只是在最后求答案时要乘以
现在我们可以改变 DP 的函数了,设
完整代码:
#include <bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int N = 6e2 + 9, MOD = 202407031;
int dp[N][N][2], binom[N][N], pwr[N * N], po[N][N], n, a, ans;
int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-')
f = -1;
ch = getchar();
}
while(isdigit(ch)){
x = ((x << 1) + (x << 3) + (ch ^ 48));
ch = getchar();
}
return x * f;
}
void write(int x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
signed main(){
n = read();
a = read();
a %= MOD;
pwr[0] = 1;
for(int i = 1; i <= n * n; i++)
pwr[i] = pwr[i - 1] * a % MOD;
for(int i = 1; i <= n; i++){
po[i][0] = 1;
for(int j = 1; j <= n; j++)
po[i][j] = po[i][j - 1] * (pwr[i] - 1 + MOD) % MOD;
}
binom[0][0] = 1;
for(int i = 1; i <= n; i++){
binom[i][0] = 1;
for(int j = 1; j <= i; j++)
binom[i][j] = (binom[i - 1][j] + binom[i - 1][j - 1]) % MOD;
}
for(int i = 1; i <= n; i++)
dp[i][i][1] = dp[i][i][0] = 1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(dp[i][j][0] != 0){
for(int p = 1; p <= n - i; p++){
dp[i + p][p][0] = (dp[i + p][p][0] + dp[i][j][0] * binom[i + p][p] % MOD * po[j][p] % MOD * pwr[p * (i - j)] % MOD) % MOD;
dp[i + p][p][1] = (dp[i + p][p][1] + dp[i][j][0] * binom[i + p][p] % MOD * po[j][p] % MOD * pwr[p * (i - j)] % MOD) % MOD;
}
}
if(dp[i][j][1] != 0){
for(int p = 1; p <= n - i; p++)
dp[i + p][p][1] = (dp[i + p][p][1] + dp[i][j][1] * binom[i + p][p] % MOD * po[j][p] % MOD * pwr[p * (i - j)] % MOD) % MOD;
}
}
}
for(int i = 1; i <= n; i++)
ans = (ans + dp[n][i][1]) % MOD;
write(ans);
return 0;
}
P11030 『DABOI Round 1』Blessings Repeated
由于
那么问题就转变成了求出
其中初值为
现在就比较好做了,我们将
完整代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e3 + 9, M = 19, MOD = 998244353;
int k, dp[N], tim[M][M], inv[M], fac[M], n, m, ans;
char s[N], t[M];
vector <int> vec;
int qpow(int a, int b){
int res = 1;
while(b > 0){
if(b & 1)
res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
int binom(int a, int b){
if(b > a)
return 0;
__int128 res = 1;
for(int i = a; i >= a - b + 1; i--)
res = res * i % MOD;
return res * inv[b] % MOD;
}
void dfs(int now){
if(now == 0){
int l = 1, r, tmp = 1;
for(int i = 0; i < (int)vec.size(); i++){
r = l + vec[i] - 1;
tmp = tmp * tim[l][r] % MOD;
l = r + 1;
}
ans = (ans + tmp * binom(k, vec.size()) % MOD) % MOD;
return;
}
for(int i = 1; i <= now; i++){
vec.push_back(i);
dfs(now - i);
vec.pop_back();
}
}
signed main(){
scanf("%lld", &k);
scanf("%s", s + 1);
scanf("%s", t + 1);
n = strlen(s + 1);
m = strlen(t + 1);
fac[0] = 1;
for(int i = 1; i <= m; i++)
fac[i] = fac[i - 1] * i % MOD;
inv[m] = qpow(fac[m], MOD - 2);
for(int i = m - 1; i >= 0; i--)
inv[i] = inv[i + 1] * (i + 1) % MOD;
for(int l = 1; l <= m; l++){
for(int r = l; r <= m; r++){
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for(int i = 1; i <= n; i++)
for(int j = r; j >= l; j--)
if(t[j] == s[i])
dp[j - l + 1] = (dp[j - l + 1] + dp[j - l]) % MOD;
tim[l][r] = dp[r - l + 1];
}
}
dfs(m);
printf("%lld", ans);
return 0;
}
P10879 「KDOI-07」对树链剖分的爱
我们考虑通常如何把一条树链
推出了性质后,我们从简单情况一步一步分析。假设只有一组修改
我们考虑期望的线性性,两个独立事件的期望和,等于这两个事件至少发生一件的期望,那么由于这几个加法操作相互独立,它们的期望可以直接加在一起,于是改变
我们考虑转移方程,对于所有
完整代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e3 + 9, MOD = 998244353;
int f[N][N], s1[N][N], s2[N][N], inv[N], l[N], r[N], ans[N], n, m;
void init(){
inv[1] = 1;
for(int i = 2; i < N; i++)
inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
}
signed main(){
init();
scanf("%lld", &n);
for(int i = 2; i <= n; i++)
scanf("%lld%lld", &l[i], &r[i]);
scanf("%lld", &m);
for(int i = 1; i <= m; i++){
int u, v, w;
scanf("%lld%lld%lld", &u, &v, &w);
if(u < v)
swap(u, v);
f[u][v] = (f[u][v] + w) % MOD;
}
for(int i = n; i >= 1; i--){
for(int j = i - 1; j >= 1; j--){
s1[i][j] = (s1[i][j] + s1[i + 1][j]) % MOD;
s2[i][j] = (s2[i][j] + s2[i][j + 1]) % MOD;
f[i][j] = (f[i][j] + s1[i][j] + s2[i][j]) % MOD;
ans[i] = (ans[i] + f[i][j]) % MOD;
int num = f[i][j] * inv[r[i] - l[i] + 1] % MOD;
if(j < l[i]){
s1[r[i]][j] = (s1[r[i]][j] + num) % MOD;
s1[l[i] - 1][j] = ((s1[l[i] - 1][j] - num) % MOD + MOD) % MOD;
} else if(j > r[i]){
s2[j][r[i]] = (s2[j][r[i]] + num) % MOD;
s2[j][l[i] - 1] = ((s2[j][l[i] - 1] - num) % MOD + MOD) % MOD;
} else {
s2[j][j] = (s2[j][j] + num) % MOD;
s1[r[i]][j] = (s1[r[i]][j] + num) % MOD;
s2[j][l[i] - 1] = ((s2[j][l[i] - 1] - num) % MOD + MOD) % MOD;
s1[j][j] = ((s1[j][j] - num) % MOD + MOD) % MOD;
}
}
}
for(int i = 2; i <= n; i++)
printf("%lld ", ans[i]);
return 0;
}
P10868 [HBCPC2024] Points on the Number Axis B
我们先从简单的情况考虑,如果只有两个点
-
先合并
x_1, x_2 ,变成\displaystyle\frac{x_1}{2} + \frac{x_2}{2} ,再合并x_3 ,变成\displaystyle\frac{x_1}{4} + \frac{x_2}{4} + \frac{x_3}{2} ; -
先合并
x_2, x_3 ,变成\displaystyle\frac{x_2}{2} + \frac{x_3}{2} ,再合并x_1 ,变成\displaystyle\frac{x_1}{2} + \frac{x_2}{4} + \frac{x_3}{4} 。
因此期望就是
其实可以发现,最后的式子一定可以表示成
设
现在来考虑将枚举到的这个点合并进来,此时就要乘以系数
我们现在考虑优化,首先一个优化方式就是将期望改写成总和除以总方案数,那么就可以将转移方程改写为
现在就到了要充分发扬类人智慧的时候了,考虑
完整代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 9, MOD = 998244353;
int x[N], fac[N], inv[N], des[N], c, n, ans;
int qpow(int a, int b){
int res = 1;
while(b > 0){
if(b & 1)
res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
signed main(){
scanf("%lld", &n);
for(int i = 1; i <= n; i++)
scanf("%lld", &x[i]);
c = qpow(2, MOD - 2);
fac[0] = 1;
for(int i = 1; i <= n; i++)
fac[i] = fac[i - 1] * i % MOD;
inv[n] = qpow(fac[n], MOD - 2);
for(int i = n - 1; i >= 0; i--)
inv[i] = inv[i + 1] * (i + 1) % MOD;
des[0] = 1;
for(int i = 1; i <= n; i++)
des[i] = (i - c + MOD) % MOD * des[i - 1] % MOD;
for(int i = 1; i <= n; i++)
ans = (ans + x[i] * des[i - 1] % MOD * des[n - i] % MOD * inv[i - 1] % MOD * inv[n - i] % MOD) % MOD;
printf("%lld", ans);
return 0;
}
P9428 [蓝桥杯 2023 国 B] 逃跑
这道题代码难度只有红,但思维难度直接把这道题提升到了绿。
我们设
显然,如果一个星球
那么如果
完整代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 9;
struct Edge{
int v, nex;
} e[N << 1], e2[N];
int head[N], ecnt;
void addEdge(int u, int v){
e[++ecnt] = Edge{v, head[u]};
head[u] = ecnt;
}
int flag[N], n, m;
double dp[N], p, ans;
void dfs(int u, int fa, double pp){
if(flag[fa])
pp *= p;
if(u != 1){
if(flag[fa])
dp[u] = dp[fa] + 1;
else
dp[u] = dp[fa] + pp;
}
ans += dp[u];
for(int i = head[u]; i; i = e[i].nex){
int v = e[i].v;
if(v == fa)
continue;
dfs(v, u, pp);
}
pp /= p;
}
int main(){
scanf("%d%d%lf", &n, &m, &p);
for(int i = 1; i < n; i++){
int u, v;
scanf("%d%d", &u, &v);
addEdge(u, v);
addEdge(v, u);
}
for(int i = 1; i <= m; i++){
int u;
scanf("%d", &u);
flag[u] = true;
}
dfs(1, 0, 1);
printf("%.2lf", ans / n);
return 0;
}
P5011 水の造题
其实这道题目的样例解释已经把如何统计答案讲解的差不多了。
我们考虑 DP,设
我们枚举前一个字符
下面来考虑威力值的加成,那么前一个填的数字必须是
完整代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int K = 1e6 + 9, MOD = 19491001;
int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-')
f = -1;
ch = getchar();
}
while(isdigit(ch)){
x = ((x << 1) + (x << 3) + (ch ^ 48)) % MOD;
ch = getchar();
}
return x * f;
}
int qpow(int a, int b){
int res = 1;
while(b > 0){
if(b & 1)
res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
int n, k, sum, a[K];
signed main(){
n = read();
k = read();
for(int i = 1; i <= k; i++){
scanf("%lld", &a[i]);
sum = (sum + a[i]) % MOD;
}
printf("%lld", sum * (n * (k + 2) % MOD - 2 + MOD) % MOD * qpow(k * k % MOD, MOD - 2) % MOD);
return 0;
}