定义一个长度为 \(n\) 的序列是好的,当且仅当有一个子段是 \(k\) 的排列。问所有长度为 \(n\),值域为 \(k\) 的彩色序列中,序列中一个长度为 \(m\) 的序列 \(A\) 一共出现了多少次。对于 \(1e9+7\) 取模。
\(1\le n \le 25000,1\le k\le 400\)。
我们考虑正难则反,考虑计算出不管序列好不好,出现了多少次 \(A\),然后减去不好的 \(A\) 中出现的次数即可。
那么前面的答案是 \((n-m+1)k^{n-m}\),然后我们考虑怎么算出来后面这个答案。
首先,如果 \(A\) 本身就是好的数列,那么,后面那个部分肯定是 \(0\)。
有一个部分分是算 \(A\) 里边的数互不相同的,我们只要算出来对于任意的 \(A\) 出现多少次,然后除以 \(k^{\underline m}\) 即可。
我们称一个序列是 \(\text{diff}\) 的,当且仅当它的数各不相同。预处理出来 \(F[i][j]\) 表示长度为 \(i\) 且不好的序列,结尾恰有一个极长为 \(j\) 的序列的个数。我们可以注意到,这种情况下,其实对于 \(\text{diff}\) 序列是怎么样的,他们的方案数都是一样的。
我们可以得到 \(F[1][1] = k\),然后递推公式为 \(F[i][j] = (k - j + 1)F[i - 1][j-1]+\sum_{l\ge j}F[i-1][j]\)。这个不难看出来。
也就是说,我们对于某个特定的长度 \(j\) 的 \(\text{diff}\) 序列,所有长度为 \(i\) 的不好的序列的方案数 \(G[i][j]\) 以它结尾的方案数是 \(\frac{\sum_{l=j}^{k-1}F[i][l]}{k^{\underline j}}\)。
那么,这也就启发了,我们要对于 \(A\) 是不是 \(\text{diff}\) 序列进行讨论,这样就不会有一个 \(k\) 排列跨越 \(A\) 了。
我们其次在考虑 \(A\) 是个互不相同的情况,该怎么计算答案。
我们枚举这个 \(A\) 的开头位置 \(i\),然后枚举这个序列的前 \(i+m-1\) 位置结尾最长的 \(\text{diff}\) 序列的长度 \(j\)(这个的字符串的方案数是 \(\frac{F[i+m-1][j]}{k^{\underline m}}\)),然后从这个 \(\text{diff}\) 序列的开头位置 \(pos\) 来乘上一个 \(G[m-pos+1][j]\) 即可。
那么,我们在考虑 \(A\) 有相同数字的情况,我们就只要把这个东西分成两段,然后分别用 \(G\) 计算即可。
#include <bits/stdc++.h> using std::cin; using std::cout; const int MAXK = 405, MAXN = 2.5e4 + 10, MOD = 1e9 + 7; int N, M, K, A[MAXN], F[MAXN][MAXK], S[MAXN][MAXK], lst[MAXK]; long long sig[MAXK]; auto Ksm = [] (long long x, int y) -> long long { long long ret = 1; for (; y; y >>= 1, x = x * x % MOD) { if (y & 1) { ret = ret * x % MOD; } } return ret; }; int main() { freopen("sequence.in", "r", stdin); freopen("sequence.out", "w", stdout); std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> N >> K >> M; sig[0] = 1; for (int i = 1; i <= K; ++i) { sig[i] = sig[i - 1] * (K - i + 1) % MOD; } for (int i = 1; i <= K; ++i) { sig[i] = Ksm(sig[i], MOD - 2); } F[1][1] = S[1][1] = K; for (int i = 2; i <= N; ++i) { for (int j = 1; j < K; ++j) { F[i][j] = (F[i - 1][j - 1] * (K - j + 1LL) + S[i - 1][j]) % MOD; } for (int j = K - 1; j; --j) { S[i][j] = (S[i][j + 1] + F[i][j]) % MOD; } } for (int i = 1; i <= M; ++i) { cin >> A[i]; } int ANS = Ksm(K, N - M) * (N - M + 1) % MOD, l = 1; for (int i = 1; i <= M; ++i) { if (lst[A[i]] >= l) { l = lst[A[i]] + 1; } lst[A[i]] = i; if (i - l + 1 >= K) { cout << ANS << '\n'; return 0; } } if (l != 1) { // 有相同的 再找一个 r memset(lst, 63, sizeof lst); int r = M; for (int i = M; i; --i) { if (lst[A[i]] <= r) { r = lst[A[i]] - 1; } lst[A[i]] = i; } auto solve2 = [&] (int n, int m, int L, int R) -> int { int ret = 0; for (int i = 1; i <= n - m + 1; ++i) { ret = (ret + 1LL * S[i + L - 1][L] * S[n - i - m + 1 + R][R]) % MOD; } return 1LL * ret * sig[L] % MOD * sig[R] % MOD; }; cout << (ANS - solve2(N, M, r, M - l + 1) + MOD) % MOD << '\n'; } else { auto solve1 = [&] (int n, int m, int k) -> int { int ret = 0; for (int i = 1; i <= n - m + 1; ++i) { for (int j = m; j < std::min(i + m, k); ++j) { ret = (ret + 1LL * F[i + m - 1][j] * S[n - i - m + 1 + j][j] % MOD * sig[j]) % MOD; } } return 1LL * ret * sig[m] % MOD; }; cout << (ANS - solve1(N, M, K) + MOD) % MOD << '\n'; } return 0; }
再来说一下神 Cirno_9 的对于 \(A\) 互不相同的神做法:
首先 \(F\) 数组是一样的。然后我们考虑直接去计算一个数组 \(C[i][j]\) 表示,一个长度为 \(i\) 的序列,序列末尾有一个极长为 \(j\) 的 \(\text{diff}\) 序列的出现了长度为 \(m\) 的互不相同的数字的组数。
那么,我们可以得到 \(C[i][j] = (k - j + 1)C[i-1][j - 1]+\sum_{l\ge j} C[i - 1][l] + [j\ge m] dp[i][j]\)
然后我们发现 \(\frac{\sum _{l}C[n][l]}{k^{\underline m}}\) 就是答案了。
给你一个有 \(n\) 个点,\(m\) 条边的有向图,一个节点是好的,当且仅当这个节点到其他节点都是只有一条路径的。保证给你的图里面有趣的节点个数的百分比不少于 \(20\%\),求所有这样有趣的节点。
这种百分比的东西,启发我们需要随机化。
我们先考虑怎么找到一个有趣的点,我们只要对这个点做 \(\text{dfs}\),然后如果没有横叉边和重孙边,那么这就是对的。
那么,我们随机100次肯定可以找到一个有趣的点。
我们找到了这个有趣的点,以这个点建立 \(\text{dfs}\) 树,满足这棵树上只有可能返租边和树边。那么,我们考虑,如果有两个返租边跨越一个节点,那么肯定有这个节点是不合法的。
否则,假定我们可以通过 \(x\) 子树内的返祖边到达上方的顶点是个 \(u\),那么如果 \(u\) 不好,\(x\) 肯定也不好,因为这必定是上一种情况,或者说还可以在 \(u\) 的子树内向上爬升为 \(v\),那么最上方的那个顶点肯定也是不合法的。
#include <bits/stdc++.h> const int MAXN = 1e5 + 10; using std::cin; using std::cout; using std::vector; int T, N, M; vector<int> e[MAXN]; int vis[MAXN], inst, ck[MAXN], cnt[MAXN], anc[MAXN], dep[MAXN]; long long rnd() { return (((long long) rand()) << 26) | rand(); } void dfs(int nw) { vis[nw] = 1; for (auto &j: e[nw]) { if (!vis[j]) { dfs(j); } else if (vis[j] == 2) { inst = 0; } } vis[nw] = 2; } int Dfs(int nw) { vis[nw] = 1; anc[nw] = nw; for (auto &j: e[nw]) { if (!vis[j]) { dep[j] = dep[nw] + 1; cnt[nw] += Dfs(j); if (dep[anc[nw]] > dep[anc[j]]) { anc[nw] = anc[j]; } } else { --cnt[j]; ++cnt[nw]; if (dep[j] < dep[anc[nw]]) { anc[nw] = j; } } } if (cnt[nw] > 1) { ck[nw] = 1; } return cnt[nw]; } void Dfs2(int nw) { vis[nw] = 1; ck[nw] |= ck[anc[nw]]; for (auto &j: e[nw]) { if (!vis[j]) { Dfs2(j); } } return; } void solve() { cin >> N >> M; for (int i = 1; i <= N; ++i) { e[i].clear(); } for (int i = 1, x, y; i <= M; ++i) { cin >> x >> y; e[x].push_back(y); } int id = -1; for (int i = 1; i <= 100; ++i) { int r = rnd() % N + 1; auto check = [&] () -> int { memset(vis, 0, 4 * (N + 1)); inst = 1; dfs(r); return inst; }; if (check()) { id = r; break; } } assert(id != -1); memset(vis, 0, 4 * (N + 1)); memset(ck, 0, 4 * (N + 1)); memset(cnt, 0, 4 * (N + 1)); dep[0] = -1; Dfs(id); memset(vis, 0, 4 * (N + 1)); Dfs2(id); for (int i = 1; i <= N; ++i) { if (!ck[i]) { cout << i << ' '; } } cout << '\n'; return; } int main() { freopen("graph.in", "r", stdin); freopen("graph.out", "w", stdout); srand(20050112); std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); for (cin >> T; T--; ) { solve(); } return 0; }
给你 \(n\) 个向量 \((x,y)\),然后你可以用这些向量来构成一个凸多边形,要满足这个多边形能放在一个 \(m\times m\) 的一个正方形里边。
求有多少种方案可以构成这个凸多边形,对于 \(998244353\) 取模
我们考虑,由于构成的就是个凸多边形,所以每个向量的顺序,其实在你选定这些向量的时候,就定下来了。那么我们只要求,有多少种选定向量的个数就可以了。
也就是说,我们要求这个 \(c\) 序列的方案树,满足 \(\sum_{i=1}^nc_ix_i=0,\sum_{i=1}^nc_iy_i=0,\sum_{i=1}^nc_ix_i[x_i>0]\le m,\sum_{i=1}^nc_iy_i[y_i>0]\le m\) 。
然后这个题就开始奇怪的技巧了。我们的 \(dp\) 要保证我们的正的 \(c_ix_i\) 的和比 \(m\) 小,\(y\) 同理,负的也同理,同时最后要满足加的正的数字要等于负的数字。然后这就类似于数位 \(dp\)。我们按照每次根据二进制位来做,每次转移这些 \(c_i\) 的第 \(i\) 位是不是 \(0/1\),设 \(f[i][cpx][cpy][cnx][cny][bx][by]\) 为我们做到第 \(i\) 位,然后前 \(i\) 位里我们有 \(x\) 轴上正的进位为 \(cpx\),负的进位为 \(cnx\),有 \(y\) 轴上正的进位为 \(cpy\),负的进位为 \(cny\),\(bx\) 为前 \(i\) 位里 \(x\) 和 \(m\) 相比的大小关系,\(by\) 为前 \(i\) 位里 \(y\) 和 \(m\) 的大小关系。
然后每次,我们转移这些 \(c\) 的第 \(i\) 个二进制位,然后,这些 \(c_i\) 就会改变状态,然后转移到 \(f[i+1]\) 上。
这个进位是什么意思,就是说,我们不是会加一些 \(c_i[bit(c_i,k)]x_i\) 吗,然后这些会给总的 \(\sum c_ix_i\) 做贡献,然后就是这玩意会记录和 \(m\) 的大小关系即可。
最后我们要的答案就是 \(dp[30][0][0][0][0][0][0]-1\)(要减去什么都不选的方案)。
这个进位的数组大小大概开20就够了,因为每次最多加 \(20\),除以二就是 \(10\),然后相当于是
\[10+5+\cdots+\frac{1}{2^k}<20 \]#include <bits/stdc++.h> const int MOD = 998244353; using std::cin; using std::cout; auto Mod = [] (int x) -> int { if (x >= MOD) { return x - MOD; } else if (x < 0) { return x + MOD; } else { return x; } }; int x[5], y[5], px[32], py[32], nx[32], ny[32], dp[31][20][20][20][20][2][2]; int main() { freopen("shape.in", "r", stdin); freopen("shape.out", "w", stdout); std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); int N, M; cin >> N >> M; for (int i = 0; i < N; ++i) { cin >> x[i] >> y[i]; } int limit = 1 << N; for (int mask = 0; mask < limit; ++mask) { for (int i = 0; i < N; ++i) { if ((mask >> i) & 1) { if (x[i] > 0) { px[mask] += x[i]; } else { nx[mask] += x[i]; } if (y[i] > 0) { py[mask] += y[i]; } else { ny[mask] += y[i]; } } } nx[mask] = -nx[mask]; ny[mask] = -ny[mask]; } dp[0][0][0][0][0][0][0] = 1; for (int i = 0; i < 30; ++i) { for (int cpx = 0; cpx < 20; ++cpx) { for (int cpy = 0; cpy < 20; ++cpy) { for (int cnx = 0; cnx < 20; ++cnx) { for (int cny = 0; cny < 20; ++cny) { for (int bx = 0; bx < 2; ++bx) { for (int by = 0; by < 2; ++by) { if (!dp[i][cpx][cpy][cnx][cny][bx][by]) { continue; } for (int mask = 0; mask < limit; ++mask) { int npx = cpx + px[mask], npy = cpy + py[mask], nnx = cnx + nx[mask], nny = cny + ny[mask]; if ((npx ^ nnx) & 1 || (npy ^ nny) & 1) { continue; } int cx = npx & 1, mx = (M >> i) & 1, cy = npy & 1, my = (M >> i) & 1; dp[i + 1][npx >> 1][npy >> 1][nnx >> 1][nny >> 1][cx != mx ? cx > mx : bx][cy != my ? cy > my : by] = Mod(dp[i + 1][npx >> 1][npy >> 1][nnx >> 1][nny >> 1][cx != mx ? cx > mx : bx][cy != my ? cy > my : by] + dp[i][cpx][cpy][cnx][cny][bx][by]); } } } } } } } } cout << Mod(dp[30][0][0][0][0][0][0] - 1) << '\n'; return 0; }