比赛链接
A. Sort
暴力
- k = 1 k=1 k=1 检查数组是否有序;
- k = 2 k=2 k=2 相当于再环上找个起点使得数组有序,直接判断;
- k ≥ 3 k\ge 3 k≥3 考虑插入排序,每次暴力找到第 i i i 小的数的位置 p i p_i pi ,构造 0 p i − 1 p i n 0\ p_i-1\ p_i\ n 0 pi−1 pi n 和排列 1 3 2 1\ 3\ 2 1 3 2 将第 i i i 小的放到最后面,重复 n n n 次即可,段数和 ≤ 3 n \le 3n ≤3n 满足要求。
O ( n 2 ) O(n^2) O(n2)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
int n, k, a[N], b[N];
void Solve() {
for (int i = 1; i <= n; ++i) scanf("%d", a + i), b[i] = a[i];
if (is_sorted(a + 1, a + n + 1))
return puts("0"), void();
if (k == 1) return puts("-2"), void();
if (k == 2) {
int i = n;
for (; i; --i) if (a[i - 1] > a[i]) break;
if (a[n] > a[1] || !is_sorted(a + 1, a + i)) puts("-2");
else printf("1\n2\n0 %d %d\n2 1\n", i - 1, n);
return;
}
vector<int> v;
int *c = b + 1;
sort(b + 1, b + n + 1);
for (int i = 1, j; i <= n; ++i, ++c) {
for (j = 1; j <= n - i + 1; ++j) if (a[j] == *c) break;
if (j == n) continue;
v.push_back(j);
for (int t = j; t <= n - i; ++t) a[t] = a[t + 1];
}
printf("%d\n", (int) v.size());
for (auto x: v)
if (x == 1) printf("2\n0 %d %d\n2 1\n", x, n);
else printf("3\n0 %d %d %d\n1 3 2\n", x - 1, x, n);
}
int main() {
int T; scanf("%d", &T);
while (T--) scanf("%d%d", &n, &k), Solve();
return 0;
}
树状数组
先预处理出最初第 i i i 小的数的位置 p i p_i pi ,将 i i i 挪到最后相当于对所有 p j > p i p_j>p_i pj>pi 减 1 ,那么第 i i i 小的数的真实位置应该为 p i − ∑ j < i [ p j < p i ] \displaystyle p_i-\sum_{j<i}[p_j<p_i] pi−j<i∑[pj<pi] 。用树状数组维护顺序对即可。
O ( n log n ) O(n\log n) O(nlogn)
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int n, k, a[N], c[N], p[N];
void mdy(int i, int w) { for (; i <= n; i += i & -i) c[i] += w; }
int qry(int i, int w = 0) { for (; i; i -= i & -i) w += c[i]; return w; }
void Solve() {
for (int i = 1; i <= n; ++i) scanf("%d", a + i), p[i] = i, c[i] = 0;
if (is_sorted(a + 1, a + n + 1))
return puts("0"), void();
if (k == 1) return puts("-2"), void();
if (k == 2) {
int i = n;
for (; i; --i) if (a[i - 1] > a[i]) break;
if (a[n] > a[1] || !is_sorted(a + 1, a + i)) puts("-2");
else printf("1\n2\n0 %d %d\n2 1\n", i - 1, n);
return;
}
vector<int> v;
sort(p + 1, p + n + 1, [&](int x, int y) { return a[x] < a[y]; });
for (int i = 1, j; i <= n; ++i) {
j = p[i] + qry(p[i]);
if (j == n) continue;
v.push_back(j), mdy(p[i], -1);
}
printf("%d\n", (int) v.size());
for (auto x: v)
if (x == 1) printf("2\n0 %d %d\n2 1\n", x, n);
else printf("3\n0 %d %d %d\n1 3 2\n", x - 1, x, n);
}
int main() {
int T; scanf("%d", &T);
while (T--) scanf("%d%d", &n, &k), Solve();
return 0;
}
B. Mailman
经过的路径总是一条欧拉回路,所以题目可以转化为动态修改某个点度数,并询问将所有点度数变为偶数的最小花费。
由于 n ≤ 3 n\le 3 n≤3 ,考虑对列建线段树,每个节点维护中间节点度数全为偶数,且左右侧节点度数状态分别为 S , T S,T S,T 的最小花费。
合并的时候 O ( 2 3 n ) O(2^{3n}) O(23n) 枚举左右和中间的状态,只用横向边连接。
注意当 L + 1 = R / m i d + 1 = R L+1=R\ /\ mid+1=R L+1=R / mid+1=R 的时候,中间连的边会改变左右端点的状态。
修改点度直接递归到叶子暴力修改然后向上合并即可。
O ( 2 3 n ( m + q ) log m ) O(2^{3n}(m+q)\log m) O(23n(m+q)logm)
#include<bits/stdc++.h>
#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
using ll = int64_t;
const ll Inf = 1e18;
const int N = 5e4 + 5;
int k, n, q, a[N][2]; ll b[N][8]; // 3 * 1e9 > int
void cmin(ll &x, ll y) { x > y ? x = y : 0; }
struct Seg {
ll tr[N << 2][8][8];
#define lc (p << 1)
#define rc (p << 1 | 1)
void Up(int p, int L, int R) {
int m = (L + R) >> 1;
fp(i, 0, k) fp(j, 0, k) tr[p][i][j] = Inf;
if (L + 1 == R) fp(i, 0, k) fp(j, 0, k) fp(t, 0, k) cmin(tr[p][i ^ t][j ^ t], tr[lc][i][i] + b[m][t] + tr[rc][j][j]);
else if (m + 1 == R) fp(i, 0, k) fp(j, 0, k) fp(t, 0, k) cmin(tr[p][i][j ^ t], tr[lc][i][t] + b[m][t] + tr[rc][j][j]);
else fp(i, 0, k) fp(j, 0, k) fp(t, 0, k) cmin(tr[p][i][j], tr[lc][i][t] + b[m][t] + tr[rc][t][j]);
// int x = m == L ? k : 0, y = m + 1 == R ? k : 0; // Unified, but twice as slow
// fp(i, 0, k) fp(j, 0, k) fp(t, 0, k) cmin(tr[p][i ^ (x & t)][j ^ (y & t)], tr[lc][i][x ? i : t] + b[m][t] + tr[rc][y ? j : t][j]);
}
void Build(int p, int L, int R) {
if (L == R) {
auto &w = tr[p];
int x = a[L][0], y = a[L][1];
fp(i, 0, k) fp(j, 0, k) w[i][j] = Inf;
if (k == 3) {
if (L == 1 || L == n) w[3][3] = x, w[0][0] = 0;
else w[0][0] = x, w[3][3] = 0;
} else {
if (L == 1 || L == n)
w[7][7] = x + y, w[4][4] = y, w[2][2] = 0, w[1][1] = x;
else w[0][0] = x + y, w[3][3] = y, w[5][5] = 0, w[6][6] = x;
}
return;
}
int m = (L + R) >> 1;
Build(lc, L, m), Build(rc, m + 1, R), Up(p, L, R);
}
void mdy(int p, int L,int R, int x,int y){
if (L == R) {
static ll w[8];
x = 1 << (x - 1);
fp(i, 0, k) w[i ^ x] = tr[p][i][i];
fp(i, 0, k) tr[p][i][i] = w[i];
return;
}
int m = (L + R) >> 1;
y <= m ? mdy(lc, L, m, x, y) : mdy(rc, m + 1, R, x, y), Up(p, L, R);
}
}T;
int main() {
ll ans = 0;
scanf("%d%d%d", &k, &n, &q);
fp(i, 0, k - 2) fp(j, 1, n) scanf("%d", a[j] + i), ans += a[j][i];
fp(i, 0, k - 1) fp(j, 1, n - 1) scanf("%lld", b[j] + (1 << i)), ans += b[j][1 << i];
fp(j, 1, n - 1) fp(i, 0, 7) b[j][i] = b[j][i & (i - 1)] + b[j][i & -i];
k = (1 << k) - 1, T.Build(1, 1, n);
for (int x1, y1, x2, y2, w; q--;) {
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &w), ans += w;
T.mdy(1, 1, n, x1, y1), T.mdy(1, 1, n, x2, y2);
printf("%lld\n", ans + T.tr[1][0][0]);
}
return 0;
}
C. Range Subsequence
莫队+分块 1
考虑如何从 [ l + 1 , r ] [l+1,r] [l+1,r] 的答案求出 [ l , r ] [l,r] [l,r] 的答案。
设 f ( l , r ) f(l,r) f(l,r) 表示 [ l , r ] [l,r] [l,r] 中以 a l a_l al 开头的最长 Range 子序列长度, b i b_i bi 表示 i i i 所在的块。
则 a n s l , r = a n s l + 1 , r + f ( l , r ) − f ( n x l , r ) ans_{l,r}=ans_{l+1,r}+f(l,r)-f(nx_l,r) ansl,r=ansl+1,r+f(l,r)−f(nxl,r) ,其中 n x l nx_l nxl 表示下一个值等于 a l a_l al 的位置。
考虑分块预处理,对于所有 i i i ,暴力求出 f 1 ( i , k ) = f ( i , k n ) , k ≥ b i f_1(i,k)=f(i,k\sqrt n),k\ge b_i f1(i,k)=f(i,kn),k≥bi 和 f 2 ( i , j ) = f ( i , i + j ) f_2(i,j)=f(i,i+j) f2(i,j)=f(i,i+j) ,其中 i , i + j i,i+j i,i+j 在同一个块中。
对于每个块
t
t
t 维护块内每个数第一次和最后一次出现的位置
p
t
,
x
,
q
t
,
x
p_{t,x},q_{t,x}
pt,x,qt,x ,则有
f
1
(
i
,
k
)
=
f
1
(
i
,
k
−
1
)
+
f
1
(
p
k
,
a
i
+
f
1
(
i
,
k
−
1
)
,
k
)
f_1(i,k)=f_1(i,k-1)+f_1(p_{k,a_i+f_1(i,k-1)},k)
f1(i,k)=f1(i,k−1)+f1(pk,ai+f1(i,k−1),k)
同理若
l
,
r
l,r
l,r 不在一个块中,则
f
(
l
,
r
)
=
f
1
(
l
,
b
r
−
1
)
+
f
2
(
p
b
r
,
a
l
+
f
1
(
l
,
b
r
−
1
)
,
r
)
f(l,r)=f_1(l,b_r-1)+f_2(p_{b_r,a_l+f_1(l,b_r-1)},r)
f(l,r)=f1(l,br−1)+f2(pbr,al+f1(l,br−1),r)
对于从
[
l
,
r
−
1
]
[l,r-1]
[l,r−1] 的答案求出
[
l
,
r
]
[l,r]
[l,r] ,对称地维护以
a
r
a_r
ar 结尾的答案
g
(
l
,
r
)
g(l,r)
g(l,r) ,同样也可以得到
g
1
(
i
,
k
)
=
g
(
k
n
,
i
)
,
k
<
b
i
g_1(i,k)=g(k\sqrt n,i),k<b_i
g1(i,k)=g(kn,i),k<bi 和
g
2
(
i
,
j
)
=
g
(
i
−
j
,
i
)
g_2(i,j)=g(i-j,i)
g2(i,j)=g(i−j,i) 。
注意到 f 1 , g 1 f_1,g_1 f1,g1 以及 f 2 , g 2 f_2,g_2 f2,g2 的空间互补,所以可以压缩空间到 2 n n ≈ 241 M B 2n\sqrt n\approx 241{\ \rm MB} 2nn≈241 MB ,所以 p , q p,q p,q 可以直接开成两个 n n n\sqrt n nn 的桶(因为 u n o r d e r e d _ m a p \rm unordered\_map unordered_map 的 c o u n t \rm count count 太慢过不了),总空间约 500 M B 500\ \rm MB 500 MB 。
O ( n n + n m ) O(n\sqrt n+n\sqrt m) O(nn+nm)
#include<bits/stdc++.h>
#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
#define fd(i, a, b) for(int i = (a), _##i = (b) - 1; i > _##i; --i)
using namespace std;
using ll = int64_t;
const int N = 1e5 + 5;
struct qry { int l, r, id; } q[N];
int a[N], b[N], pr[N], nx[N];
vector<int> f1[N], f2[N], f3[N], p1[315], p2[315];
int main() {
int n, m, B, S, T; ll val = 0;
scanf("%d%d", &n, &m), B = sqrt(n) + 1, S = n / sqrt(m) + 1, T = n / B + 1;
vector<int> pos(n); vector<ll> ans(m);
fp(i, 1, n) scanf("%d", a + i), --a[i];
fp(i, 0, m - 1) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
sort(q, q + m, [&](qry a, qry b) { return a.l / S == b.l / S ? (a.l / S & 1 ? a.r > b.r : a.r < b.r) : a.l < b.l; });
fp(t, 0, T - 1) {
int L = t * B + 1, R = min(n, L + B - 1);
p1[t].assign(n, 0), p2[t] = p1[t];
fp(i, L, R) {
auto &f = f2[i];
pr[i] = pos[a[i]], pos[a[i]] = i, b[i] = t;
f.assign(R - i + 1, 0), f[0] = 1, p2[t][a[i]] = i;
fp(j, i + 1, R) f[j - i] = f[j - i - 1] + (a[j] == a[i] + f[j - i - 1]);
f1[i].assign(T + 1, 0), f1[i][t + 1] = f[R - i];
}
fd(i, R, L) {
auto &f = f3[i];
f.assign(i - L + 1, 0), f[0] = 1, p1[t][a[i]] = i;
fd(j, i - 1, L) f[i - j] = f[i - j - 1] + (a[j] == a[i] - f[i - j - 1]);
f1[i][t] = f[i - L];
fd(k, b[i] - 1, 0) f1[i][k] = f1[i][k + 1] + (a[i] >= f1[i][k + 1] && p2[k][a[i] - f1[i][k + 1]] ? f3[p2[k][a[i] - f1[i][k + 1]]].back() : 0);
}
}
pos.assign(n, n + 1);
fd(i, n, 1) {
nx[i] = pos[a[i]], pos[a[i]] = i;
fp(k, b[i] + 1, T - 1) f1[i][k + 1] = f1[i][k] + (a[i] + f1[i][k] < n && p1[k][a[i] + f1[i][k]] ? f2[p1[k][a[i] + f1[i][k]]].back() : 0);
}
auto f = [&](int L, int R) {
if (L > R) return 0;
if (b[L] == b[R]) return f2[L][R - L];
int w = f1[L][b[R]], nx = a[L] + w, p;
if (nx < n && (p = p1[b[R]][nx]) && p <= R) w += f2[p][R - p];
return w;
};
auto g = [&](int L, int R) {
if (L > R) return 0;
if (b[L] == b[R]) return f3[R][R - L];
int w = f1[R][b[L] + 1], nx = a[R] - w, p;
if (nx >= 0 && (p = p2[b[L]][nx]) && p >= L) w += f3[p][p - L];
return w;
};
int L = 1, R = 0;
fp(i, 0, m - 1) {
int ql = q[i].l, qr = q[i].r;
while (L > ql) --L, val += f(L, R) - f(nx[L], R);
while (R < qr) ++R, val += g(L, R) - g(L, pr[R]);
while (L < ql) val -= f(L, R) - f(nx[L], R), L++;
while (R > qr) val -= g(L, R) - g(L, pr[R]), R--;
ans[q[i].id] = val;
}
for (auto w: ans) printf("%lld\n", w);
return 0;
}
莫队+分块 2
上面那种写法太麻烦了,而且还卡空间,卡常。
考虑将 f 1 ( i , k ) f_1(i,k) f1(i,k) 的定义改为值 a i + f ( i , k n ) a_i+f(i,k\sqrt n) ai+f(i,kn) 的位置,则 f ( i , k n ) = a f 1 ( i , k ) − a i + 1 f(i,k\sqrt n)=a_{f_1(i,k)}-a_i+1 f(i,kn)=af1(i,k)−ai+1。;记 n x t i nxt_i nxti 表示下一个 a i + 1 a_i+1 ai+1 出现的位置。
从大到小枚举 i i i ,从 i i i 开始,在块内不断跳 n x t nxt nxt 得到 f 1 ( i , b i + 1 ) f_1(i,b_i+1) f1(i,bi+1) ,然后再跳一次 n x t nxt nxt 得到下一个位置 p ≥ i p\ge i p≥i ,则 f 1 ( p , k ) f_1(p,k) f1(p,k) 已经求出,于是有
f 1 ( i , k ) = { f 1 ( i , b i + 1 ) b p ≥ k f 1 ( p , k ) b p < k f_1(i,k)=\left\{\begin{aligned} &f_1(i,b_i+1) & b_p\ge k\\ &f_1(p,k) & b_p<k \end{aligned}\right. f1(i,k)={f1(i,bi+1)f1(p,k)bp≥kbp<k
其他数组也可以做同样的简化。
O ( n n + n m ) O(n\sqrt n+n\sqrt m) O(nn+nm)
#include<bits/stdc++.h>
#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
#define fd(i, a, b) for(int i = (a), _##i = (b) - 1; i > _##i; --i)
using namespace std;
const int N = 1e5 + 5;
using ll = int64_t;
using arr = int[N];
struct qry { int l, r, id; } q[N];
arr a, b, p1, n1, p2, n2;
vector<int> f1[N];
vector<short> f2[N], f3[N];
int f(int L, int R) {
if (L > R) return 0;
if (b[L] == b[R]) return f2[L][R - L];
int p = f1[L][b[R]], w = a[p] - a[L] + 1;
if ((p = n2[p]) <= R) w += f2[p][R - p];
return w;
}
int g(int L, int R) {
if (L > R) return 0;
if (b[L] == b[R]) return f3[R][R - L];
int p = f1[R][b[L] + 1], w = a[R] - a[p] + 1;
if ((p = p2[p]) >= L) w += f3[p][p - L];
return w;
}
int main() {
int n, m, B, S, T; ll val = 0;
scanf("%d%d", &n, &m);
b[0] = -1, b[n + 1] = n + 1;
vector<int> pos(n + 1); vector<ll> ans(m);
B = sqrt(n) + 1, S = n / sqrt(m) + 1, T = n / B + 1;
fp(i, 1, n) scanf("%d", a + i), f1[i].assign(T + 1, 0);
fp(i, 0, m - 1) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
sort(q, q + m, [&](qry a, qry b) { return a.l / S == b.l / S ? (a.l / S & 1 ? a.r > b.r : a.r < b.r) : a.l < b.l; });
fp(t, 0, T - 1) {
int L = t * B + 1, R = min(n, L + B - 1), x, p;
fp(i, L, R) {
f3[i].assign(i - L + 1, 1), b[i] = t;
p1[i] = pos[x = a[i]], pos[x] = i, p = p2[i] = pos[x - 1];
fd(j, p, L) f3[i][i - j] = f3[p][p - j] + 1;
for (p = i; p >= L; p = p2[p]) f1[i][t] = p;
fd(k, t - 1, 0) f1[i][k] = b[p] >= k ? f1[p][k] : f1[i][t];
}
}
pos.assign(n + 2, n + 1);
fd(t, T - 1, 0) {
int L = t * B + 1, R = min(n, L + B - 1), x, p;
fd(i, R, L) {
f2[i].assign(R - i + 1, 1);
n1[i] = pos[x = a[i]], pos[x] = i, p = n2[i] = pos[x + 1];
fp(j, p, R) f2[i][j - i] = f2[p][j - p] + 1;
for (p = i; p <= R; p = n2[p]) f1[i][t + 1] = p;
fp(k, t + 2, T - 1) f1[i][k] = b[p] < k ? f1[p][k] : f1[i][t + 1];
}
}
int L = 1, R = 0;
fp(i, 0, m - 1) {
int ql = q[i].l, qr = q[i].r;
while (L > ql) --L, val += f(L, R) - f(n1[L], R);
while (R < qr) ++R, val += g(L, R) - g(L, p1[R]);
while (L < ql) val -= f(L, R) - f(n1[L], R), L++;
while (R > qr) val -= g(L, R) - g(L, p1[R]), R--;
ans[q[i].id] = val;
}
for (auto w: ans) printf("%lld\n", w);
return 0;
}
回滚莫队
考虑只加不删的回滚莫队。
设 f i f_i fi 表示对于以值 i i i 结尾的最长子序列为 f i , f i + 1 , . . . , i = d e f [ f i , i ] f_i,f_i+1,...,i\overset{\rm def}{=}[f_i,i] fi,fi+1,...,i=def[fi,i] , c i = ∑ j [ f j = i ] \displaystyle c_i=\sum_j [f_j=i] ci=j∑[fj=i] 。
若在右边加入一个值 x x x ,且 f x > f x − 1 f_x>f_{x-1} fx>fx−1 ,则可以多出 f x − f x − 1 f_x-f_{x-1} fx−fx−1 个不同的连续子序列,即 [ f x − 1 , x ] , [ f x − 1 + 1 , x ] , . . . , [ f x − 1 , x ] [f_{x-1},x],[f_{x-1}+1,x],...,[f_x-1,x] [fx−1,x],[fx−1+1,x],...,[fx−1,x] ,同时更新 f x , c f x , c f x − 1 f_x,c_{f_x},c_{f_{x-1}} fx,cfx,cfx−1 的值即可。
考虑右端点固定的情况下再左边插入一个值 x x x ,那么所有 f i = x + 1 f_i=x+1 fi=x+1 的连续序列都可以向前拓展一位到 x x x ,对答案贡献为 c x + 1 c_{x+1} cx+1 ,同时更新 c x , c x + 1 c_x,c_{x+1} cx,cx+1 即可。
O ( n n + m n ) O(n\sqrt n+m\sqrt n) O(nn+mn)
#include <bits/stdc++.h>
#define fp(i, a, b) for(int (i) = (a), i##_ = (b) + 1; (i) < i##_; ++(i))
#define fd(i, a, b) for(int (i) = (a), i##_ = (b) - 1; (i) > i##_; --(i))
using namespace std;
const int N = 1e5 + 5;
using ll = long long;
int n, m, B, a[N], b[N];
struct Qry { int l, r, id; } q[N];
struct addMoTeam {
int tp{}, f[N]{};
ll ans{}, c[N]{};
pair<ll *, ll> s[N];
void init() { ans = 0; fp(i, 0, n) f[i] = i + 1, c[i + 1] = 1; }
int calc(int l, int r) {
int res = 0, p;
fp(i, l, r) if (p = a[i], f[p] > f[p - 1])
res += f[p] - f[p - 1], f[p] = f[p - 1];
fp(i, l, r) f[a[i]] = a[i] + 1;
return res;
}
void addR(int x) {
if (f[x] <= f[x - 1]) return;
ans += f[x] - f[x - 1], --c[f[x]], ++c[f[x - 1]], f[x] = f[x - 1];
}
void addL(int x) {
if (!c[x + 1]) return;
mdy(ans, ans + c[x + 1]), mdy(c[x], c[x] + c[x + 1]), mdy(c[x + 1], 0);
}
void mdy(ll &x, ll y) { s[++tp] = {&x, x}, x = y; }
void roll() { for (; tp; tp--) *s[tp].first = s[tp].second; }
} D;
int main() {
scanf("%d%d", &n, &m), B = sqrt(n) + 1;
fp(i, 1, n) scanf("%d", a + i), b[i] = i / B;
fp(i, 0, m - 1) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
sort(q, q + m, [](Qry x, Qry y) { return b[x.l] == b[y.l] ? x.r < y.r : x.l < y.l; });
vector<ll> ans(m);
for (int R = B - 1, i = 0; R - B < n; R += B) {
D.init();
for (; i < m && q[i].r <= R; ++i) ans[q[i].id] = D.calc(q[i].l, q[i].r);
for (int p = R; i < m && q[i].l <= R; ++i) {
while (p < q[i].r) D.addR(a[++p]);
fd(l, R, q[i].l) D.addL(a[l]);
ans[q[i].id] = D.ans, D.roll();
}
}
for (auto x: ans) printf("%lld\n", x);
return 0;
}
D. Linear Algebra
设
g
(
x
)
=
∑
i
=
0
2
n
c
i
x
i
,
f
(
x
)
=
∑
i
=
0
n
a
i
x
i
\displaystyle g(x)=\sum_{i=0}^{2n}c_ix^i, f(x)=\sum_{i=0}^na_ix^i
g(x)=i=0∑2ncixi,f(x)=i=0∑naixi ,则
f
(
x
+
1
)
=
∑
i
=
0
n
x
i
∑
j
≥
i
(
j
i
)
a
j
=
d
e
f
∑
i
=
0
n
b
i
x
i
\displaystyle f(x+1)=\sum_{i=0}^nx^i\sum_{j\ge i}{j\choose i}a_j\overset{\rm def}{=}\sum_{i=0}^nb_ix^i
f(x+1)=i=0∑nxij≥i∑(ij)aj=defi=0∑nbixi 。由
c
2
n
=
1
c_{2n}=1
c2n=1 可得
a
n
=
b
n
=
1
a_n=b_n=1
an=bn=1 。考虑
c
n
+
k
c_{n+k}
cn+k
c
n
+
k
=
[
x
n
+
k
]
f
(
x
)
f
(
x
+
1
)
=
∑
i
=
0
n
−
k
a
n
−
i
b
k
+
i
=
a
n
b
k
+
a
k
b
n
+
∑
i
=
1
n
−
k
−
1
a
n
−
i
b
k
+
i
=
2
a
n
a
k
+
a
n
∑
i
>
k
(
i
k
)
a
i
+
∑
i
=
k
+
1
n
−
1
a
n
−
i
+
k
b
i
⇒
{
k
!
(
b
k
−
a
k
)
=
∑
i
=
k
+
1
n
i
!
a
i
⋅
1
(
i
−
k
)
!
2
a
k
−
c
n
+
k
=
−
(
b
k
−
a
k
)
−
∑
i
=
k
+
1
n
−
1
a
n
−
i
+
k
b
i
\begin{aligned} c_{n+k}=&[x^{n+k}]f(x)f(x+1)\\ &=\sum_{i=0}^{n-k}a_{n-i}b_{k+i}\\ &=a_nb_k+a_kb_n+\sum_{i=1}^{n-k-1}a_{n-i}b_{k+i}\\ &=2a_na_k+a_n\sum_{i>k}{i\choose k}a_i+\sum_{i=k+1}^{n-1}a_{n-i+k}b_i \end{aligned}\\ \Rightarrow\left\{ \begin{aligned} &k!(b_k-a_k)=\sum_{i=k+1}^ni!a_i\cdot\frac1{(i-k)!}\\ &2a_k-c_{n+k}=-(b_k-a_k)-\sum_{i=k+1}^{n-1}a_{n-i+k}b_i\\ \end{aligned}\right.\\
cn+k=[xn+k]f(x)f(x+1)=i=0∑n−kan−ibk+i=anbk+akbn+i=1∑n−k−1an−ibk+i=2anak+ani>k∑(ki)ai+i=k+1∑n−1an−i+kbi⇒⎩⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎧k!(bk−ak)=i=k+1∑ni!ai⋅(i−k)!12ak−cn+k=−(bk−ak)−i=k+1∑n−1an−i+kbi
考虑分治,其中 b k − a k b_k-a_k bk−ak 的形式是标准的分治 F F T \rm FFT FFT 形式,可以直接算。
考虑 ∑ i = k + 1 n − 1 a n − i + k b i \displaystyle\sum_{i=k+1}^{n-1}a_{n-i+k}b_i i=k+1∑n−1an−i+kbi ,对于区间 [ L , R ) [L,R) [L,R) ,令 k = max ( m , n − R + L ) k=\max(m,\ n-R+L) k=max(m, n−R+L) 。考虑 [ m , R ) [m,R) [m,R) 对 [ L , m ) [L,m) [L,m) 的贡献,可以分成 3 3 3 个部分: a [ m , R ) × b [ k , n ) , b [ m , R ) × a [ k , n ) a_{[m,R)}\times b_{[k,n)},\ b_{[m,R)}\times a_{[k,n)} a[m,R)×b[k,n), b[m,R)×a[k,n) ,以及减去前两者重复的部分 a [ k , R ) × b [ k , R ) a_{[k,R)}\times b_{[k,R)} a[k,R)×b[k,R) 。
O ( n log 2 n ) O(n\log^2n) O(nlog2n)
#include<bits/stdc++.h>
#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
#define fd(i, a, b) for(int i = (a), _##i = (b) - 1; i > _##i; --i)
using namespace std;
const int N = 1e5 + 5, P = 998244353, I2 = (P + 1) >> 1;
using ll = int64_t;
#define MUL(a, b) ((ll) (a) * (b) % P)
#define ADD(a, b) (((a) += (b)) >= P ? (a) -=P : 0) // (a += b) %= P
#define SUB(a, b) (((a) -= (b)) < 0 ? (a) += P: 0) // ((a -= b) += P) %= P
int POW(ll a, int b = P - 2, ll x = 1) { for (; b; b >>= 1, a = a * a % P) if (b & 1) x = x * a % P; return x; }
namespace NTT {
const int g = 3;
vector<int> Omega(int L) {
int wn = POW(g, P / L);
vector<int> w(L); w[L >> 1] = 1;
fp(i, L / 2 + 1, L - 1) w[i] = MUL(w[i - 1], wn);
fd(i, L / 2 - 1, 1) w[i] = w[i << 1];
return w;
}
auto W = Omega(1 << 18); // NOLINT
void calc1(int &x, int &y, ll z) { z = (x - y + P) * z % P, ADD(x, y), y = z;}
void calc2(int &x, int &y, ll z) { z = y * z % P, y = x, SUB(y, z), ADD(x, z); }
void DIF(int *a, int n) {
for (int k = n >> 1; k; k >>= 1)
for (int i = 0; i < n; i += k << 1)
fp(j, 0, k - 1) calc1(a[i + j], a[i + j + k], W[j + k]);
}
void IDIT(int *a, int n){
for (int k = 1; k < n; k <<= 1)
for (int i = 0; i < n; i += k << 1)
fp(j, 0, k - 1) calc2(a[i + j], a[i + j + k], W[j + k]);
int Inv = P - (P - 1) / n;
fp(i, 0, n - 1) a[i] = MUL(a[i], Inv);
reverse(a + 1, a + n);
}
}
namespace Polynomial {
using Poly = std::vector<int>;
void DFT(Poly &a) { NTT::DIF(a.data(), a.size()); }
void IDFT(Poly &a) { NTT::IDIT(a.data(), a.size()); }
int getLen(int n) { return 1 << (32 - __builtin_clz(n)); }
Poly &dot(Poly &a, Poly &b) {
fp(i, 0, a.size() - 1) a[i] = MUL(a[i], b[i]);
return a;
}
Poly operator*(Poly a, Poly b) {
int n = a.size() + b.size() - 1;
if (a.size() <= 8 || b.size() <= 8) {
Poly c(n);
fp(i, 0, a.size() - 1) fp(j, 0, b.size() - 1)
c[i + j] = (c[i + j] + (ll) a[i] * b[j]) % P;
return c;
}
int L = getLen(n);
a.resize(L), b.resize(L);
DFT(a), DFT(b), dot(a, b), IDFT(a);
return a.resize(n), a;
}
Poly dcov(Poly &a, Poly b) { return reverse(b.begin(), b.end()), a * b; }
}
using namespace Polynomial;
int n, fac[N], ifac[N], a[N], b[N], g[2 * N];
void cdq(int L, int R) {
if (L + 1 == R) {
b[L] = MUL(b[L], ifac[L]);
a[L] = L == n ? 1 : MUL(a[L] - b[L] + P, I2);
ADD(b[L], a[L]);
return;
}
int m = (L + R) >> 1, k = max(m, n - R + L);
cdq(m, R);
Poly A = Poly(a + k, a + n) * Poly(b + m, b + R);
fp(i, max(L, k + m - n), m - 1) SUB(a[i], A[i - k - m + n]);
A = Poly(b + k, b + n) * Poly(a + m, a + R);
fp(i, max(L, k + m - n), m - 1) SUB(a[i], A[i - k - m + n]);
if (k < R) {
A = Poly(a + k, a + R) * Poly(b + k, b + R);
fp(i, max(L, 2 * k - n), min(m, 2 * R - n - 1) - 1) ADD(a[i], A[i - 2 * k + n]);
}
A.resize(R - m);
fp(i, m, R - 1) A[i - m] = MUL(a[i], fac[i]);
A = dcov(A, Poly(ifac, ifac + R - L));
fp(i, L, m - 1) ADD(b[i], A[i - m + R - L - 1]);
cdq(L, m);
}
int main() {
scanf("%d", &n), fac[0] = 1;
fp(i, 1, n) fac[i] = MUL(fac[i - 1], i);
ifac[n] = POW(fac[n]);
fd(i, n, 1) ifac[i - 1] = MUL(ifac[i], i);
fp(i, 0, 2 * n) scanf("%d", g + i);
copy(g + n, g + 2 * n + 1, a), cdq(0, n + 1);
Poly f = Poly(a, a + n + 1) * Poly(b, b + n + 1);
fp(i, 0, 2 * n) if (f[i] != g[i]) return printf("No"), 0;
puts("Yes");
fp(i, 0, n) printf("%d%c", a[i], " \n"[i == n]);
return 0;
}
E. Nearest Point
考虑固定一个点 i i i ,并以 i i i 为参考系。
设 P r o j ( j , α ) {\rm Proj}(j,\alpha) Proj(j,α) 表示 j j j 在 v α = ( cos α , sin α ) v_{\alpha}=(\cos\alpha,\sin\alpha) vα=(cosα,sinα) 方向上的投影长。
对于任意两个向量 j , k j,k j,k ,存在 4 个临界角度 α \alpha α 使得 P r o j ( j , α ) = P r o j ( k , α ) {\rm Proj}(j,\alpha)={\rm Proj}(k,\alpha) Proj(j,α)=Proj(k,α) ,易证 v α ⊥ j + k v_\alpha\perp j+k vα⊥j+k 或 v α ⊥ j − k v_\alpha\perp j-k vα⊥j−k (画图理解一下)。
一共有 O ( n 2 ) O(n^2) O(n2) 个临界角,把 [ − π , π ) [-\pi,\pi) [−π,π) 分成了 O ( n 2 ) O(n^2) O(n2) 段,每一段内 i i i 的最近点是不变的。所以可以每一段内任取一个角度,算出最近点,并把这一段的概率都算到那个点上即可。
O ( n 4 log n ) O(n^4\log n) O(n4logn)
#include<bits/stdc++.h>
#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
using db = double;
const db Pi = acos(-1);
struct Vec {
int x, y;
Vec operator+(Vec b) const { return {x + b.x, y + b.y}; }
Vec operator-(Vec b) const { return {x - b.x, y - b.y}; }
db v_deg() const { return atan2(x, -y); }
};
int main() {
int n; scanf("%d", &n);
vector<db> d, ans(n);
vector<Vec> a(n), b(n);
for (auto &p: a) scanf("%d%d", &p.x, &p.y);
auto Proj = [&](db x, db y, Vec c) { return abs(x * c.x + y * c.y); };
fp(i, 0, n - 1) {
ans.assign(n, 0), d = {-Pi, Pi};
fp(j, 0, n - 1) b[j] = a[j] - a[i];
fp(j, 0, n - 1) fp(k, j + 1, n - 1)
d.push_back((b[j] - b[k]).v_deg()), d.push_back((b[j] + b[k]).v_deg());
for (auto deg: d) d.push_back(deg + (deg < 0 ? Pi : -Pi));
sort(d.begin(), d.end());
fp(t, 1, d.size() - 1) {
if (d[t] - d[t - 1] < 1e-11) continue;
int k = 0; db L = d[t] - d[t - 1], m = d[t - 1] + L * 0.5, x = cos(m), y = sin(m), mn = 1e18, w;
fp(j, 0, n - 1) if (j != i && mn - (w = Proj(x, y, b[j])) > 1e-9) mn = w, k = j;
ans[k] += L;
}
fp(j, 0, n - 1) printf("%.9lf%c", i == j ? 0 : ans[j] / Pi * .5, " \n"[j == n - 1]);
}
return 0;
}
F. Leapfrog
题目等价于找到一个排列 p p p ,最大化 a p n + ∑ i = 1 n − 1 b p i t p i n − i \displaystyle a_{p_n}+\sum_{i=1}^{n-1}b_{p_i}t_{p_i}^{n-i} apn+i=1∑n−1bpitpin−i
设 p ′ p' p′ 为以 t t t 第一关键字降序, b b b 第二关键字升序, a a a 第三关键字升序排序后的排列。
注意到 b ≤ 1 0 4 , t ∈ { 2 , 3 } b\le10^4,t\in\{2,3\} b≤104,t∈{2,3} ,而 1 0 4 × 2 23 < 3 23 10^4\times2^{23}<3^{23} 104×223<323 ,所以 p ′ p' p′ 与最优排列的前 n − 23 n-23 n−23 项一定相同。
考虑用 D P \rm DP DP 来求出最后剩下的 m m m 个物品的最优排列。
设 f i , j , 0 / 1 f_{i,j,0/1} fi,j,0/1 表示放了 i i i 个物品,其中有 j j j 个 t = 2 t=2 t=2 的物品,以及是否有物品放到了最后的最优答案, x i / y i x_i/y_i xi/yi 分别表示第 i i i 个 t = 2 / 3 t=2/3 t=2/3 的物品的位置。
则有
f
i
,
j
,
0
=
max
{
f
i
−
1
,
j
,
0
+
b
x
i
−
j
2
m
−
i
,
f
i
−
1
,
j
−
1
,
0
+
b
y
j
3
m
−
i
}
f
i
,
j
,
1
=
max
{
f
i
−
1
,
j
,
1
+
b
x
i
−
j
2
m
−
i
+
1
,
f
i
−
1
,
j
−
1
,
1
+
b
y
j
3
m
−
i
+
1
,
f
i
−
1
,
j
,
0
+
a
x
i
−
j
,
f
i
−
1
,
j
−
1
,
0
+
a
y
j
}
\begin{aligned} f_{i,j,0}&=\max\{f_{i-1,j,0}+b_{x_{i-j}}2^{m-i}, f_{i-1,j-1,0}+b_{y_j}3^{m-i}\}\\ f_{i,j,1}&=\max\{f_{i-1,j,1}+b_{x_{i-j}}2^{m-i+1}, f_{i-1,j-1,1}+b_{y_j}3^{m-i+1},f_{i-1,j,0}+a_{x_{i-j}}, f_{i-1,j-1,0}+a_{y_j}\}\\ \end{aligned}
fi,j,0fi,j,1=max{fi−1,j,0+bxi−j2m−i,fi−1,j−1,0+byj3m−i}=max{fi−1,j,1+bxi−j2m−i+1,fi−1,j−1,1+byj3m−i+1,fi−1,j,0+axi−j,fi−1,j−1,0+ayj}
O
(
n
log
n
+
2
3
2
)
O(n\log n+23^2)
O(nlogn+232)
#include<bits/stdc++.h>
#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
using ll = int64_t;
const int N = 1e5 + 5, P = 998244353;
struct Data { int a, b, t; } p[N];
int n; ll f[24][24][2], p2[N], p3[N];
void cmax(ll &x, ll y) { x < y ? x = y : 0; }
ll dp(vector<Data> &a, vector<Data> &b) {
int A = a.size(), B = b.size();
memset(f, 0x80, sizeof f), f[0][0][0] = 0, n = A + B;
fp(i, 0, n - 1) fp(j, 0, min(i, B)) {
if (i - j < A) {
cmax(f[i + 1][j][1], f[i][j][0] + a[i - j].a);
fp(k, 0, 1) cmax(f[i + 1][j][k], f[i][j][k] + a[i - j].b * p2[n - i - 1 + k]); // + k !!
}
if (j == B) continue;
cmax(f[i + 1][j + 1][1], f[i][j][0] + b[j].a);
fp(k, 0, 1) cmax(f[i + 1][j + 1][k], f[i][j][k] + b[j].b * p3[n - i - 1 + k]); // + k !!
}
return f[n][B][1];
}
void Solve() {
fp(i, 1, n) scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].t);
sort(p + 1, p + n + 1, [](Data x, Data y) { return x.t == y.t ? (x.b == y.b ? x.a < y.a : x.b > y.b) : x.t > y.t; });
ll ans = 0, m = max(0, n - 23); vector<Data>a, b;
fp(i, 1, m) ans += (ll) p[i].b * (p[i].t == 2 ? p2[n - i] : p3[n - i]) % P;
fp(i, m + 1, n) p[i].t == 2 ? a.push_back(p[i]) : b.push_back(p[i]);
printf("%lld\n", (ans + dp(a, b)) % P);
}
int main() {
p2[0] = p3[0] = 1;
fp(i, 1, 23) p2[i] = 2 * p2[i - 1], p3[i] = 3 * p3[i - 1];
fp(i, 24, N - 1) p2[i] = 2 * p2[i - 1] % P, p3[i] = 3 * p3[i - 1] % P;
int T; scanf("%d", &T);
while (T--) scanf("%d", &n), Solve();
return 0;
}
G. Limit
直接泰勒展开,原式可化为
x
−
t
∑
i
=
1
n
a
i
∑
j
≥
1
(
−
1
)
j
+
1
(
b
i
x
)
j
j
=
∑
j
≥
1
1
j
x
j
−
t
∑
i
=
1
n
(
−
1
)
j
+
1
a
i
b
i
j
x^{-t}\sum_{i=1}^na_i\sum_{j\ge1}(-1)^{j+1}\frac{(b_ix)^j}{j}=\sum_{j\ge1}\frac1jx^{j-t}\sum_{i=1}^n(-1)^{j+1}a_ib_i^j
x−ti=1∑naij≥1∑(−1)j+1j(bix)j=j≥1∑j1xj−ti=1∑n(−1)j+1aibij
算出所有系数,根据极限定义输出答案即可。
O ( t n ) O(tn) O(tn)
#include<bits/stdc++.h>
using namespace std;
using ll = int64_t;
int main() {
int n, t;
scanf("%d%d", &n, &t);
if (!t)return printf("0"), 0;
vector<ll> a(6);
ll x, y, z, g, b = t;
for (int i = 0; i < n; ++i) {
scanf("%lld%lld", &x, &y), z = y;
for (int j = 1; j < 6; ++j) a[j] += (j & 1 ? 1 : -1) * x * z, z *= y;
}
for (int i = 1; i < t; ++i)
if (a[i]) return printf("infinity"), 0;
g = __gcd(a[t], b), a[t] /= g, b /= g;
if (abs(b) == 1) printf("%lld", a[t]);
else printf("%lld/%d", a[t], b);
return 0;
}
H. Set
随机
每次随机在 S S S 中随机选取 l = ⌈ 512 r ⌉ \displaystyle l=\left\lceil\frac{512}r\right\rceil l=⌈r512⌉ 个数即可。
考虑出错的概率,即存在
r
r
r 个集合,这
r
r
r 个集合的并至多为
256
256
256 个数中的任意
127
127
127 个,即
p
E
r
r
o
r
≤
(
k
r
)
(
256
127
)
[
(
127
l
)
(
256
l
)
]
r
<
3.502
×
1
0
−
88
p_{\rm Error}\le {k\choose r}{256\choose 127}\left[\frac{{127\choose l}}{{256\choose l}}\right]^r<3.502\times10^{-88}
pError≤(rk)(127256)[(l256)(l127)]r<3.502×10−88
完全可以通过。
O ( 256 k ) O(256k) O(256k)
#include<bits/stdc++.h>
using namespace std;
int main() {
int k, r;
scanf("%d%d", &k, &r), r = (512 + r - 1) / r;
while (k--) {
string s(256, '0');
for (int i = 0; i < r; ++i) s[rand() & 255] = '1';
puts(s.c_str());
}
return 0;
}
构造
设 I t = { i + ( t − 1 ) x m o d 256 + 1 ∣ 0 ≤ i < l } I_t=\{i+(t-1)x \mod 256 + 1\mid 0\le i< l\} It={i+(t−1)xmod256+1∣0≤i<l} ,任选 r r r 个集合的并集大小最小为 l + ( r − 1 ) x ≥ 128 l+(r-1)x\ge 128 l+(r−1)x≥128 ,取 x ≥ ⌈ 128 − l r − 1 ⌉ \displaystyle x\ge\left\lceil\frac{128-l}{r-1}\right\rceil x≥⌈r−1128−l⌉ 即可。
实际上可以求出下界为 10 10 10 ,所以只需要任选一个 10 ≤ x ≤ 246 10\le x\le 246 10≤x≤246 即可。
O ( 256 k ) O(256k) O(256k)
#include<bits/stdc++.h>
using namespace std;
int main() {
int k, r, y = 0;
scanf("%d%d", &k, &r), r = (512 + r - 1) / r;
for (; k--; y += 10) {
string s(256, '0');
for (int i = 0; i < r; ++i) s[(i + y) & 255] = '1';
puts(s.c_str());
}
return 0;
}
I. Discrete Mathematics
J. Leaking Roof
高格子向四周低格子连边跑拓扑排序即可。
O ( n 2 ) O(n^2) O(n2)
#include<bits/stdc++.h>
#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
const int N = 500 + 5, M = N * N;
using ll = int64_t;
using db = double;
using arr = int[N];
int in[M], a[N][N], id[N][N];
vector<int> G[M];
void ADD(int u, int v) { G[u].push_back(v), ++in[v]; }
int main() {
int n, m, cnt = 0;
scanf("%d%d", &n, &m);
fp(i, 1, n)a[i][0] = a[i][n + 1] = a[0][i] = a[n + 1][i] = 1e9;
fp(i, 1, n)fp(j, 1, n)scanf("%d", a[i] + j), id[i][j] = ++cnt;
fp(i, 1, n) fp(j, 1, n) {
if (a[i][j] > a[i - 1][j]) ADD(id[i][j], id[i - 1][j]);
if (a[i][j] > a[i + 1][j]) ADD(id[i][j], id[i + 1][j]);
if (a[i][j] > a[i][j - 1]) ADD(id[i][j], id[i][j - 1]);
if (a[i][j] > a[i][j + 1]) ADD(id[i][j], id[i][j + 1]);
}
queue<int> q;
vector<db> f(cnt + 1, m);
fp(i, 1, cnt) if (!in[i]) q.push(i);
while (!q.empty()) {
int u = q.front();
q.pop();
if (!G[u].empty()) f[u] /= G[u].size();
for (auto v: G[u]) {
f[v] += f[u];
if (!--in[v]) q.push(v);
}
}
for (int i = 1; i <= n; ++i, puts(""))
fp(j, 1, n) a[i][j] ? printf("0 ") : printf("%lf ", f[id[i][j]]);
return 0;
}
K. Meal
实际上题目相当于每个人从当前没有被选的菜中以 a i , j a_{i,j} ai,j 的概率选取一道菜。
所以直接记录当前已经打了的菜的集合为 S S S 的概率 f ( S ) f(S) f(S) ,然后直接枚举下一道菜转移即可。
O ( n 2 n ) O(n2^n) O(n2n)
#include<bits/stdc++.h>
#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
const int N = 21, M = 1 << 20 | 5, P = 998244353;
using ll = int64_t;
using arr = int[N];
vector<int> inv;
int a[N][N];
int main() {
inv.resize(2005), inv[1] = 1;
fp(i, 2, 2e3) inv[i] = (ll) (P - P / i) * inv[P % i] % P;
int n, m;
scanf("%d", &n), m = (1 << n) - 1;
fp(i, 0, n - 1) fp(j, 0, n - 1) scanf("%d", a[i] + j);
vector<int> f(m + 1), g(m + 1), cnt(m + 1);
f[0] = 1;
fp(s, 0, m) cnt[s] = __builtin_popcount(s);
fp(i, 0, n - 1) {
vector<ll> ans(n);
g = f, f.assign(m + 1, 0);
fp(s, 0, m) if (cnt[s] == i) {
ll sum = 0, val;
vector<int> vj;
for (int t = m ^ s; t; t &= t - 1) vj.push_back(__builtin_ctz(t)), sum += a[i][vj.back()];
sum = inv[sum];
for (auto j: vj) val = (ll) g[s] * a[i][j] % P * sum % P, f[s | 1 << j] = (f[s | 1 << j] + val) % P, ans[j] += val;
}
fp(j, 0, n - 2) printf("%lld ", ans[j] % P);
printf("%lld%c", ans.back() % P, "\n\0"[i == n - 1]);
}
return 0;
}
L. Euler Function
设 p p p 是质数,根据欧拉函数的性质可得:
- 当 p ∣ q p\mid q p∣q 时, φ ( p q ) = p φ ( q ) \varphi(pq)=p\varphi(q) φ(pq)=pφ(q)
- 当 p ∤ q p\nmid q p∤q 时, φ ( p q ) = ( p − 1 ) φ ( q ) \varphi(pq)=(p-1)\varphi(q) φ(pq)=(p−1)φ(q)
注意到 w ≤ 100 w\le 100 w≤100 ,最多只有 25 25 25 个质数。
考虑线段树,每个节点开一个长度为 25 25 25 的 b i t s e t \rm bitset bitset 表示区间内是否所有数都含有这个质因子。
当整个区间的数都含有质因子 p p p 时,可以直接打上一个乘法标记,没有则向下递归。
由于只有 25 25 25 个质数,所以每个节点至多被暴力修改 25 25 25 次。
O ( 25 n + m log n ) O(25n+m\log n) O(25n+mlogn)
预处理写得有点长,应该用埃氏筛法。
#include<bits/stdc++.h>
#define fp(i, a, b) for(int i = (a), _##i = (b) + 1; i < _##i; ++i)
using namespace std;
const int N = 1e5 + 5, A = 101, P = 998244353;
using ll = int64_t;
bitset<A> vis;
int a[N], phi[A], pr[A], id[A];
vector<pair<int, int>> val[A];
bitset<26> G[A];
void sieves() {
phi[1] = 1;
fp(i, 2, A - 1) {
if (!vis[i]) pr[++pr[0]] = i, phi[i] = i - 1, val[i] = {{i, id[i] = pr[0] - 1}}, G[i][pr[0] - 1] = 1;
for (int j = 1, x, p; j <= pr[0] && (x = i * (p = pr[j])) < A; ++j) {
vis[x].flip(), val[x] = val[i], G[x] = G[i];
if (i % pr[j]) phi[x] = phi[i] * (pr[j] - 1), val[x].emplace_back(pr[j], id[p]), G[x][id[p]] = 1;
else {
phi[x] = phi[i] * p;
for (auto &w: val[x])
if (w.second == id[p])
w.first *= p;
break;
}
}
}
}
struct Seg {
int tag[N << 2], tr[N << 2];
bitset<26> f[N << 2];
#define lc (p << 1)
#define rc (p << 1 | 1)
void Up(int p) { tr[p] = (tr[lc] + tr[rc]) % P, f[p] = f[lc] & f[rc]; }
void upd(int p, int w) { tr[p] = (ll) tr[p] * w % P, tag[p] = (ll) tag[p] * w % P; }
void Down(int p) { int &w = tag[p]; if (w > 1) upd(lc, w), upd(rc, w), w = 1; }
void Build(int p, int L, int R) {
tag[p] = 1;
if (L == R) return f[p] = G[a[L]], tr[p] = phi[a[L]], void();
int m = (L + R) >> 1;
Build(lc, L, m), Build(rc, m + 1, R), Up(p);
}
void mdy(int p, int L, int R, int a, int b, int x, int i) {
if (a <= L && R <= b && f[p][i]) return upd(p, x);
if (L == R) return f[p][i] = 1, tr[p] = (ll) tr[p] * phi[x] % P, void();
int m = (L + R) >> 1; Down(p);
if (a <= m) mdy(lc, L, m, a, b, x, i);
if (b > m) mdy(rc, m + 1, R, a, b, x, i);
Up(p);
}
int qry(int p, int L, int R, int a, int b) {
if (a <= L && R <= b) return tr[p];
int m = (L + R) >> 1, w = 0; Down(p);
if (a <= m) w = qry(lc, L, m, a, b);
if (b > m) w = (w + qry(rc, m + 1, R, a, b)) % P;
return w;
}
} T;
int main() {
sieves();
int n, q; scanf("%d%d", &n, &q);
fp(i, 1, n) scanf("%d", a + i);
T.Build(1, 1, n);
for (int op, l, r, w; q--;) {
scanf("%d%d%d", &op, &l, &r);
if (!op) {
scanf("%d", &w);
for (auto x: val[w])
T.mdy(1, 1, n, l, r, x.first, x.second);
} else printf("%d%c", T.qry(1, 1, n, l, r), "\n\0"[!q]);
}
return 0;
}
M. Addition
先求出和 c c c ,若 c ≥ 0 c\ge 0 c≥0 ,则默认符号位全为 1 1 1 ,否则令 c = − c c=-c c=−c 并默认符号位为 − 1 -1 −1 。
若当前位为 1 1 1 且实际符号位与默认不符,则高位 + 1 +1 +1 ,即 2 k = 2 k + 1 − 2 k 2^k=2^{k+1}-2^k 2k=2k+1−2k 。
O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std;
using ll = int64_t;
int main() {
int n; scanf("%d", &n);
vector<int> sgn(n);
for (auto &x: sgn)scanf("%d", &x);
ll a = 0, b = 1;
for (ll x, i = 0; i < n; ++i) scanf("%lld", &x), a += sgn[i] * (x << i);
for (ll x, i = 0; i < n; ++i) scanf("%lld", &x), a += sgn[i] * (x << i);
if (a < 0) b = -1, a = -a;
for (ll i = 0; i < n; ++i) {
printf("%d%c", a & 1, " \0"[i == n - 1]);
a = (a >> 1) + (a & 1 && sgn[i] != b);
}
return 0;
}