安吉D1-L
- 1安吉D19 T1
- 2安吉D19 T3
- 3安吉D20 模考12总结
- 4安吉D17 T3
- 5安吉D17 T4
- 6安吉D19 模考11总结
- 7安吉D15 A
- 8安吉D16 T4
- 9安吉D17 T1
- 10安吉D17 模考10总结
- 11安吉D16 T3
- 12安吉D16 模考9总结
- 13安吉D13 模考8总结
- 14安吉D13-T2
- 15安吉D13-T3
- 16安吉D12-T3
- 17安吉D11-B
- 18安吉D11-C
- 19安吉D11-D
- 20安吉D12 模考7总结
- 21安吉D12-T2
- 22安吉D11-A
- 23安吉D10 模考6总结
- 24安吉D10-T3
- 25安吉D10-T4
- 26安吉D8-E
- 27安吉D9-T4
- 28安吉D8-A
- 29安吉D8-B
- 30安吉D8-C
- 31安吉D8-D
- 32安吉D9 模考5总结
- 33安吉D9-T2
- 34安吉D9-T3
- 35安吉D6-T4
- 36安吉D4-G
- 37安吉D5-T2
- 38安吉D6 模考4总结
- 39安吉D6-T1
- 40安吉D6-T3
- 41安吉-开营测试 T4
- 42安吉D5 模考3总结
- 43安吉D5-T3
- 44安吉D4-A
- 45安吉D4-B
- 46安吉D4-C
- 47安吉D4:容斥原理
- 48安吉Day4-D
- 49安吉D3 模考2总结
- 50安吉D3-T1
- 51安吉D3-T2
- 52安吉D3-T4
- 53安吉D2 模考1总结
- 54安吉D2-T2
- 55安吉D1-G
- 56安吉D1-L本文
- 57安吉D2-T4
原题呈现
P15862 回去!
题目描述
有一个 个点 条边的有向图 ,满足每个点的出度为 .每个点有一个小写字母作为权值.
现在 DZ1000 准备在 上游走,他打算用一个字符串 和一个栈 来记录他的行程.初始时栈 为空.
每一步他都有两种可能的行动选择:
- 「前进?」:DZ1000 有 的概率沿着当前点的出边移动,并将他到达的节点放进栈 中.
- 「回去!」:DZ1000 有 的概率弹出栈 顶部的节点,然后回到当前位于栈 顶部的节点.(若行动前栈 为空则他不会这么做且一定会前进,若行动前栈 大小为 则回到起点.)
每走完一步,DZ1000 都会将当前他所在节点的权值接在字符串 的后面.
现在 DZ1000 将从 上等概率随机选择一个起始节点,开始走 步,他想让你求出走完 步后字符串 的前 位和后 位相等的概率.答案乘上 后对 取模.
注意起始节点的权值不等同于字符串 的第一位.
输入格式
输入共 行.
第一行,三个正整数 ,分别表示 的点数、行动的步数和往前进的概率( 原本是一个介于 和 之间的有理数,现给出其对 取模后的结果).
第二行,由小写字母组成的长度为 的字符串,第 个字母 表示 号点的权值.
第三行, 个正整数 ,第 个正整数 表示 号点出边指向的点.
输出格式
输出共 行,一个正整数表示走完 步后字符串 的前 位和后 位相等的概率乘 的结果,对 取模.
输入输出样例 #1
输入 #1
3 1 499122177aba2 3 1输出 #1
1输入输出样例 #2
输入 #2
10 10 882315098nqqmlmnnon6 3 4 1 4 4 9 4 10 7输出 #2
106458929说明/提示
样例 解释
在模 下是 ,故原始的 .
有如下路径:
- ,概率为 ,字符串为
ba,不符合条件. - ,概率为 ,字符串为
ba,不符合条件. - ,概率为 ,字符串为
aa,符合条件. - ,概率为 ,字符串为
ab,不符合条件. - ,概率为 ,字符串为
ab,不符合条件. - ,概率为 ,字符串为
aa,符合条件.
故有 的概率符合条件,乘 的结果为 ,故输出 .
数据规模
对于 的数据,满足 .
| 子任务编号 | 特殊性质 | 分值 | ||
|---|---|---|---|---|
| 无 | ||||
| 无特殊限制 | ^ | ^ | ||
| ^ | ^ | |||
| ^ | 无特殊限制 | A | ||
| ^ | ^ | B | ||
| ^ | ^ | C | ||
| ^ | ^ | 无 |
特殊性质 A:所有 相同.
特殊性质 B:对于任意 ,有 .
特殊性质 C:对于任意 ,有 ,且 .
思路
由于每一个节点出度相同,考虑枚举起点.如果枚举了起点,一直往前走的整条路径就固定了,可以将其预处理出来.将图上的问题转化为链上的问题.
接下来,问题转化为:有一个人在一条链上跳,可以向前跳,也可以向后跳,但是不能跳出界.问如何跳才能满足得到的字符串前 个和后 个相等.
由于想要前后两半相等,可以想象有两个人,一个人跳前半段,一个人跳后半段,枚举权值与第一个点 相等的点 ,两人从 和 开始跳,每跳一步都保证落点权值相等,且最终第一个人的最终落点和第二个人的起点相邻的可能.
为了计算可能性,我们定义 dp[i][x][y] 表示两个人都走了 i 步,第一个人走到 x,第二个人走到 y 这种情况的可能性.
存在如下初始状态:
dp[1][fi][se] = 1;存在如下转移方程:
int q = (1 - p + MOD) % MOD; // 后退的概率if (x - 1 >= 0) { int p1 = (x - 1 == 0) ? 1 : p; // 玩家 1 前进的概率 if (y - 1 >= 0) { int p2 = (y - 1 == 0) ? 1 : p; // 玩家 2 前进的概率 // 都前进 dp[i][x][y] = (dp[i][x][y] + 1ll * dp[i - 1][x - 1][y - 1] * (1ll * p1 * p2 % MOD)) % MOD; } if (y + 1 <= 2 * k) // 玩家 1 前进,玩家 2 后退 dp[i][x][y] = (dp[i][x][y] + 1ll * dp[i - 1][x - 1][y + 1] * (1ll * p1 * q % MOD)) % MOD;}if (x + 1 <= 2 * k) { if (y - 1 >= 0) { // 玩家 1 后退,玩家 2 前进 int p2 = (y - 1 == 0) ? 1 : p; // 玩家 2 前进的概率 dp[i][x][y] = (dp[i][x][y] + 1ll * dp[i - 1][x + 1][y - 1] * (1ll * q * p2 % MOD)) % MOD; } if (y + 1 <= 2 * k) // 都后退 dp[i][x][y] = (dp[i][x][y] + 1ll * dp[i - 1][x + 1][y + 1] * (1ll * q * q % MOD)) % MOD;}统计结果时,只统计第一人终点和第二人起点相邻的情况:
for (int x = 0; x <= 2 * k; x++) { // 枚举第一人终点 if (x != se - 1 && x != se + 1) continue; // 距离恰好等于 1 int bp = (se == x + 1) ? ((x == 0) ? 1 : p) : q; // 连接步(k->k+1)走对的概率 long long s = 0; for (int y = 0; y <= 2 * k; y++) // 枚举第二人终点 s += dp[k][x][y]; ans = (ans + (s % MOD) * bp) % MOD;}显然,通过观察,我们不难发现,dp[i][..][..] 的值只与 dp[i-1][..][..] 有关,因此可以使用滚动数组优化.
标程
#include <bits/stdc++.h>using namespace std;const int MOD = 998244353;const int N = 70;int n, k, p;char c[N];int nxt[N];int dp[2][N][N]; // 使用滚动数组vector<int> pane;int ans;
void start(int fi, int se) { memset(dp, 0, sizeof(dp)); dp[1][fi][se] = 1; int q = (1 - p + MOD) % MOD; for (int i = 2; i <= k; i++) { int cur = i & 1, pre = (i - 1) & 1; // cur => i; pre => i - 1 for (int x = 0; x <= 2 * k; x++) { for (int y = 0; y <= 2 * k; y++) { dp[cur][x][y] = 0; if (c[pane[x]] != c[pane[y]]) continue; if (x - 1 >= 0) { int p1 = (x - 1 == 0) ? 1 : p; if (y - 1 >= 0) { int p2 = (y - 1 == 0) ? 1 : p; dp[cur][x][y] = (dp[cur][x][y] + 1ll * dp[pre][x - 1][y - 1] * (1ll * p1 * p2 % MOD)) % MOD; } if (y + 1 <= 2 * k) dp[cur][x][y] = (dp[cur][x][y] + 1ll * dp[pre][x - 1][y + 1] * (1ll * p1 * q % MOD)) % MOD; } if (x + 1 <= 2 * k) { if (y - 1 >= 0) { int p2 = (y - 1 == 0) ? 1 : p; dp[cur][x][y] = (dp[cur][x][y] + 1ll * dp[pre][x + 1][y - 1] * (1ll * q * p2 % MOD)) % MOD; } if (y + 1 <= 2 * k) dp[cur][x][y] = (dp[cur][x][y] + 1ll * dp[pre][x + 1][y + 1] * (1ll * q * q % MOD)) % MOD; } } } } int bk = k & 1; for (int x = 0; x <= 2 * k; x++) { if (x != se - 1 && x != se + 1) continue; // 距离恰好等于 1 int bp = (se == x + 1) ? ((x == 0) ? 1 : p) : q; long long s = 0; for (int y = 0; y <= 2 * k; y++) s += dp[bk][x][y]; ans = (ans + (s % MOD) * bp) % MOD; }}void solve(int st) { pane.clear(); for (int i = 1; i <= 2 * k + 1; i++) { pane.push_back(st); st = nxt[st]; } for (int i = 0; i <= 2 * k; i++) { if (c[pane[i]] == c[pane[1]]) { start(1, i); } }}signed main() { cin >> n >> k >> p; for (int i = 1; i <= n; i++) cin >> c[i]; for (int i = 1; i <= n; i++) cin >> nxt[i]; for (int i = 1; i <= n; i++) solve(i); cout << (ans + MOD) % MOD;}实际上,对于一个对象进行两组重复的动作时,这也可以等同于两个对象分别做一组相同的动作.
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!


