2024年07月随便做做
[2024.07.15] 省选模拟
对不起。
但是为什么 B 题时间复杂度 \(O(n\sqrt n \log_2 A)\) 和 \(O(n^2\log_2 n)\) 同分啊/kk。
A. tree
记录 \(f_{u, x}\) 表示以 \(u\) 为根的连通块,最大权值为 \(x\) 的方案数。
可以发现两个子树在父亲上的合并应该形如:
对多个子树也是同样的。在最后考虑父亲点的权值一定会考虑进去,所以再多算一次。
这个可以线段树合并来维护。问题是对于每个点,都要考虑 \(f_{u,x}\) 对 \(x\) 最终的贡献。我们需要单独记录贡献的标记。这中间有很多细节,一定要理清楚贡献标记和乘法标记的优先级。
// Not afraid to dark.
#include <bits/stdc++.h>
using namespace std;
#define inline __inline__ __attribute__ ((always_inline))
namespace io {
// io
};
const int N = 2e5, mod = 998244353;
int n, m, a[N + 5];
int firs[N + 5], nex[N * 2 + 5], to[N * 2 + 5], tot;
inline void Add (int u, int v){
++ tot;
nex[tot] = firs[u];
firs[u] = tot;
to[tot] = v;
++ tot;
nex[tot] = firs[v];
firs[v] = tot;
to[tot] = u;
}
const int P = N * 25;
int rt[N + 5], ls[P + 5], rs[P + 5], tt;
struct TAG {
int x, y; // x (mul) y (query)
inline void apply (TAG t){ y = (y + 1ll * x * t.y % mod) % mod; x = 1ll * x * t.x % mod; }
TAG (int _x_ = 1, int _y_ = 0){ x = _x_, y = _y_; }
} tag[P + 5];
struct INFO {
int s, r; // s (sum) r (contribute)
inline void apply (TAG t){ r = (r + 1ll * s * t.y % mod) % mod; s = 1ll * s * t.x % mod; }
INFO (int _s_ = 0, int _r_ = 0){ s = _s_, r = _r_; }
} info[P + 5];
inline int new_node (){ ++ tt, tag[tt] = TAG (), info[tt] = INFO (), ls[tt] = rs[tt]= 0; return tt; }
inline void apply (int p, TAG t){ tag[p].apply (t), info[p].apply (t); }
inline void push_down (int p){
if (ls[p]) apply (ls[p], tag[p]);
if (rs[p]) apply (rs[p], tag[p]);
tag[p] = TAG ();
}
inline void push_up (int p){ info[p].s = (info[ls[p]].s + info[rs[p]].s) % mod; }
int Merge (int u, int v, int pu, int pv){
if (! u && ! v) return 0;
if (! u) return apply (v, TAG (pu + 1, 0)), v;
if (! v) return apply (u, TAG (pv + 1, 0)), u;
if (! ls[u] && ! rs[u]){
info[u].r = (info[u].r + info[v].r) % mod;
info[u].s = (1ll * info[u].s * (pv + info[v].s + 1) % mod + 1ll * info[v].s * (pu + info[u].s + 1) % mod + 1ll * (mod - 1) * info[u].s % mod * info[v].s % mod) % mod;
return u;
}
push_down (u), push_down (v);
rs[u] = Merge (rs[u], rs[v], (pu + (ls[u] ? info[ls[u]].s : 0)) % mod, (pv + (ls[v] ? info[ls[v]].s : 0)) % mod);
ls[u] = Merge (ls[u], ls[v], pu, pv);
push_up (u);
return u;
}
int Upd (int u, int l, int r, int x){
if (! u) return 0;
int res = 0;
if (r <= x){
res = info[u].s;
return apply (u, TAG (0, 0)), res;
}
push_down (u);
int mid = (l + r) >> 1;
res = res + Upd (ls[u], l, mid, x);
if (x > mid)
res = res + Upd (rs[u], mid + 1, r, x);
return res % mod;
}
void Mdy (int &u, int l, int r, int x, int t){
if (! u)
u = new_node ();
if (l == r)
return info[u].s = t, void ();
int mid = (l + r) >> 1;
push_down (u);
if (x <= mid)
Mdy (ls[u], l, mid, x, t);
else
Mdy (rs[u], mid + 1, r, x, t);
push_up (u);
}
void Output (int u, int l, int r){
if (l == r)
return io::write (info[u].r), putchar (r == m ? '\n' : ' '), void ();
push_down (u);
int mid = (l + r) >> 1;
Output (ls[u], l, mid);
Output (rs[u], mid + 1, r);
}
void Solve (int u, int father){
for (int e = firs[u], v;e;e = nex[e]){
v = to[e];
if (v == father) continue;
Solve (v, u);
rt[u] = Merge (rt[u], rt[v], 0, 0);
}
Mdy (rt[u], 1, m, a[u], (Upd (rt[u], 1, m, a[u]) + 1) % mod);
apply (rt[u], TAG (1, 1));
}
signed main (){
freopen ("tree.in", "r", stdin);
freopen ("tree.out", "w", stdout);
io::read (n), io::read (m);
for (int i = 1;i <= n;++ i)
io::read (a[i]);
for (int i = 1;i < n;++ i)
Add (io::read (), io::read ());
Solve (1, 0);
Output (rt[1], 1, m);
return 0;
}
B. train
最唐的一次。绕着正解想了各种歪解居然没有想到这个。
显然原题的要求可以转换成每次从 \(w\) 跳到 \(\min\{ x|x\ge l\land \forall i \in (x,w),a_i\oplus a_w<a_x\oplus a_w \}\)。
然后可以根据这个条件对于序列二分分治,在每一个子问题中,对于右部的一个点考虑一步跳到 \(\le mid\) 的位置需要让 \(l\) 最多为多少,记为 \(lim\),这个是好求的。
然后对于每一个子问题,在右部建出 \(l=mid+1\) 时的跳的树。
在询问中,递归到一个子问题中,当前 \(l\le mid,r>mid\),则可以倍增找到 \(r\) 跳到的第一个 \(lim_u\ge l\) 的点,然后正常跳到左部即可。
想到建树和条件转化,为什么没有考虑分治呢?
// Not afraid to dark.
#include <bits/stdc++.h>
using namespace std;
#define inline __inline__ __attribute__ ((always_inline))
namespace io {
// io
};
const int N = 1e5;
int n, q, a[N + 5];
int tot, son[N * 35 + 5][2], mx[N * 35 + 5];
int rt[N + 5];
inline void release (int x){ son[x][0] = son[x][1] = mx[x] = 0; }
inline void Insert (int u, int v, int x, int t){
for (int i = 30, e;i >= 0;-- i){
e = x >> i & 1; mx[u] = t;
son[u][e] = ++ tot; son[u][e ^ 1] = son[v][e ^ 1];
u = son[u][e], v = son[v][e];
}
mx[u] = t;
}
inline int Query_1 (int u, int x, int rs = 0){
static int res; res = 0;
for (int i = 30, e, r;i >= 0;-- i){
e = x >> i & 1, r = rs >> i & 1;
if (r)
u = son[u][e ^ 1];
else {
if (son[u][e ^ 1])
res = max (res, mx[son[u][e ^ 1]]);
u = son[u][e];
}
if (u == 0)
break;
}
return res;
}
inline int Query_2 (int u, int x, int l){
for (int i = 30, e;i >= 0;-- i){
e = x >> i & 1;
if (mx[son[u][e ^ 1]] >= l)
u = son[u][e ^ 1];
else
u = son[u][e];
}
return mx[u];
}
inline int Query_3 (int u, int x, int l){
static int res; res = 0;
for (int i = 30, e;i >= 0;-- i){
e = x >> i & 1;
if (mx[son[u][e ^ 1]] >= l)
u = son[u][e ^ 1], res |= 1 << i;
else
u = son[u][e];
}
return res;
}
int fa[20][20][N + 5], mf[20][20][N + 5];
#define Fa fa[d]
#define Mf mf[d]
void DAC (int l, int r, int d){
if (l == r)
return ;
int mid = (l + r) >> 1;
DAC (l, mid, d + 1);
DAC (mid + 1, r, d + 1);
for (int i = mid + 1;i <= r;++ i){
Fa[0][i] = Query_2 (rt[i - 1], a[i], mid + 1);
if (i != mid + 1)
Mf[0][i] = Query_1 (rt[mid], a[i], Query_3 (rt[i - 1], a[i], mid + 1));
else
Mf[0][i] = i - 1;
}
for (int i = mid + 1;i <= r;++ i)
for (int j = 1;j <= 17;++ j){
Fa[j][i] = Fa[j - 1][Fa[j - 1][i]];
Mf[j][i] = max (Mf[j - 1][i], Mf[j - 1][Fa[j - 1][i]]);
}
}
void qDAC (int l, int r, int d, int x, int y, int &ans){
if (l == r)
return ;
int mid = (l + r) >> 1;
if (y <= mid)
qDAC (l, mid, d + 1, x, y, ans);
else
if (x > mid)
qDAC (mid + 1, r, d + 1, x, y, ans);
else {
for (int i = 17;i >= 0;-- i)
if (Mf[i][y] < x){
y = Fa[i][y];
ans += 1 << i;
}
y = Query_2 (rt[y - 1], a[y], x), ++ ans;
qDAC (l, mid, d + 1, x, y, ans);
}
}
inline void ARKNIGHTS (){
while (tot)
release (tot --);
io::read (n), io::read (q);
for (int i = 1;i <= n;++ i){
io::read (a[i]);
Insert (rt[i] = ++ tot, rt[i - 1], a[i], i);
}
DAC (1, n, 1);
for (int l, r, ans;q --;){
io::read (l), io::read (r);
qDAC (1, n, 1, l, r, ans = 0);
io::write (ans), putchar ('\n');
}
}
signed main (){
freopen ("train.in", "r", stdin);
freopen ("train.out", "w", stdout);
for (int T = io::read ();T --;)
ARKNIGHTS ();
return 0;
}
C. circle
显然,因为对操作次数没有限制,所以相当于每次可以将一个长度为 $R = \gcd {b_1,\cdots,b_m,r-l+1} $ 的区间同时加上一个整数。
发现实际上可以全部消掉当且仅当 \(\forall i=0,\cdots R-1:sum_i=\sum_{j\in[l,r],j\equiv i\mod R} a_i\) 应该全部是相等的。必要性显然。充分性可以考虑如果有这个性质,那么从第一个位置开始每次以当前位置为左界操作操作,直到当前位置变为 \(0\),那么最后的 \(R\) 个数一定是相等的。
然后就转化成了 \(sum_{0,\cdots,R-1}\) 要相等的问题。
考虑 hash。如果
那么就一定是可以的。
考虑对 \(R\) 进行根号分治。
\(R\le \sqrt n\) 时,分块后对于每个 \(R\) 值记录块内的 \(sum_0\) 和 \(\sum a_i\cdot base^{i\textrm{ mod }k}\),这部分修改和查询都是简单的。
\(R> \sqrt n\) 时,考虑对于一个 \(R\),所有可能的 \(i-i\textrm{ mod } R\) 是 \(O(\frac n R) =O(\sqrt n)\) 级别的,而且是一段一段出现的。那么在 \([l,r]\) 中枚举 \(i - i \textrm{ mod } R\) 相同的一段,对于每一段在分块上求出 \(\sum a_i\cdot base^i\) 的值,再统一除以 \(base^{i-i\textrm{ mod } R}\) 即可。\(sum_0\) 可以直接暴力求出。
总共的时间复杂度为 \(O(n\sqrt n)\)。
// Not afraid to dark.
#include <bits/stdc++.h>
using namespace std;
#define inline __inline__ __attribute__ ((always_inline))
#define int long long
namespace io {
// io
};
const int N = 2e5;
inline int power (int x, int p, int mod){
static __int128 res, a;
res = 1, a = x;
while (p > 0){
if (p & 1)
res = res * a % mod;
a = a * a % mod;
p >>= 1;
}
return (int) res;
}
namespace HASH {
const int b1 = 137, b2 = 149, b3 = 151;
const int mod1 = 1222827239, mod2 = 1610612741, mod3 = 1000000007;
int i1, i2, i3;
int bp1[N + 5], bp2[N + 5], bp3[N + 5];
int qp1[N + 5], qp2[N + 5], qp3[N + 5];
int ip1[N + 5], ip2[N + 5], ip3[N + 5];
inline void init (int n){
i1 = power (b1, mod1 - 2, mod1), i2 = power (b2, mod2 - 2, mod2), i3 = power (b3, mod3 - 2, mod3);
bp1[0] = bp2[0] = bp3[0] = 1;
qp1[0] = qp2[0] = qp3[0] = 1;
ip1[0] = ip2[0] = ip3[0] = 1;
for (int i = 1;i <= n;++ i){
bp1[i] = bp1[i - 1] * b1 % mod1; qp1[i] = (qp1[i - 1] + bp1[i]) % mod1; ip1[i] = ip1[i - 1] * i1 % mod1;
bp2[i] = bp2[i - 1] * b2 % mod2; qp2[i] = (qp2[i - 1] + bp2[i]) % mod2; ip2[i] = ip2[i - 1] * i2 % mod2;
bp3[i] = bp3[i - 1] * b3 % mod3; qp3[i] = (qp3[i - 1] + bp3[i]) % mod3; ip3[i] = ip3[i - 1] * i3 % mod3;
}
}
struct hash_number {
int x, y, z;
hash_number (int _x_ = 0, int _y_ = 0, int _z_ = 0){ x = _x_; y = _y_; z = _z_; }
inline bool friend operator == (const hash_number a, const hash_number b){
return a.x == b.x && a.y == b.y && a.z == b.z;
}
inline void push_back (int a){
x = (x * b1 + a % mod1) % mod1;
y = (y * b2 + a % mod2) % mod2;
z = (z * b3 + a % mod3) % mod3;
}
inline hash_number operator << (const int t){
return hash_number (x * bp1[t] % mod1, y * bp2[t] % mod2, z * bp3[t] % mod3);
}
inline hash_number operator >> (const int t){
return hash_number (x * ip1[t] % mod1, y * ip2[t] % mod2, z * ip3[t] % mod3);
}
inline hash_number friend operator + (const hash_number a, const hash_number b){
return hash_number ((a.x + b.x) % mod1, (a.y + b.y) % mod2, (a.z + b.z) % mod3);
}
inline hash_number friend operator - (const hash_number a, const hash_number b){
return hash_number ((a.x - b.x + mod1) % mod1, (a.y - b.y + mod2) % mod2, (a.z - b.z + mod3) % mod3);
}
};
inline hash_number Hash (int X){
return hash_number ((X % mod1 + mod1) % mod1, (X % mod2 + mod2) % mod2, (X % mod3 + mod3) % mod3);
}
inline hash_number BOUND (hash_number B, int R){
return hash_number (B.x * qp1[R - 1] % mod1, B.y * qp2[R - 1] % mod2, B.z * qp3[R - 1] % mod3);
}
}
using HASH::hash_number;
using HASH::Hash;
using HASH::BOUND;
const int T = 450;
int n, q, a[N + 5];
int sq, len, block[N + 5], bl[T + 5], br[T + 5];
hash_number ctr[T + 5][T + 5], s0[T + 5][T + 5], pre[N + 5];
inline void modify (int x, int y){
for (int i = 1;i <= T;++ i){
ctr[block[x]][i] = ctr[block[x]][i] + (Hash (y) << (x % i));
if (x % i == 0)
s0[block[x]][i] = s0[block[x]][i] + Hash (y);
}
}
inline void reconstruct (int x){
for (int i = bl[x];i <= br[x];++ i){
if (i == bl[x]) pre[i] = hash_number ();
else pre[i] = pre[i - 1];
pre[i] = pre[i] + (Hash (a[i]) << i);
}
}
inline hash_number query (int l, int r){
static hash_number res; res = hash_number ();
if (block[l] == block[r]){
res = pre[r];
if (l != bl[block[l]])
res = res - pre[l - 1];
return res;
}
res = pre[br[block[l]]] + pre[r];
if (l != bl[block[l]])
res = res - pre[l - 1];
for (int i = block[l] + 1;i < block[r];++ i)
res = res + pre[br[i]];
return res;
}
inline void Build (){
sq = (int) sqrt (n);
len = (n - 1) / sq + 1;
for (int i = 1;i <= sq;++ i){
bl[i] = br[i - 1] + 1, br[i] = min (bl[i] + len - 1, n);
for (int j = bl[i];j <= br[i];++ j)
block[j] = i, modify (j, a[j]);
reconstruct (i);
}
}
signed main (){
freopen ("circle.in", "r", stdin);
freopen ("circle.out", "w", stdout);
io::read (n), io::read (q);
HASH::init (n);
for (int i = 1;i <= n;++ i)
io::read (a[i]);
Build ();
for (int l, r, m, x, w;q --;)
if (io::read () == 1){
io::read (x), io::read (w);
modify (x, w - a[x]);
a[x] = w;
reconstruct (block[x]);
} else {
io::read (l), io::read (r), io::read (m);
int R = r - l + 1;
for (;m --;)
R = __gcd (R, io::read ());
hash_number A, B;
if (R <= T){
for (int i = l;i <= min (r, br[block[l]]);++ i){
A = A + (Hash (a[i]) << (i % R));
if (i % R == 0)
B = B + Hash (a[i]);
}
for (int i = block[l] + 1;i < block[r];++ i)
A = A + ctr[i][R], B = B + s0[i][R];
if (block[l] != block[r])
for (int i = bl[block[r]];i <= r;++ i){
A = A + (Hash (a[i]) << (i % R));
if (i % R == 0)
B = B + Hash (a[i]);
}
B = BOUND (B, R);
} else {
auto get_r = [&] (const int t){ return min (r, t - t % R + R - 1); };
auto get_next = [&] (const int t) { return t - t % R + R; };
int L = l, T;
while (L <= r){
T = get_r (L);
hash_number xx;
A = A + (xx = query (L, T) >> (L - L % R));
L = T + 1;
}
L = get_next (l - 1);
while (L <= r){
B = B + Hash (a[L]);
L = get_next (L);
}
B = BOUND (B, R);
}
if (A == B)
puts ("YES");
else
puts ("NO");
}
return 0;
}
Miscellaneous
*[2024.06.28] 珍珠
一个 \(n\) 个珠子的项链可翻转、可旋转,染 \(01\) 颜色,问本质不同的项链中,满足存在一段子串为给定 \(s\) ,这些项链的个数。
\(n\le 10^9,m=|s|,m\le 22\)。
考虑带权 Burnside 引理,转换成:
其中 \(W(S)=\sum_{c\in S}w(c)\),\(w(c)\) 为 \(1\) 当且仅当 \(c\) 满足题目要求,否则为 \(0\)。
对于后面的和式,我们可以将 \(G\) 考虑分为两部分。
记 \(\Delta=(2,3,\cdots,n,1),\rho = (n,n-1,\cdots, 1)\)。
会发现一部分是未反转的部分,记为 \(G_1=\{\Delta^t|t\in \N^*\}\)。
而另一部分是反转过的,记为 \(G_2=\{\Delta^t\circ \rho|t\in \N^*\}\)。
会发现有循环和反转在环上的性质显然有:\(G_1\cup G_2=G,G_1\cap G_2=\empty\)。
且因为 \(\Delta^n=\iota\),因此 \(|G_1|=|G_2|=n\)。
首先考虑 \(G_2\) 部分的 \(\sum W(C(g))\) 怎么算。
考虑 \(n\) 的奇偶性,如果 \(n\) 为奇数,就只有一种情况,否则会出现两种情况。这里我们讨论奇数,因为偶数的情况类似,只是更加麻烦不易理解。
考虑 \(n\) 为奇数的情况,对于一个 \(t\),\(\Delta^t\circ \rho=(t,t-1,\cdots,1,n,n-1,\cdots,t+1)\)。因此\(C(\Delta^t\circ \rho)\) 中的串一定都是由两个回文串拼接成的,但是如果我们再将 \(\Delta^{-t}\) 作用在这些串上,一定会一一对应 \(C(\rho)\) 中的串,因为置换有逆,因此这个作用可以看作 \(C(\Delta^t\circ \rho)\) 到 \(C(\rho)\) 的双射。
而且同时知道 \(w(c)=w(\Delta^{k}*c)\),结合双射可知有 \(W(C(\Delta^t\circ \rho))=W(C(\rho))\)。
因此可知 \(\forall f,g\in G_2,W(C(f))=W(C(g))\)。
所以我们只需要考虑 \(W(C(\rho))\) 也就是有多少个长度为 \(n\) 的回文串满足首尾拼接成环后出现 \(s\) 或 \(s^R\)。正着不太好做,考虑反着算,即计算有多少串不满足条件。
考虑记扫描字符串的状态 \((x,y)\) 表示以当前顺序扫描到的字符串,\(x\) 为最长的后缀长度满足这个后缀是 \(s\) 的前缀,\(y\) 为最长的后缀长度满足这个后缀是 \(s^R\) 的前缀。会发现这些状态只有 \(O(m)\) 个,可以通过 AC 自动机或者 KMP 自动机维护。
因此可以先 \(O(2^m)\) 枚举前面的串,判断首位拼接是否合法并记录到状态各自的初始方案数,随后进行矩阵乘法,然后再在中心判断是否出现 \(s,s^R\) ,如果没有出现,即可计入答案。
这部分时间复杂度为 \(O(m^3\log_2 n+m2^m)\)。
如果要考虑 \(n\) 为偶数则还需考虑一种情况,就是两个奇长度的回文串的拼接,这种情况只能转换成一个单字符再加上一个奇回文串的情况,和上面的计算方法同理。
随后是 \(G_1\) 部分,这部分是简单的。
存在三个性质,强度是递进的:
- \(\Delta^t\) 中有 \(\gcd (t, n)\) 个置换环。
- \(\Delta^t\) 中 \(i\) 和 \(i+\gcd (t,n)\) (如果存在)一定属于一个轮换。
- \(\Delta^t\) 中 \(\forall 1\le i\le \gcd (t,n)\) 归属的轮换各不相同。
证明是简单的初等数论知识。
拥有这三个性质,我们知道对于 \(\Delta^t\) 考虑 \(W(C(\Delta^t))\) 只需要考虑长度为 \(\gcd(t,n)\) 并满足首位拼接后出现 \(s\) 或 \(s^R\),统计这样的串的个数即为 \(W(C(\Delta^t))\)。
所以考虑 \(F(x)\) 表示长度为 \(x\) 并满足首位拼接后出现 \(s\) 或 \(s^R\) 的串的个数。
\(F(x)\) 可以用矩阵乘法计算。同样先计算出从头到尾没出现过 \(s\) 或 \(s^R\) 的串的个数再做减法,我们可以先钦定末尾的状态是什么,做完矩阵乘法后再取出同样状态位置上的数计入答案即可。
那么知道有:
时间复杂度 \(O(d(n)m^3\log_2 n)\)。
总的时间复杂度是 \(O(d(n)m^3\log_2n+m2^m)\)。
// Not afraid to dark.
#include <bits/stdc++.h>
using namespace std;
clock_t start_time, end_time;
#define GET_START start_time = clock ();
#define GET_END end_time = clock (); fprintf (stderr, "TIME COSSEMED : %0.3lf\n", 1.0 * (end_time - start_time) / CLOCKS_PER_SEC);
#define inline __inline__ __attribute__ ((always_inline))
#define int long long
namespace io {
// io
}
template < int SIZE >
struct Automaton {
int nex[SIZE + 5][2], len[SIZE + 5], tot;
int a[SIZE + 5], cnt;
inline void init (){
nex[0][0] = nex[0][1] = len[0] = 0;
tot = cnt = 0;
}
inline int new_node (){
++ tot;
nex[tot][0] = nex[tot][1] = len[tot] = 0;
return tot;
}
inline void Insert (int x){
static int u, fw;
a[++ cnt] = x;
u = tot;
fw = nex[u][x];
nex[u][x] = new_node ();
nex[tot][0] = nex[fw][0];
nex[tot][1] = nex[fw][1];
}
inline int to (int x, int c){
return nex[x][c];
}
};
const int M = 22, mod = 1e9 + 7;
int n, m, l, ans;
char s[M + 5], t[M + 5];
Automaton < M + 1 > pre, suf;
int cn;
vector < pair < int , int > > state;
map < pair < int , int > , int > st;
struct matrix {
int a[M << 1 | 1][M << 1 | 1];
inline void init (int c = 0){
for (int i = 0;i < cn;++ i)
for (int j = 0;j < cn;++ j)
a[i][j] = (i == j ? c : 0);
}
inline matrix friend operator * (const matrix a, const matrix b){
static matrix res;
static int i, j, k;
static __int128 x;
res.init ();
for (i = 0;i < cn;++ i)
for (j = 0;j < cn;++ j){
for (k = 0, x = 0;k < cn;++ k)
x += (__int128) a.a[i][k] * b.a[k][j];
res.a[i][j] = x % mod;
}
return res;
}
matrix (int c = 0){
init (c);
}
};
inline matrix power (matrix a, int p){
matrix res (1);
while (p > 0){
if (p & 1)
res = res * a;
a = a * a;
p >>= 1;
}
return res;
}
inline int power (int a, int p){
int res = 1;
while (p > 0){
if (p & 1)
res = res * a % mod;
a = a * a % mod;
p >>= 1;
}
return res;
}
inline int nex (int x, int c){
static int p, q;
tie (p, q) = state[x];
p = pre.to (p, c);
q = suf.to (q, c);
return st[make_pair (p, q)];
}
inline bool tar (int x){
return state[x].first == m || state[x].second == m;
}
inline void Calc_Reverse (){
matrix res, g;
int rt = 0;
auto od_calc = [&] (){
int T = 0;
for (int i = 0, pt, j, u, v;i < cn;++ i)
if (res.a[0][i]){
u = i;
for (pt = 0;pt < 2;++ pt){
v = nex (u, pt);
if (tar (v))
continue;
if (state[i].first >= state[i].second)
for (j = state[i].first;j >= 1;-- j){
v = nex (v, s[j] - '0');
if (tar (v))
goto E_1;
}
else
for (j = state[i].second - 1;j >= 0;-- j){
v = nex (v, s[m - j] - '0');
if (tar (v))
goto E_1;
}
rt = (rt + res.a[0][i]) % mod;
T += res.a[0][i];
E_1 : ;
}
}
};
for (int i = 0, c;i < cn;++ i)
for (c = 0;c < 2;++ c)
if (! tar (i) && ! tar (nex (i, c)))
g.a[i][nex (i, c)] = 1;
if (n & 1){
int t = min (m, n >> 1);
for (int i = 0, j, u, v;i < (1 << t);++ i){
for (j = t - 1, v = 0;j >= 0;-- j){
v = nex (v, i >> j & 1);
if (tar (v))
goto E_2;
}
for (j = u = 0;j < t;++ j){
u = nex (u, i >> j & 1);
v = nex (v, i >> j & 1);
if (tar (v))
goto E_2;
}
++ res.a[0][u];
E_2 : ;
}
res = res * power (g, (n >> 1) - t);
od_calc ();
rt = (power (2, (n + 1) >> 1) - rt + mod) * n % mod;
} else {
int t = min (m, n >> 1);
for (int i = 0, j, u, v;i < (1 << t);++ i){
for (j = t - 1, v = 0;j >= 0;-- j){
v = nex (v, i >> j & 1);
if (tar (v))
goto E_3;
}
for (j = u = 0;j < t;++ j){
u = nex (u, i >> j & 1);
v = nex (v, i >> j & 1);
if (tar (v))
goto E_3;
}
++ res.a[0][u];
E_3 : ;
}
res = res * power (g, (n >> 1) - t);
for (int i = 0, j, u;i < cn;++ i)
if (res.a[0][i]){
u = i;
if (state[i].first >= state[i].second){
for (j = state[i].first;j >= 1;-- j){
u = nex (u, s[j] - '0');
if (tar (u))
goto E_4;
}
} else{
for (j = state[i].second - 1;j >= 0;-- j){
u = nex (u, s[m - j] - '0');
if (tar (u))
goto E_4;
}
}
rt = (rt + res.a[0][i] % mod) % mod;
E_4 : ;
}
res.init ();
t = min (m, (n >> 1) - 1);
for (int i = 0, j, u, v, s, pt;i < (1 << t);++ i){
for (j = t - 1, u = 0;j >= 0;-- j){
u = nex (u, i >> j & 1);
if (tar (u))
goto E_5;
}
for (pt = 0;pt < 2;++ pt){
v = nex (u, pt);
if (tar (v))
goto E_6;
for (j = s = 0;j < t;++ j){
s = nex (s, i >> j & 1);
v = nex (v, i >> j & 1);
if (tar (v))
goto E_6;
}
++ res.a[0][s];
E_6 : ;
}
E_5 : ;
}
res = res * power (g, (n >> 1) - 1 - t);
od_calc ();
rt = (power (2, n >> 1) + power (2, (n >> 1) + 1) - rt + mod) * (n >> 1) % mod;
}
ans = (ans + rt) % mod;
}
inline void Calc_inord (){
vector < int > fac;
int r = n;
for (int i = 2;i * i <= r;++ i)
while (r % i == 0){
if (fac.empty () || fac.back () != i)
fac.push_back (i);
r /= i;
}
if (r > 1)
fac.push_back (r);
auto phi = [&] (const int &x){
int res = x;
for (int y : fac)
if (x % y == 0)
res /= y, res *= (y - 1);
return res;
};
matrix A, g;
for (int i = 0, c;i < cn;++ i)
for (c = 0;c < 2;++ c)
if (! tar (i) && ! tar (nex (i, c)))
g.a[i][nex (i, c)] = 1;
auto F = [&] (const int x){
int res = 0;
A.init ();
for (int i = 0;i < cn;++ i){
if (max (state[i].first, state[i].second) == m)
continue;
A.a[i][i] = 1;
}
A = A * power (g, x);
for (int i = 0;i < cn;++ i)
res = (res + A.a[i][i]) % mod;
return (power (2, x) - res + mod) % mod;
};
int rt = 0;
for (int i = 1;i * i <= n;++ i)
if (n % i == 0){
rt = (rt + phi (n / i) * F (i) % mod) % mod;
if (n / i != i)
rt = (rt + phi (i) * F (n / i) % mod) % mod;
}
ans = (ans + rt) % mod;
}
signed main (){
GET_START
io::read (n), io::read (m);
io::get_string (s);
if (n == 1)
return putchar ('1'), putchar ('\n'), 0;
if (n == 2){
if (m == 2)
return putchar ('1'), putchar ('\n'), 0;
else
return putchar ('2'), putchar ('\n'), 0;
}
pre.init ();
for (int i = 1;i <= m;++ i)
pre.Insert (s[i] - '0'), suf.Insert (s[m - i + 1] - '0');
state.push_back (make_pair (0, 0));
for (int i = 1, j, u;i <= m;++ i){
for (j = 1, u = 0;j <= i;++ j)
u = suf.to (u, s[j] - '0');
state.push_back (make_pair (i, u));
for (j = m, u = 0;j > m - i;-- j)
u = pre.to (u, s[j] - '0');
state.push_back (make_pair (u, i));
}
sort (state.begin (), state.end ());
state.erase (unique (state.begin (), state.end ()), state.end ());
cn = (int) state.size ();
for (int i = 0;i < cn;++ i)
st[state[i]] = i;
Calc_Reverse ();
Calc_inord ();
io::write (ans * power ((n << 1) % mod, mod - 2) % mod), putchar ('\n');
GET_END
return 0;
}
Codeforces 1987G1 - Spinning Round (Easy Version)
考虑对于以 \(1\) 为根的情况,一定可以分成两段不交的路径,分别线段树维护单调栈扫前后缀即可。
但是需要注意的是不以 \(1\) 为根的情况,这时发现对于一段前缀,如果最终不以 \(1\) 为根且要以自身为一段路径,一定会以后面第一个大于当前后缀最大值的点为根,对于每个前缀这个点是唯一的,因此扫后缀是顺便记录即可。扫前缀时同理。
Submission #268435009 - Codeforces
Codeforces 1987G2 - Spinning Round (Hard Version)
不再考虑复杂的线段树,换换思维考虑 dp。
建出笛卡尔树,发现如果不是连向笛卡尔树上的父亲,一定就会连向从它往上的祖先中,第一个改变指向的点的父亲。
于是考虑 DP 方程,记 \(f(x,0/1/2)\) 表示 \(x\) 子树中最终从左侧跳出的最长路径、最终从右侧跳出的最长路径、分别从左右两边跳出的两条路径和的最大值。
转移是简单的。
Submission #268861885 - Codeforces
QOJ #3029. The Great Drone Show
相当于求每条边什么时候断裂。
考虑一条边是否断裂可以转化成存在一个整数 \(lim_e\),使得 \(|z_u-z_v|\le lim_e\) 时不断裂,\(|z_u-z_v|\ge lim_e+1\) 时断裂,如果固定 \(z_v\) 则转化成 \(z_u\) 一定要在某个区间内,这个 \(lim\) 时容易找到的。
考虑原图是一棵树怎么做,可以将儿子到父亲的边挂在父亲上,每次一个点的 \(z\) 发生变化,我们先更新它和父亲之间的边,具体地是改变一个区间,判断是不是断裂,是就删去,否则就会在父亲上去掉原本挂着的区然后挂上新的。然后考虑儿子,把挂上来的区间中不包含新的 \(z_u\) 的全部删掉并断裂。这些都可以用 set 做到。
考虑原本的图与树的区别在于,给边定向之后变成 DAG,出度可能很大。但是因为平面图我们可以每次将度数最小的取出来并删掉,以这个删掉的顺序作为拓扑序,可以得到一个所有点出度小于等于 \(5\) 的 DAG,就做完了,时间复杂度 \(O(n\log_2 n)\)。
顺便一提,如果没有平面图的条件那么时间复杂度应该是 \(O(\min (\frac{m}{n},n)n\log_2 n)\)
[THUPC Final 2024 A] 古明地枣的袜子
将操作按更新位置排序之后分块处理,对于每个块分别更新答案。
如果是其他块影响了这个块,那么可以前缀和预处理出来。
那么本块中只剩 \(O(\sqrt n)\) 个操作,相当于也只有本质 \(O(n)\) 个询问。
这里可以线段树做到单块 \(O(n\log n)\) 而且很好写,常数也小。也可以分治,我们在块内分治并按操作时间归并,同时记录 \(f(l,r)\) 表示归并之后只考虑 \(l,r\) 之间的操作的答案,这个需要超级细节的统计做到左右的合并,分析时间复杂度 \(T(B)=2T(B/2)+B^2=B^2\),因此单块在 \(O(n)\) 时间内就可以完成合并。
之所以是超级细节是因为操作位置可以重复因此有些位置并不能作为起点,需要各种判断。
[The 2nd Universal Cup. Stage 18] F. Fortune Wheel
BFS 求出不走随机的时候的步数,然后枚举随机目标大小,设为 \(i\) 则会随机期望 \(\frac{n}{i}\) 次,然后跳到任意一个 dis 前 \(i\) 小的位置上,枚举即可。
Codeforces 741C - Arpa's overnight party and Mehrdad's silent
发现直接从 \(2i-1\) 连边到 \(2i\),显然如果这其中所有边的两个顶点颜色不同就会满足原本的条件,而且这是一个匹配。发现原图也是一个匹配,两个匹配任意合并一定是一个二分图,因此一定存在二分图染色。
Submission #269645326 - Codeforces
CodeForces 1656G - Cycle Palindrome
随便排出一个回文串,使 \(i\to p_i:c_i=c_{p_i}\)。发现如果交换 \(p_i,p_j\) 会将新的位置上的两个字符交换,同时有:如果 \(i,p_i\) 和 \(j,p_j\) 原本不在一个环上,就会使他们合并成一个环;否则会分裂成两个环。
因此我们可以在回文串上从两边往中间合并,有不同的情况,但最终都是可以做的。
Submission #269652195 - Codeforces
CodeForces 1844E - Great Grids
发现有一个规律,如果把 \(A,B,C\) 分别在三进制上表示为 \(0,1,2\),则一行上的所有数和上一行的差值是相等的,列也是如此。
我们把第 \(i\) 行和第 \(i-1\) 行的差值记为 \(a_i\),第 \(j\) 列和第 \(j-1\) 列差值记为 \(b_j\)。
发现原题给的限制相当于是告诉了 \(a_i-b_j=0\) 或者 \(a_i+b_j=0\),扩展域并查集即可。
Submission #269655464 - Codeforces
[The 1st Universal Cup. Stage 14] C.LaLa and Lamp
每个点可以得出一个关于三条线的 01 方程。但是发现显然有很多线性相关的。
因此枚举最后两行和左下角的情况,发现已经可以得出所有线,判断对于其他位置是否合法即可。
CodeForces 1646F - Playing Around the Table
如果所有人手里的牌恰好是 \(1,2,\cdots,n\) 各一张,那么可以构造出一种 \(\frac{(n^2-n)}{2}\) 次操作的方案,这个是简单的。
考虑选一个位置,它存在重复的牌。从这个位置开始一直往后给自己重复的牌,考虑这样做一定可以执行到上面的状态,因为除了一开始的人,每个人做选择时考虑的实际上手里应该有 \(n+1\) 张牌。
接下来考虑为什么这样一定可以做到 \(\frac{(n^2-n)}{2}\) 次以内。是因为每种牌如果出手就不会再回到自己这里,因为如果这样,那么这种牌一定不止 \(n\) 张。因此每一步都不是无用的。考虑每一个牌最多会移动 \(\frac{(n^2-n)}{2}\) 次,但是总共有 \(n\) 种牌,但是一次会移动 \(n\) 步,所以总共的步数也是 \(\frac{(n^2-n)}{2}\) 的。
合起来满足题目的要求。
Submission #269693347 - Codeforces
CodeForces 1693F - I Might Be Wrong
发现每次排序时 \(cnt_0=cnt_1\),否则通过调整法一定可以不劣:
不妨设 \(cnt_0>cnt_1\)。
发现如果 \(s_l=0\),直接 \(l'=l+1\) 更优;如果 \(s_l=1\),那么一定存在一个前缀 \([l,r']\) 使 \(cnt'_0=cnt'_1\),此时我们可以做两次操作:\([l,r'],[l+1,r]\) 与原本的操作代价一样。
所以从前往后,枚举起点,考虑最大的 \(cnt_0=cnt_1\) 的区间即可。case 比较多。
Submission #269712451 - Codeforces
QOJ #895. Color
等价于最终图上对于每个颜色存在 \(\frac{m+1}{2}\) 的匹配。
记一个颜色的 \(s_0,s_1,s_2\) 分别表示匹配的两个点都没在当前图中的个数,一个点在图中的个数,两个点在图中的个数。
考虑当有一种颜色有 \(s_1+s_2>\frac{m+1}{2}\) 时无解,否则有解。
我们考虑怎么将 \(n\) 扩展为 \(n+1\)。
建出二分图,左边 \(m\) 个点表示颜色,右边前 \(n\) 个点表示前面的点,最后一个点表示不连,最后一个点的出流量为 \(m-n\),其他点都是 \(1\)。如果一个点在原图没有这种颜色的边,就连边;然后对于每个颜色,向第 \(n+1\) 个点连 \(2s_0\) 个边。
如果有完美匹配则可以扩展,如果一条边满流,说明新加的点和这个边的右点连接,颜色为左点。
发现将 \(n+1\) 这个点拆成 \(m-n\) 个点,每个点的出度或者入度均为 \(m+1-n\),因此一定存在完美匹配。
UOJ #354. 新年的投票
Sub 3:
我们执行这样一个策略:第 \(i\) 个人观察前面 \(i-1\) 个人中是否有 \(1\)。如果有则投 \(0\) 票;否则把自己当作 \(1\),向全体的异或和投出 \(2^{i-1}\) 票。
这样最终绝对的话语权会掌握在第一个为 \(1\) 的人手中,他投出的票比其他所有人加起来还要多。而且他投出的是正确的票。
这时只会在全为 \(0\) 时错误。
Sub 1&2:
我们可以分成 \((1),(2,3),(4,5,6,7),(8,9,10,11,12,13,14,15)\) 这些组,然后把一组看作一个人执行 Sub 3 的操作。
因为第一组一定不会投 \(0\) 票,所以投 \(0\) 票的组可以看作一半投 \(0\) 一半投 \(1\),投 \(2^{i-1}\) 票则可以看作每个人投了一票给同一个答案。
总共错误的情况是:\({1\choose 0}({2\choose 0}+{2\choose 2})({4\choose 0}+{4\choose 2}+{4\choose 4})\cdots=2^{1-1}\times 2^{2-1}\times2^{4-1}\times 2^{8-1}=2048\)
Sub 4:
很神秘,我的说明并不符合自然的思考路线,因此仅仅作为答案。
记每个位置对应为 \(x_i=0/1\),\(s=\sum x_i\)。
考虑把给异或和为 \(1\) 投票记为投 \(-1\) 票,\(0\) 记为投 \(1\) 票。那么最后结果和票数的正负有关。
我们想构造出一个关于 \(s\) 的 \(k\) 次多项式 \(p\),使得 \(p(s)\) 在最多的 \(s\) 上取到正确的符号(负则表示投票结果为 \(1\),相反亦然),此时将 \(s=\sum x_i\) 代入。对于一个大小小于等于 \(k\) 的可重集 \(S\),\(\prod_{i\in S}x_i\) 存在一个系数。如果这个集合中的所有 \(x_i\) 均为 \(1\),那么票数会加上这个系数。我们每次补上 \(S\) 在 \([1,n]\) 上的补集中的最小值直到 \(S\) 对应的不重集大小等于 \(k\),然后将这个系数加在这个集合对应的人的策略上(如果这些 \(x_i\) 均为 \(1\) 时,只有这个人会投出对应的票数)。
考虑到二项分布,结合 \(n=12,k=7\),最终得出我们在 \(s\in [3,9]\) 取到正确的值,\(s<3\) 时取负号,\(s>9\) 时取正号。因此在 \(s=0,2,11\) 时错误,总共有 \({12\choose 0}+{12\choose 2}+{12\choose 11}=79\) 种错误情况(逆天)。
考虑怎么构造 \(p(s)\),我们知道应该有:
那么不难构造出:
这个会存在小数的情况,但是我们可以在乘式的每一项中都乘 \(2\),总共会乘以 \(128\),就不会出现小数了。通过计算器算出乘以 \(128\) 后最终的票数不会超过 \(5\times 10^7\)。于是最终可以得出一个:
将 \(s=\sum x_i\) 代入,然后将系数化到相应的人处即可。
// Not afraid to dark.
#include <bits/stdc++.h>
using namespace std;
#define inline __inline__ __attribute__ ((always_inline))
//#define int long long
namespace io {
// io
};
inline void VOTE (int x, int y){
if (x == 1)
io::write (y), putchar (' ');
else
io::write (- y), putchar (' ');
}
namespace SUB_1_2 {
inline void Solve (){
static int bel[16], vs[5];
bel[0] = 1;
bel[1] = bel[2] = 2;
bel[3] = bel[4] = bel[5] = bel[6] = 3;
bel[7] = bel[8] = bel[9] = bel[10] = bel[11] = bel[12] = bel[13] = bel[14] = 4;
for (int i = 0;i < 15;++ i){
int xors;
for (int s = 0;s < (1 << 14);++ s){
vs[1] = vs[2] = vs[3] = vs[4] = xors = 0;
for (int j = 0;j < 14;++ j){
vs[bel[j + (j >= i)]] ^= s >> j & 1, xors ^= s >> j & 1;
// printf ("(%d %d)", j + (j >= i), bel[j + (j >= i)]);
}
for (int k = 1;k <= bel[i];++ k)
if (k == bel[i]){
// if (i == 0 && s == 0)
// io::write (xors ^ vs[k] ^ 1);
io::write (xors ^ vs[k] ^ 1);
} else
if (vs[k] == 1){
// if (s == 1)
// putchar ('0');
io::write (i & 1);
break;
}
}
putchar ('\n');
}
}
}
namespace SUB_3 {
inline void Solve (){
for (int i = 0;i < 15;++ i, putchar ('\n'))
for (int s = 0;s < (1 << 14);++ s){
int xors = 1;
for (int j = 0;j < 14;++ j)
xors ^= s >> j & 1;
for (int j = 0;j <= i;++ j)
if (i == j){
VOTE (xors, 1 << i);
} else
if (s >> j & 1){
VOTE (0, 0);
break;
}
putchar (' ');
}
}
}
namespace SUB_4 {
#define popcount(x) __builtin_popcount (x)
#define lowb(x) ((x) & - (x))
inline int lowbit (int x){
// printf ("%d\n", lowb (((1 << 12) - 1) ^ x));
return lowb (((1 << 12) - 1) ^ x);
}
int res[8];
int sum[1 << 12], ans[1 << 12][1 << 7];
inline void Solve (){
res[0] = 1;
for (int k = 0;k < 7;++ k){
for (int i = 6;i >= 0;-- i){
res[i + 1] -= res[i] * 2;
res[i] = res[i] * (7 + k * 2);
}
// for (int i = 0;i < 8;++ i)
// printf ("%d ", res[i]);
// puts ("");
}
sum[0] = 1;
for (int k = 0;k <= 7;++ k){
for (int i = (1 << 12) - 1;i >= 0;-- i){
if (sum[i] == 0)
continue;
int ss = sum[i];
int u = i, s = 0;
while (popcount (u) != 7){
// printf ("%d\n", u);
u |= lowbit (u);
}
for (int j = 11;j >= 0;-- j)
if (u >> j & 1){
s <<= 1;
s |= i >> j & 1;
}
for (int j = 0;j < (1 << 7);++ j)
if ((j & s) == s)
ans[u][j] += ss * res[k];
// ans[u][s] += ss * res[k];
sum[i] = 0;
for (int j = 0;j < 12;++ j)
sum[i | (1 << j)] += ss;
}
}
for (int i = 0;i < (1 << 12);++ i)
if (popcount (i) == 7){
for (int j = 0;j < (1 << 7);++ j)
io::write (ans[i][j]), putchar (' ');
putchar ('\n');
}
}
}
signed main (){
// freopen ("vote1.ans", "w", stdout);
// freopen ("vote2.ans", "w", stdout);
// freopen ("vote3.ans", "w", stdout);
// freopen ("vote4.ans", "w", stdout);
// SUB_1_2::Solve ();
// SUB_3::Solve ();
// SUB_4::Solve ();
return 0;
}
提交记录 #696324 - Universal Online Judge (uoj.ac)
UOJ #698. 枪打出头鸟
线性基求交和最大权的模板题目。
提交记录 #701518 - Universal Online Judge (uoj.ac)
[THUPC Final 2023 F] Freshman Dream
因为题目的限制,对于 \(B\) 每一列单独列出异或方程组(显然 \(B\) 每一列在各自的方案上是不影响的),移项使右边全部变为 \(0\)。把左边的方程组表示成矩阵 \(A\),可以发现对于 \(B\) 中的每一列 \(A\) 应该是均匀随机的,我们可以证明它的自由元有极大概率极小。
具体地,假设 \(A\) 的秩为 \(k\)。因为线性空间 \(A\) 和它的零空间有双射关系,也就是数量一致。我们估计 \(k\) 维零空间对应的 \(A\) 的个数一定小于等于 \({2^n\choose k}\)。同时 \(A\) 中所有向量都不落在钦定零空间上的概率是 \((\frac{1}{2^k})^n\),因为我们任意选择 \(n-k\) 个确定的元,剩下的 \(k\) 个位置都只有一种选择。
因此大概估算出 \(A\) 的秩为 \(k\) 的概率为 \(O(\frac{(2^n)^{\underline k}}{k!}\times \frac{1}{2^{kn}})=O(\frac 1 {k!})\)。
(口胡)