AtCoder Beginner Contest 372 F Solution
题意
给出一个按标号排序的环,以及 \(m\) 条辅助边,求从节点 \(1\) 开始走 \(k\) 步的方案数。\(2\le n\le 2\times 10^5\),\(1\le m\le {\color{red}{50}}\),\(1\le k\le 2\times 10^5\)。
思路
本题有显然的 \(O(k(n+m))\) 暴力转移思路,但数据范围决定了这是不可行的。考虑优化。
首先考虑 \(m=0\) 的情况,即没有辅助边。在这种情况下,不管走几步,高桥君能够抵达的点都是唯一的。
在这种情况下,DP 数组就像是每次向后循环位移了一位。这也正是本题的关键。
设 \(d_i\) 为走到节点 \(i\) 的方案数。
首先,为了处理方便,不妨将所有节点的标号减 \(1\),这样旋转操作可以转化为标号取模操作。
在旋转辅助边的情况下,我们可以忽略对原环上边的转移。边 \((x,y)\) 在第 \(t\) 次转移时的实际效果是
\[d_{(y-t)\bmod n}\to d_{(y-t)\bmod n}+d_{(x-t+1)\bmod n}
\]
原因是,若数组整体向后偏移一位,相对的,在原数组上的位置相应向前偏移一位。由于转移前数组未偏移,所以得到额外方案的位置少偏移一位。
另外,由于所有操作需要同时进行,所以需要缓存值后再操作。
时间复杂度 \(O(n+mk)\)。
代码
#include <cstdio>
#include <utility>
using namespace std;
const int N = 2e5 + 10, K = 5e1 + 10, mod = 998244353;
int n, m, k, x[N], y[N], dp[N], res;
using pii = pair<int, int>;
pii ad[K];
#define adm(x, y, mod) ((x) + (y) >= (mod) ? (x) + (y) - (mod) : (x) + (y))
int main()
{
scanf("%d%d%d", &n, &m, &k);
dp[0] = 1;
for (int i = 1; i <= m; i++)
{
scanf("%d%d", x + i, y + i);
x[i]--, y[i]--;
}
for (int i = 0, p = 1, p2 = 0; i < k; i++, p = adm(p, 1, n), p2 = adm(p2, 1, n))
{
for (int j = 1; j <= m; j++)
{
ad[j] = {adm(y[j], n - p, n), dp[adm(x[j], n - p2, n)]};
}
for (int j = 1; j <= m; j++)
{
dp[ad[j].first] = adm(dp[ad[j].first], ad[j].second, mod);
}
}
for (int i = 0; i < n; i++)
res = adm(res, dp[i], mod);
printf("%d\n", res);
}