正睿 25 年省选联考 Day 19
正睿 25 年省选联考 Day 19
得分
| T1 | T2 | T3 | 总分 | 排名 |
|---|---|---|---|---|
| \(10\) | \(90\) | \(70\) | \(170\) | \(6/18\) |
题解
T1 陈太阳的石子游戏
由于黑堆每次的操作顺序是固定的,所以我们可以将所有黑堆看成一个整体。剩下的白堆显然就是一个 Nim 游戏,每一个白堆的 SG 函数值是 \(\text{SG}(x)=x\)。现在我们要求的就是黑堆的 SG 函数。
打表可以发现,如果我们令 \(m\) 表示黑堆中最小堆的石头数,\(cnt\) 表示有最小堆出现了几次,那么我们可以得出黑堆的 SG 函数为 \(m-(cnt+[所有黑堆大小相同])\bmod 2\)。我们的目标是后手必胜,那么就要求所有 SG 函数异或和为 \(0\)。
考虑枚举 \(m,cnt\),此时比 \(m\) 小的堆一定都是白色,我们只用看比当前堆大的数即可。我们要求和某个数异或和为 \(0\) 的选数方案,显然用线性基实现一下即可。复杂度 \(O(n\log n)\)。注意特判当所有黑堆大小不同的时候除了 \(m\) 以外必须要再选一个数。
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int Maxn = 2e5 + 5;
const int Inf = 2e9;
const int Mod = 1e9 + 7;
il int Add(int x, int y) {return x + y >= Mod ? x + y - Mod: x + y;} il void pls(int &x, int y) {x = Add(x, y);}
il int Del(int x, int y) {return x - y < 0 ? x - y + Mod : x - y;} il void sub(int &x, int y) {x = Del(x, y);}
il int qpow(int a, int b, int P = Mod) {int res = 1; for(; b; a = 1ll * a * a % P, b >>= 1) if(b & 1) res = 1ll * res * a % P; return res;}
il int Inv(int a) {return qpow(a, Mod - 2);}
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
x = 0; char ch = getchar(); bool flg = 0;
for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
static short Stk[50], Top = 0;
x < 0 ? putchar('-'), x = -x : 0;
do Stk[++Top] = x % 10, x /= 10; while(x);
while(Top) putchar(Stk[Top--] | 48);
typ ? putchar('\n') : putchar(' ');
}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;
int n, a[Maxn];
int pre[Maxn], suf[Maxn];
namespace LB {
int p[62];
int cnt;
void insert(int x) {
for(int i = 61; i >= 0; i--) {
if(!(x >> i)) continue;
if(!p[i]) {p[i] = x; return ;}
x ^= p[i];
}
cnt++;
}
int query(int x) {
for(int i = 61; i >= 0; i--) {
if(!(x >> i)) continue;
if(!p[i]) return 0;
x ^= p[i];
}
return qpow(2, cnt);
}
}
int fac[Maxn], inv[Maxn];
void init() {
fac[0] = 1; for(int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[n] = Inv(fac[n]); for(int i = n; i >= 1; i--) inv[i - 1] = 1ll * inv[i] * i % Mod;
}
int C(int n, int m) {
if(n < 0 || m < 0 || n < m) return 0;
return 1ll * fac[n] * inv[m] % Mod * inv[n - m] % Mod;
}
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
signed main() {
read(n);
init();
for(int i = 1; i <= n; i++) read(a[i]);
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; i++) pre[i] = pre[i - 1] ^ a[i];
for(int i = n; i >= 1; i--) suf[i] = suf[i + 1] ^ a[i];
int ans = 0;
if(pre[n] == 0) ans++;
for(int i = n; i >= 1;) {
int pos = i, num = 0;
while(a[pos] == a[i]) pos--, num++;
for(int j = 1; j <= num; j++) {
int ret1 = a[i] - ((j + 1) & 1), ret2 = a[i] - (j & 1);
if((num - j) & 1) ret1 ^= a[i], ret2 ^= a[i];
if((pre[pos] ^ ret1 ^ suf[i + 1]) == 0) pls(ans, C(num, j));
pls(ans, 1ll * C(num, j) * LB::query(pre[pos] ^ ret2) % Mod);
if((pre[pos] ^ ret2 ^ suf[i + 1]) == 0) sub(ans, C(num, j));
}
for(int j = 1; j <= num; j++) LB::insert(a[i]);
i = pos;
}
write(ans);
Usd();
return 0;
}
附上打表代码:
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int Maxn = 2e5 + 5;
const int Inf = 2e9;
const int Mod = 1e9 + 7;
il int Add(int x, int y) {return x + y >= Mod ? x + y - Mod: x + y;} il void pls(int &x, int y) {x = Add(x, y);}
il int Del(int x, int y) {return x - y < 0 ? x - y + Mod : x - y;} il void sub(int &x, int y) {x = Del(x, y);}
il int qpow(int a, int b, int P = Mod) {int res = 1; for(; b; a = 1ll * a * a % P, b >>= 1) if(b & 1) res = 1ll * res * a % P; return res;}
il int Inv(int a) {return qpow(a, Mod - 2);}
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
x = 0; char ch = getchar(); bool flg = 0;
for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
static short Stk[50], Top = 0;
x < 0 ? putchar('-'), x = -x : 0;
do Stk[++Top] = x % 10, x /= 10; while(x);
while(Top) putchar(Stk[Top--] | 48);
typ ? putchar('\n') : putchar(' ');
}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;
int n, m, a[Maxn];
bool check() {
for(int i = 1; i <= n; i++) if(a[i]) return 0;
return 1;
}
int hsh() {
int res = 0;
for(int i = 1; i <= n; i++) res = res * 10 + a[i];
return res;
}
map <int, int> mp;
int dfs(int x) {
int hs = hsh();
if(mp.find(hs) != mp.end()) return mp[hs];
if(check()) {
for(int i = 1; i <= n; i++) cout << a[i] << " "; cout << ": 0\n";
return mp[hs] = 0;
}
bool vis[55];
for(int i = 0; i <= 54; i++) vis[i] = 0;
for(int i = 1; i <= n; i++) {
if(!a[i]) continue;
for(int j = 1; j <= a[i]; j++) {
a[i] -= j;
vis[dfs(x + 1)] = 1;
a[i] += j;
}
break;
}
int mex = 0;
while(vis[mex]) mex++;
for(int i = 1; i <= n; i++) cout << a[i] << " "; cout << ": " << mex << '\n';
return mp[hs] = mex;
}
void dfs(int x, int w) {
if(x == n + 1) {
int tmp = dfs(0);
return ;
}
for(int i = w; i <= m; i++) {
a[x] = i; dfs(x + 1, i);
}
}
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
read(n); read(m);
dfs(0, 0);
Usd();
return 0;
}
T2 陈阳太的集合
考虑倒过来完成这个过程,相当于我们要求每次将一个集合拆成两个集合,然后求不同方案数。令 \(f_n\) 表示将 \(n\) 个元素拆分的方案数,则转移如下:
- \(n\) 个点单独成为一个集合,方案为 \(1\)。
- 枚举给第一个集合多少个数,乘上组合数就是转移。不过由于左右集合不互相区分,所以还要乘 \(\dfrac{1}{2}\)。
那么转移方程就是:
这个转移很像卷积形式,考虑指数生成函数 \(F(x)=\sum f_i \dfrac{x^i}{i!}\),那么展开一下有:
写成生成函数就是 \(F(x)=e^x+\dfrac{1}{2}+\dfrac{1}{2}F^2(x)\)。解一下这个二次方程就是 \(F(x)=2-\sqrt{3-2e^x}\)(可以通过 \(f_0=1\) 判断取正还是负)。所以用多项式开根处理一下即可,复杂度 \(O(n\log n)\)。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int Maxn = (1 << 21) + 1;
const int Inf = 2e9;
const int Mod = 998244353;
const int YG = 3, InvG = 332748118, Inv2 = 499122177;
il int Add(int x, int y) {return x + y >= Mod ? x + y - Mod: x + y;} il void pls(int &x, int y) {x = Add(x, y);}
il int Del(int x, int y) {return x - y < 0 ? x - y + Mod : x - y;} il void sub(int &x, int y) {x = Del(x, y);}
il int qpow(int a, int b, int P = Mod) {int res = 1; for(; b; a = 1ll * a * a % P, b >>= 1) if(b & 1) res = 1ll * res * a % P; return res;}
il int Inv(int a) {return qpow(a, Mod - 2);}
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
x = 0; char ch = getchar(); bool flg = 0;
for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
static short Stk[50], Top = 0;
x < 0 ? putchar('-'), x = -x : 0;
do Stk[++Top] = x % 10, x /= 10; while(x);
while(Top) putchar(Stk[Top--] | 48);
typ ? putchar('\n') : putchar(' ');
}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;
int F[Maxn], G[Maxn];
int lim = 1;
int r[Maxn];
il void NTT(int *a, int n, int typ) {
for(int i = 0; i < n; i++) if(i < r[i]) swap(a[i], a[r[i]]);
for(int h = 1; h < n; h <<= 1) {
int cur = qpow(typ == 1 ? YG : InvG, (Mod - 1) / (h << 1));
for(int i = 0; i < n; i += (h << 1)) {
int w = 1;
for(int j = 0; j < h; j++, w = 1ll * w * cur % Mod) {
int x = a[i + j], y = 1ll * a[i + j + h] * w % Mod;
a[i + j] = Add(x, y);
a[i + j + h] = Del(x, y);
}
}
}
if(typ == -1) {
int iv = Inv(n);
for(int i = 0; i < n; i++) a[i] = 1ll * a[i] * iv % Mod;
}
}
int A[Maxn], B[Maxn];
il void Inv(int *a, int *b, int n) {
b[0] = Inv(a[0]);
for(int lim = 2; lim < (n << 1); lim <<= 1) {
int len = lim << 1;
for(int i = 0; i < lim; i++) A[i] = a[i], B[i] = b[i];
for(int i = 0; i < len; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) * (len >> 1));
NTT(A, len, 1), NTT(B, len, 1);
for(int i = 0; i < len; i++) b[i] = 1ll * B[i] * Del(2, 1ll * A[i] * B[i] % Mod) % Mod;
NTT(b, len, -1);
for(int i = lim; i < len; i++) b[i] = 0;
}
for(int i = 0; i <= (n << 1); i++) A[i] = B[i] = 0;
}
int C[Maxn], D[Maxn];
il void Sqrt(int *a, int *b, int n) {
b[0] = 1;
for(int lim = 2; lim < (n << 1); lim <<= 1) {
int len = lim << 1;
for(int i = 0; i < lim; i++) C[i] = a[i];
Inv(b, D, lim);
for(int i = 0; i < len; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) * (len >> 1));
NTT(C, len, 1), NTT(D, len, 1);
for(int i = 0; i < len; i++) C[i] = 1ll * C[i] * D[i] % Mod;
NTT(C, len, -1);
for(int i = 0; i < lim; i++) b[i] = 1ll * Add(b[i], C[i]) * Inv2 % Mod;
}
for(int i = 0; i <= (n << 1); i++) C[i] = D[i] = 0;
}
int T, n;
int fac[Maxn], inv[Maxn];
void init(int N) {
fac[0] = 1; for(int i = 1; i <= N; i++) fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[N] = Inv(fac[N]); for(int i = N; i >= 1; i--) inv[i - 1] = 1ll * inv[i] * i % Mod;
for(int i = 0; i <= N; i++) F[i] = Mod - 2ll * inv[i] % Mod;
F[0] = 1; Sqrt(F, G, 1 << 20);
for(int i = 0; i <= N; i++) G[i] = Mod - G[i];
pls(G[0], 2);
}
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
read(T); init(1e6);
while(T--) {
read(n); write(Del(1ll * G[n] * fac[n] % Mod, 1));
}
Usd();
return 0;
}
T3 陈太阳的树
很显然我们要对这些模式串建一个 AC 自动机,然后要做的就是在链上跑一个 AC 自动机上 dp 的板子。令 \(S\) 表示模式串总长,直接暴力做的话复杂度是 \(O(qn|\Sigma|S)\),显然过不去。
由于这是一个树上问题,且 \(S\) 比较小,所以考虑使用矩阵优化转移,然后用树剖或者倍增来查询。此时预处理复杂度是 \(O(nS|\Sigma|+nS^3\log n)\) 的,查询复杂度是 \(O(qS^2\log n)\),因为我们每次询问的时候是一个向量乘矩阵。
到这里因为种种原因其实已经可以通过了,在矩阵转移的时候特判一下当前位置有没有值即可。不过题解给出了一种比较神奇的处理方式:
考虑对于一个深度为 \(x\) 的点,记 \(\text{lowbit}(x)=2^j\),那么我们只预处理它跳到 \(2^0,2^1,\cdots,2^j\) 级祖先的矩阵。查询的时候先求出 LCA,然后再跳父亲即可。但是实际上这个复杂度并没有优化多少。
我们现在希望每一个数的 \(\text{lowbit}\) 尽可能小,这样预处理的复杂度就会降低。考虑给每个点深度同时加上一个值不会影响答案,所以我们通过这个来进行调整。先考虑最后一位,如果这一位上为 \(0\) 的数比为 \(1\) 的数多,给每个数加上 \(1\),这样可以保证这一位上为 \(1\) 的数至少是 \(\tfrac{n}2\)。对于后进面的每一位同理操作,每一次加上一个 \(2^i\) 即可。
显然每一次我们最劣的情况是 \(0,1\) 数量对半分,那么此时需要预处理的次数为 \(\tfrac{n}2\times 1+\frac n4\times 2+\frac n8\times 3+\cdots <2n\),所以预处理复杂度可以降到 \(O(nS^3)\)。
综上我们可以在 \(O(n\log n+nS|\Sigma|+nS^3+qS^2\log n)\) 的复杂度内解决这个问题。
这里给出暴力做法的代码:
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int Maxn = 5e3 + 5;
const int Mod = 998244353;
il int Add(int x, int y) {return x + y >= Mod ? x + y - Mod: x + y;} il void pls(int &x, int y) {x = Add(x, y);}
il int Del(int x, int y) {return x - y < 0 ? x - y + Mod : x - y;} il void sub(int &x, int y) {x = Del(x, y);}
il int qpow(int a, int b, int P = Mod) {int res = 1; for(; b; a = 1ll * a * a % P, b >>= 1) if(b & 1) res = 1ll * res * a % P; return res;}
il int Inv(int a) {return qpow(a, Mod - 2);}
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;
int n, m, q;
int head[Maxn], edgenum;
struct node {
int nxt, to; string s;
}edge[Maxn];
void add(int u, int v, string s) {
edge[++edgenum] = {head[u], v, s}; head[u] = edgenum;
edge[++edgenum] = {head[v], u, s}; head[v] = edgenum;
}
string t[Maxn];
struct AC_Automaton {
int son[27], fail, flg;
}tr[45];
int tot = 1;
namespace ACAM {
void insert(string s) {
int u = 1;
for(int i = 0; i < s.size(); i++) {
int ch = s[i] - 'a';
if(!tr[u].son[ch]) tr[u].son[ch] = ++tot;
u = tr[u].son[ch];
}
tr[u].flg = 1;
}
queue <int> q;
void build() {
for(int i = 0; i < 26; i++) tr[0].son[i] = 1;
tr[1].fail = 0; q.push(1);
while(!q.empty()) {
int u = q.front(); q.pop();
int fa = tr[u].fail; tr[u].flg |= tr[fa].flg;
for(int i = 0; i < 26; i++) {
int v = tr[u].son[i];
if(!v) tr[u].son[i] = tr[fa].son[i];
else tr[v].fail = tr[fa].son[i], q.push(v);
}
}
tot++;
}
}
struct Mat {
int a[45][45];
Mat() {for(int i = 1; i <= tot; i++) for(int j = 1; j <= tot; j++) a[i][j] = 0;}
Mat operator * (Mat t) {
Mat c;
for(int i = 1; i <= tot; i++) {
for(int j = 1; j <= tot; j++) {
if(!a[i][j]) continue;
for(int k = 1; k <= tot; k++) {
if(!t.a[j][k]) continue;
pls(c.a[i][k], 1ll * a[i][j] * t.a[j][k] % Mod);
}
}
}
return c;
}
};
int fa[Maxn], dep[Maxn], siz[Maxn], son[Maxn];
Mat w[Maxn], sm1[Maxn], sm2[Maxn];
void dfs1(int x, int fth, string s) {
fa[x] = fth; dep[x] = dep[fth] + 1; siz[x] = 1;
w[x].a[tot][tot] += s.size();
for(int i = 1; i < tot; i++) {
if(tr[i].flg) continue;
for(auto ch : s) {
int to = tr[i].son[ch - 'a'];
if(!tr[to].flg) w[x].a[i][to]++;
else w[x].a[i][tot]++;
}
}
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
if(to == fth) continue;
dfs1(to, x, edge[i].s);
siz[x] += siz[to];
if(siz[to] > siz[son[x]]) son[x] = to;
}
}
int top[Maxn], dfn[Maxn], rnk[Maxn], idx;
void dfs2(int x, int rt) {
top[x] = rt;
rnk[dfn[x] = ++idx] = x;
if(x != rt) sm1[x] = w[x] * sm1[fa[x]], sm2[x] = sm2[fa[x]] * w[x];
else sm1[x] = sm2[x] = w[x];
if(son[x]) dfs2(son[x], rt);
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
if(to != son[x] && to != fa[x]) dfs2(to, to);
}
}
namespace SGT {
struct node {
Mat s1, s2;
}t[Maxn << 2];
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
void pushup(int p) {
t[p].s1 = t[rs(p)].s1 * t[ls(p)].s1;
t[p].s2 = t[ls(p)].s2 * t[rs(p)].s2;
}
void build(int p, int l, int r) {
if(l == r) {
t[p].s1 = t[p].s2 = w[rnk[l]];
return ;
}
int mid = (l + r) >> 1;
build(ls(p), l, mid), build(rs(p), mid + 1, r);
pushup(p);
}
void query1(int p, int l, int r, int pl, int pr, Mat &ans) {
if(pl > pr) return ;
if(pl <= l && r <= pr) {ans = ans * t[p].s1; return ;}
int mid = (l + r) >> 1;
if(pr > mid) query1(rs(p), mid + 1, r, pl, pr, ans);
if(pl <= mid) query1(ls(p), l, mid, pl, pr, ans);
}
void query2(int p, int l, int r, int pl, int pr, Mat &ans) {
if(pl > pr) return ;
if(pl <= l && r <= pr) {ans = ans * t[p].s2; return ;}
int mid = (l + r) >> 1;
if(pl <= mid) query2(ls(p), l, mid, pl, pr, ans);
if(pr > mid) query2(rs(p), mid + 1, r, pl, pr, ans);
}
}
Mat ret[Maxn], ans; int cnt = 0;
void solve(int u, int v) {
for(int i = 1; i <= tot; i++) ans.a[1][i] = 0;
ans.a[1][1] = 1;
while(top[u] != top[v]) {
if(dep[top[u]] > dep[top[v]]) ans = ans * sm1[u], u = fa[top[u]];
else ret[++cnt] = sm2[v], v = fa[top[v]];
}
if(dep[u] > dep[v]) SGT::query1(1, 1, n, dfn[v] + 1, dfn[u], ans);
else SGT::query2(1, 1, n, dfn[u] + 1, dfn[v], ans);
while(cnt) ans = ans * ret[cnt--];
cout << ans.a[1][tot] << '\n';
}
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
IOS(); cin >> n >> m >> q;
for(int i = 1, u, v; i < n; i++) {
string s; cin >> u >> v >> s;
add(u, v, s);
}
for(int i = 1; i <= m; i++) cin >> t[i], ACAM::insert(t[i]);
ACAM::build();
dfs1(1, 0, "");
dfs2(1, 1);
SGT::build(1, 1, n);
while(q--) {
int u, v; cin >> u >> v;
solve(u, v);
}
Usd();
return 0;
}

浙公网安备 33010602011771号