闲话
明日CSP,而我连廊桥都不会分配
我是不是要抱泠了
于是今日晚上写一些板子
480p
soytony:斯大林p
这是什么新p主能不能各位给我科普一下
今日歌词是摘了美影日记里新的那部分!
今天推的歌是美影日记!希望各位能去看看新的pv!
雨のち曇り晴れ
優しくきっと晴れ
きらきら浮かぶ言葉
もう忘れちゃうけれど
杂题
给定两个整数 \(n, m\) 和两个字符串 \(w_1, w_2\)。\(n\) 是两个字符串的长度,\(m\) 是答案 \(P\) 的下界,\(n, m \le {10}^5\)。
定义 Hash 函数如下
\[H_p(w)=\sum_{i=0}^{|w|-1}w_ir^i\bmod P \]其中 \(P\) 是一个质数。
请你寻找任意一对 \(P,r\) 满足 \(H_p(w_1)=H_p(w_2)\),题目保证至少存在一组可行解。
\(m\leqslant P\leqslant 10^9\),\(2\leqslant r\leqslant P-2\)。
仍然是翻译的官方题解。
问题可以被简单陈述如下:给定一个整系数多项式 \(f(x) = \sum_{i=0}^{n-1} f_ix^i\),找到一个大质数 \(p\) 以及 \(r \in [2, p-2]\) 使得 \(f(r) \equiv 0 \pmod p\)。
一种解法是随机找到一个质数 \(p\),并依次检查所有 \(r\) 的取值。由于对于 \(r\) 的随机值来说 \(f(r) \text{ mod } p\) 也可以被认为是随机的,因此我们可以粗略地认为所有 \(r\) 都不满足要求的概率为
\[\left(1-\frac 1p\right)^p \approx e^{-1} \ge \frac 13 \]因此我们只需重复 \(O(1)\) 次质数的选取。使用多项式多点求值能做到大常数的 \(O(n \log^2 n)\)。这足以解决问题,但这不是最优秀的做法,也不是最优雅的方法 : )
下面将讲述另一种做法。我们令 \(d\) 为 \(p-1\) 的任意因子。众所周知,在模 \(p\) 整数域内有恰好 \(d\) 个元素 \(r\) 满足
\[r^d \equiv 1 \pmod p \]注意到如果 \(r\) 是这样一个元素,则以 \(d=3\) 为例,有
\[r^{10} + r^5 \equiv r^{3\times 3 + 1} + r^{3+2} \equiv (r^3)^3 \times r + r^3\times r^2\equiv r +r^2 \pmod p \]事实上,如果我们将这一性质应用到整个多项式上,我们就可以将同余项的系数合并,得到一个更小度数的多项式
\[f^{(d)}(r) = \sum_{i=0}^{d-1}\alpha_ir^i \]这样求 \(d\) 个点只需要 \(O(d^2)\) 的复杂度。
当前算法的过程如下:寻找一个小整数 \(d\),选择一个形如 \(kd+1\) 的质数 \(p > m\),随后找到所有满足 \(r^d \equiv 1\pmod p\) 的整数 \(r\),随后对这些位置求解 \(f^{(d)}(r)\) 的值,祈祷着得到的值是 \(0\)。
决定性的 trick 就是既然 \(f^{(d)}\) 的系数能被一劳永逸地算出来,我们就可以对大量质数分别在 \(O(d^2)\) 的复杂度内计算所有的 \(r\) 位置对应的值。
注意到,如果我们还相信 \(f^{(d)} \pmod p\) 的值是随机的(而且我们必须要相信),那大致上 \(\frac md\approx \frac {10^5}d\) 个质数就应当足够了。
还剩下两个问题:1. 我们该选什么样的数 \(d\)? 2. 我们该如何找到满足 \(r^d \equiv 1 \pmod p\) 的整数 \(r\)?
对于 1. ,我们发现最好的选择就是那些小质数,例如 \(3,5,7,11,\cdots\)。这样我们就能留下大量形如 \(kd+1\) 的质数 \(p\),而且 \(O(d^2)\) 的时间复杂度开销也不会很大。
对于 2. ,当选好了 \(d\) 后找到满足如上条件的整数 \(r\) 也不是很难。我们随机一个整数 \(r_0\),取 \(r_0^{\frac{p-1}d}\) 为对应整数。正确性考虑费马小定理。这一步实际选出并在之后使用的数是 \(r_0^{\frac{p-1}d}\),因此其不可等于 \(1\)。
具体地说,以下流程的算法跑得很好:
- 选择一个质数 \(d\) 以及一个质数 \(p = kd+1\)。
- 从 \(r\in \{1, 2, \dots, p-1\}\) 中随机抽取一个值。
- 若 \(r^{\frac {p-1} d} \neq 1\) 进入下一步,反之回到上一步。
- 计算模 \(p\) 意义下的 \(f^{(d)}(r), f^{(d)}(r^2),\dots f^{(d)}(r^d)\)。
可以证明,上述算法一定能够输出我们想要的答案。进一步的,由于每个 \(d\) 会产生 \(O(\frac md)\) 个质数 \(p\),每个质数 \(p\) 能够验证 \(O(d^2)\) 个数字,因此取得 \(d\) 但无法得到所需答案的概率为
\[\left(1-\frac 1p\right)^{d^2\times \frac pd} = \left(1-\frac 1p\right)^{pd} \]可以看到期望复杂度很优秀。
code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,a,b) for (register int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define pre(i,a,b) for (register int i = (a), i##_ = (b) - 1; i > i##_; --i)
const int N = 1e6 + 10;
int n, m, str[N];
char s[N], t[N];
int prime[N], cnt; bool vis[N];
void sieve(int bnd) {
rep(i,2,bnd) {
if (!vis[i]) prime[++cnt] = i;
rep(j,1,cnt) {
if (i * prime[j] > bnd) break;
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
}
int qp(int a, int b, int mod) {
int ret = 1;
while (b) {
if (b & 1) ret = 1ll * ret * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
} return ret;
}
int a[N];
void check(int d) {
rep(i,0,d-1) a[i] = 0;
rep(i,0,n) a[i % d] += str[i];
for (int p = m / d * d + d + 1; p < 1e6; p += d) if (!vis[p]) {
int r, r0;
while (true) {
r = rand() * rand() % (p - 2) + 1;
r0 = qp(r, (p-1) / d, p);
if (r0 == 1) continue;
// cout << r << ' ' << r0 << endl;
r = r0;
rep(tmp,1,d) {
int t = 0, bse = 1;
rep(i,0,d-1) t = (t + 1ll * bse * a[i]) % p, bse = 1ll * bse * r % p;
if (t == 0) { cout << p << ' ' << r << endl; exit(0); }
r = 1ll * r0 * r % p;
}
break;
}
}
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m >> s+1 >> t+1;
rep(i,1,n) str[i] = s[i] - t[i];
sieve(1e6);
rep(i,2,cnt) check(prime[i]);
}
给定一棵\(n\)个点的树,点编号\(1 \sim n\),第\(i\)条边连接\(a_i\)和\(b_i\)。初始时你有一个空的序列,树上的\(n\)个点都有标记。现在按照边的编号从小到大考虑每一条边:
如果这一条边连接的两个点都有标记,则选择其中的一个点,擦除它的标记并将它的编号放入序列的末端;
否则什么都不做。
求能够由上述操作得到的不同的序列数量\(\bmod 998244353\)。
\(n \le 2\times 10^5\)
dp 题。
由于加边是有顺序的,考虑按照时间设计 dp 数组状态。
我们称一条边覆盖了一个点,当且仅当这条边擦除了这个点的标记。
然后设 \(f_{i,j}\) 为 \(i\) 节点子树的状态数,\(j\) 规定了 \(i\) 节点状态被擦除的情况。
- \(j=0\) 表示该节点被连向父亲的边前的连向儿子的边覆盖。
- \(j=1\) 表示该节点被连向父亲的边覆盖。
- \(j=2\) 表示该节点被连向父亲的边后的连向儿子的边覆盖。
- \(j=3\) 表示该节点没有被覆盖。
然后转移。
首先讨论被连向儿子的边覆盖。
枚举覆盖边连向的儿子。这个儿子 \(y\) 一定是在被连向父亲的边后被覆盖。转移值有 \(f_{y,2/3}\)。然后枚举其他儿子。对应边编号在 \(y\) 之前的点 \(z\) 一定要被覆盖,转移值有 \(f_{z,0/1}\),在 \(y\) 之后的点 \(z\) 不一定被覆盖,转移值有 $f_{z,0/2/3}。
先不考虑 \(j\) 的取值对 \(y\) 的限制,列出转移式:
限制自行加入。
然后讨论被父亲覆盖。
由于当前节点是被父亲覆盖的,因此其孩子中所有小于父亲的边都不能在覆盖当前节点后再覆盖。由于这条边不会删除当前节点,因此当前边只能擦除子节点。因此转移 \(f_{y,0/1}\) 。
相似的讨论说明对于大于父亲的节点不能选择 \(f_1\),因此转移 \(f_{y,0/2/3}\)。
然后讨论未被覆盖。
?
转移即可。
code
#include <bits/stdc++.h>
using namespace std;
#ifdef ONLINE_JUDGE
char buf[1<<21], *p1 = buf, *p2 = buf; inline char getc() { return (p1 == p2 and (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF : *p1++); }
#define getchar getc
#endif
template <typename T> inline void get(T & u){
u = 0; char ch = getchar(); bool f = 0; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
while (ch >= '0' and ch <= '9') u = (u<<1) + (u<<3) + (ch^48), ch = getchar(); f and (u = -u);
} template <typename T, typename ... Args> inline void get(T & u, Args & ... _Args) { get(u); get(_Args...); }
#define rep(i,a,b) for (register int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define pre(i,a,b) for (register int i = (a), i##_ = (b) - 1; i > i##_; --i)
const int N = 5e5 + 10, mod = 998244353;
int n, t1, t2, f[N][4];
vector <int> g[N];
void dfs(int u, int fa) {
for (auto v : g[u]) if (v != fa) dfs(v, u);
static int cnt, fap, _01[N], _23[N], _023[N];
cnt = fap = 0; _01[0] = 1;
for (auto v : g[u]) if (v == fa) fap = cnt;
else {
++cnt;
_01[cnt] = 1ll * _01[cnt - 1] * (f[v][0] + f[v][1]) % mod;
_23[cnt] = f[v][2] + f[v][3]; if (_23[cnt] >= mod) _23[cnt] -= mod;
_023[cnt] = _23[cnt] + f[v][0]; if (_023[cnt] >= mod) _023[cnt] -= mod;
}
_023[cnt + 1] = 1;
pre(i,cnt - 1,1) _023[i] = 1ll * _023[i + 1] * _023[i] % mod;
rep(i,1,cnt)
if (i <= fap) f[u][0] = (f[u][0] + 1ll * _01[i - 1] * _23[i] % mod * _023[i + 1]) % mod;
else f[u][2] = (f[u][2] + 1ll * _01[i - 1] * _23[i] % mod * _023[i + 1]) % mod;
f[u][1] = 1ll * _01[fap] * _023[fap + 1] % mod;
f[u][3] = _01[cnt];
}
signed main() {
get(n); rep(i,2,n) get(t1, t2), g[t1].emplace_back(t2), g[t2].emplace_back(t1);
dfs(1, 0);
cout << (1ll * f[1][0] + f[1][2] + f[1][3]) % mod;
}