从今天起,关心粮食和蔬菜。
A + B
发现这种计算方式不存在进位,计算过程中只有每个相加的位在得数中对应一个长度为 1 或 2 的段,对原字符串进行每个划分的方案数 dp 即可。 Θ ( log n ) \Theta(\log n) Θ(logn)
Muzyka pop
考虑最高位的贡献,
f
(
k
,
l
,
r
)
f(k, l, r)
f(k,l,r) 表示规划
l
∼
r
l\sim r
l∼r 这一段单独规划,
b
b
b 的值都在
[
0
,
2
k
)
[0, 2^k)
[0,2k) 内的方案,注意到最高位的贡献恰好是一个后缀,因此有
f
(
k
,
l
,
r
)
=
max
j
{
f
(
k
−
1
,
l
,
j
−
1
)
+
f
(
k
−
1
,
j
,
r
)
+
s
r
−
s
j
−
1
}
f(k, l, r) = \max_j \{ f(k - 1, l, j - 1) + f(k - 1, j, r) + s_r - s_{j - 1} \}
f(k,l,r)=maxj{f(k−1,l,j−1)+f(k−1,j,r)+sr−sj−1},时间复杂度
Θ
(
n
3
log
m
)
\Theta(n^3 \log m)
Θ(n3logm)。那么处理出
f
f
f 之后就可以用来类似的方程做数位 DP 了,这一部分只用对一个后缀来 DP。
时间复杂度
Θ
(
n
3
log
m
)
\Theta(n^3 \log m)
Θ(n3logm)。
代码
Wina
由于仅仅是最小化目标,考虑一个数能否被取到,用于更新答案即可。
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)
代码
Desant
考虑对 DP 进行状态化简,已经删去的数的区间我们只需记住其中一共选了几个数,这样 DP 的状态在考虑前
k
k
k 个数时只有
∏
i
=
0
k
(
1
+
b
i
)
\prod_{i=0}^k (1 + b_i)
∏i=0k(1+bi) 个,而
∑
i
=
0
k
b
i
=
n
−
k
\sum_{i=0}^k b_i = n - k
∑i=0kbi=n−k,因此前面的乘积
≤
(
n
+
1
k
+
1
)
k
+
1
\leq \left(\frac{n + 1}{k+1}\right)^{k+1}
≤(k+1n+1)k+1,复杂度
Θ
(
∑
k
=
1
n
(
k
+
1
)
(
n
+
1
k
+
1
)
k
+
1
)
\Theta\left(\sum_{k=1}^n (k+1)\left(\frac{n + 1}{k+1}\right)^{k+1}\right)
Θ(∑k=1n(k+1)(k+1n+1)k+1),这个求和的结果其实并不大。
代码
Herbata
首先至少要保证总共热量相同,也就是 ∑ l i a i = ∑ l i b i \sum l_i a_i = \sum l_i b_i ∑liai=∑libi
接着你猜测最值变成一个子区间就行,然后交了一发发现获得了 0 分的好成绩
考虑所有 ( l i , a i l i ) (l_i, a_i l_i) (li,aili) 向量按照斜率排序首尾相连组成的凸壳,发现任何一次平均操作本质上是让凸壳发生了收缩,而操作可以任意小,因此充要条件就是原状态的凸壳将终态的凸壳包住,对斜率排序即可。 Θ ( n log n ) \Theta(n\log n) Θ(nlogn)
Iloczyny Fibonacciego
注意到
F
n
+
m
=
F
n
−
1
F
m
−
1
+
F
n
F
m
F_{n+m} = F_{n-1}F_{m-1} + F_nF_m
Fn+m=Fn−1Fm−1+FnFm,不难得到
F
n
F
m
=
F
n
+
m
−
F
n
+
m
−
2
+
F
n
+
m
−
4
−
⋯
+
(
−
1
)
min
(
n
,
m
)
F
∣
n
−
m
∣
F_nF_m = F_{n+m} - F_{n+m-2} +F_{n+m-4} - \cdots + (-1)^{\min(n,m)} F_{|n-m|}
FnFm=Fn+m−Fn+m−2+Fn+m−4−⋯+(−1)min(n,m)F∣n−m∣,对于这一非正规表示方法,我们不难先用卷积
A
(
x
)
×
B
(
x
)
A(x)\times B(x)
A(x)×B(x) 得到前缀和在
n
+
m
n + m
n+m 位置的修改,然后用
A
(
−
x
)
×
B
(
1
/
x
)
x
m
A(-x)\times B(1/x) x^m
A(−x)×B(1/x)xm 得到前缀和在
∣
n
−
m
∣
−
2
|n - m| - 2
∣n−m∣−2 位置的修改。(注意这里在
i
<
j
i < j
i<j 和
i
>
j
i > j
i>j 的时候差一个
(
−
1
)
i
−
j
(-1)^{i-j}
(−1)i−j 的符号)
接下来我们得到了一个每个位置的系数在
Θ
(
n
2
)
\Theta(n^2)
Θ(n2) 内的有正有负的表示,我们考虑通过二进制分解的方法可以
Θ
(
n
log
x
)
\Theta(n\log x)
Θ(nlogx) 将一组正的表示转成正规的表示,于是把正负分别转化,之后正规表示的减法是容易的。
时间复杂度
Θ
(
n
log
n
)
\Theta(n\log n)
Θ(nlogn)。
代码
Terytoria
首先需注意到两个维度是独立的,接下来我们要做的就是分别计算。考虑一个维度上给出了
n
n
n 个区间,我们每个区间选择该区间本身或者补集,最大化其交的大小。
注意到最后能取出的集合是两两不交的,考虑对于单位
[
i
,
i
+
1
]
[i, i + 1]
[i,i+1],如果
i
,
j
i, j
i,j 两个单位出现于同一个交集中当且仅当覆盖它们的区间集合相同。可以数据结构解决,但我不想写线段树分裂。考虑哈希,给每一个区间赋予一个随机数,将区间内的单位异或上这个数,最后取值相等的视为同一交集即可。由于要离散化所以需要排序,
Θ
(
n
log
n
)
\Theta(n\log n)
Θ(nlogn)。通过生日悖论粗略估算正确率:
∏ k = 0 2 n − 1 ( 1 − 2 − 64 k ) 2 ≈ e − n ( 2 n − 1 ) 2 − 63 ≈ 1 − n ( 2 n − 1 ) 2 − 63 ≈ 1 − 5.42 × 1 0 − 8 \begin{aligned} \prod_{k=0}^{2n-1} (1 - 2^{-64}k)^2 & \approx \mathrm{e}^{-n(2n-1)2^{-63}} \\ & \approx 1 - n(2n-1)2^{-63}\\ & \approx 1 - 5.42\times 10^{-8} \end{aligned} k=0∏2n−1(1−2−64k)2≈e−n(2n−1)2−63≈1−n(2n−1)2−63≈1−5.42×10−8
Szprotki i szczupaki
显然每次吃比自己小的鱼中最重的那条。假设下一条体重
≥
\ge
≥ 自己的鱼为
w
w
w,那么我们每次假设在当前还能吃的鱼中进行线段树上二分,可以知道至少吃多少条才能使得体重达到
min
(
k
,
w
−
1
)
\min(k, w - 1)
min(k,w−1)。注意到每次进行两轮二分体重至少倍增,所以只会进行
Θ
(
log
w
)
\Theta(\log w)
Θ(logw) 轮。“吃鱼”可以通过在线段树上打一个永久化的标记完成,最后撤回所有吃鱼标记即可。
时间复杂度
Θ
(
q
log
n
log
w
)
\Theta(q\log n\log w)
Θ(qlognlogw)。
代码
Wyspa
坑
Trzy kule
考虑至少某个满足的方案数
2
n
2^n
2n 减去全都不满足的方案数,后者等于
(
r
1
′
,
r
2
′
,
r
3
′
)
=
(
n
−
1
−
r
1
,
n
−
1
−
r
2
,
n
−
1
−
r
3
)
(r'_1,r'_2,r'_3)=(n-1-r_1, n-1-r_2, n-1-r_3)
(r1′,r2′,r3′)=(n−1−r1,n−1−r2,n−1−r3) 对应的答案。字符串本质上只有
4
4
4 种位置:
000
,
001
,
010
,
011
000,001,010,011
000,001,010,011,因为第一位可以异或掉后面的。考虑 Meet in the Middle,若枚举
000
,
011
000,011
000,011 中分别分配几个
0
,
1
0,1
0,1,那么这一部分造成距离贡献为
(
k
0
+
k
3
,
k
0
+
c
3
−
k
3
,
k
0
+
c
3
−
k
3
)
(k_0 + k_3, k_0 + c_3 - k_3, k_0 + c_3 - k_3)
(k0+k3,k0+c3−k3,k0+c3−k3)。造成的距离贡献只有两个自由度,将另外一部分拼凑的看做是点,这一部分看做是询问,这个三维数点的询问只有两个自由度且可以直接表示为
(
x
,
y
,
z
)
+
(
i
+
j
,
c
3
+
i
−
j
,
c
3
+
i
−
j
)
≤
(
r
1
′
,
r
2
′
,
r
3
′
)
(x,y,z)+(i+j, c_3 + i-j, c_3 + i-j)\le (r'_1,r'_2,r'_3)
(x,y,z)+(i+j,c3+i−j,c3+i−j)≤(r1′,r2′,r3′),因此
max
(
y
−
r
2
′
,
z
−
r
3
′
)
≤
c
3
+
i
−
j
\max(y-r'_2,z-r'_3)\le c_3+i-j
max(y−r2′,z−r3′)≤c3+i−j 即可。可以缩去一维,直接通过二维前缀和处理。时间复杂度
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)。
代码
Osady i warownie 2
转为对偶图的连通性问题,但是并不是无向边。我们维护 S 可达区域和可达 T 的区域,S 的区域是一个右上角的阶梯形,T 的区域是左下角的一个阶梯形,加一条边的时候如果恰好切断,说明一端在 S 中一端在 T 中。用树状数组可以在加入一条边的时候扩展边界线,每条线维护一个堆用于找到扩展时新的可以可用边。
时间复杂度
Θ
(
k
log
k
)
\Theta(k\log k)
Θ(klogk)。
代码
Podatki drogowe
值域非常大,考虑随机二分。我们首先边分治,然后考虑每个 dist 值用一个可持久化线段树来存储,这样用 hash 就可以完成在
Θ
(
log
n
)
\Theta(\log n)
Θ(logn) 时间内的 cmp。接下来就可以通过双指针在
Θ
(
n
log
2
n
)
\Theta(n\log^2 n)
Θ(nlog2n) 时间内确认一个数的 equal_range,总复杂度就是
Θ
(
n
log
3
n
)
\Theta(n\log^3 n)
Θ(nlog3n)。
代码
Sonda
坑
接下来的部分是 PA 2019 Final,由于并不公开数据,在 LOJ 上是没有的。由于是波兰语,本文也做简要题意叙述。
Zdjęcie rodzinne
简要题意:给一颗有根树,问找到一个最长的不重复点序列使得相邻两个点有祖先关系。 n ≤ 3 × 1 0 5 n\le 3\times 10^5 n≤3×105。
考虑一个子树的 dp
f
(
u
,
j
)
f(u, j)
f(u,j) 表示一个允许使用额外的子树外的
j
j
j 个祖先的情况下,能够得到的最长序列。不难得到 dp 过程大致上是各孩子的一个 max 卷积。注意到这个过程显然关于
j
j
j 始终是凸的,因此我们可以直接维护差分值,这个东西的实际意义是多加一个祖先能多合并的部分。因此算法就是:每个子树的 dp 值用一个堆表示。将各子树的堆合并,然后将最大的两个元素求和再
+
1
+1
+1 再放回堆中(不够补
0
0
0),就得到了本节点的信息。而最终的答案是根节点的堆的最大值。
取决于使用启发式合并还是可并堆,复杂度为
Θ
(
n
log
n
)
∼
Θ
(
n
log
2
n
)
\Theta(n\log n) \sim \Theta(n\log^2 n)
Θ(nlogn)∼Θ(nlog2n)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 300010;
int n;
vector<int> g[N];
priority_queue<int> q[N];
void dfs(int u) {
for (int v : g[u]) {
dfs(v);
if (q[u].size() < q[v].size()) swap(q[u], q[v]);
while (!q[v].empty()) {
int x = q[v].top();
q[v].pop();
q[u].push(x);
}
}
int x = 1;
for (int rep = 0; rep < 2 && !q[u].empty(); ++rep) {
x += q[u].top();
q[u].pop();
}
q[u].push(x);
}
int main() {
#ifdef LBT
freopen("test.in", "r", stdin);
int nol_cl = clock();
#endif
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 2; i <= n; ++i) {
int p;
cin >> p;
g[p].push_back(i);
}
dfs(1);
printf("%d\n", q[1].top());
#ifdef LBT
LOG("Time: %dms\n", int ((clock()
-nol_cl) / (double)CLOCKS_PER_SEC * 1000));
#endif
return 0;
}
Terytoria 2
简要题意:给出 n n n 种动物,每种动物有 c c c 个个体以及一个对应的禁忌矩形,你要把该种动物放在矩形之外,求最大化全体动物处在相同位置的动物对数。 n ≤ 1 0 5 , X , Y ≤ 1 0 3 n\le 10^5, X, Y\le 10^3 n≤105,X,Y≤103。
考虑最优解的性质,每次我们找到那个最优解里面人最多的格子,那么显然其他任何动物都不能移动到这个格子,否则答案变大。因此我们只需要考虑一个枚举顺序,每次让这个格子里的人尽量多放。注意到我们首先确定第一个放置点后,没有放置的就是包含这个矩形的所有种族,接下来考虑一个格子里如果放了数,那么它一定可以让这些种族全部被挪到一个角上,所以我们下一步一定是枚举一个角,接下来剩下的一定都可以放在对角上。这个信息用前缀和就可以处理了。时间复杂度 Θ ( n + X Y ) \Theta(n + XY) Θ(n+XY)。
另外这也证明了最优解中,全体动物最多放在三个位置。
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int X = 1010;
int s[17][X][X];
void add(int k, int x1, int y1, int x2, int y2, int v) {
s[k][x1][y1] += v;
s[k][x1][y2 + 1] -= v;
s[k][x2 + 1][y1] -= v;
s[k][x2 + 1][y2 + 1] += v;
}
ll gval(int x) { return x * (x - 1LL) / 2; }
int main() {
#ifdef LBT
freopen("test.in", "r", stdin);
int nol_cl = clock();
#endif
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, x, y, tot = 0;
cin >> n >> x >> y;
while (n--) {
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
int v = 0;
tot += c;
v |= (x1 == 1 && y1 == 1);
v |= (x1 == 1 && y2 == y) << 1;
v |= (x2 == x && y1 == 1) << 2;
v |= (x2 == x && y2 == y) << 3;
add(16, 1, 1, x, y, c);
add(16, x1, y1, x2, y2, -c);
add(v, x1, y1, x2, y2, c);
}
for (int k = 0; k <= 16; ++k)
for (int i = 1; i <= x; ++i)
for (int j = 1; j <= y; ++j)
s[k][i][j] += s[k][i - 1][j] + s[k][i][j - 1] - s[k][i - 1][j - 1];
ll ans = 0;
for (int i = 1; i <= x; ++i)
for (int j = 1; j <= y; ++j) {
ll cur = gval(s[16][i][j]);
for (int k = 0; k < 4; ++k) {
int cs = 0;
for (int t = 0; t < 16; ++t)
if (!((t >> k) & 1))
cs += s[t][i][j];
ans = max(ans, cur + gval(cs) + gval(tot - s[16][i][j] - cs));
}
}
cout << ans << '\n';
#ifdef LBT
LOG("Time: %dms\n", int ((clock()
-nol_cl) / (double)CLOCKS_PER_SEC * 1000));
#endif
return 0;
}
Pionki
简要题意:给一个三维棋盘,每个格子上有一些棋子,可以将棋子沿 x , y , z x,y,z x,y,z 正方向移动,问状态 a a a 能不能变为状态 b b b。其中 B , C ≤ 6 B,C\le 6 B,C≤6。多组询问 T = 1 0 4 T=10^4 T=104,其中 ∑ A ≤ 1 0 4 \sum A\le 10^4 ∑A≤104。
考虑最大流转最小割,我们只需要按第一维的层状压转移即可,由于 S , T S,T S,T 两部一定是一个当层的一个割的状态,所以状态数是 ( B + C B ) \binom{B+C}B (BB+C),注意转移之间也可以简化成一个个增补的过程,所以每个节点的转移是 < B + C <B+C <B+C 的。时间复杂度 Θ ( A ( B + C B ) ( B + C ) ) \Theta(A \binom{B+C}B (B+C)) Θ(A(BB+C)(B+C))。
多组数据初始化可能略慢,可以特判 A = 1 A=1 A=1 卡常数(这部分可以做到 Θ ( B C ) \Theta(BC) Θ(BC))。
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
struct TrieMap {
int A;
vector<vector<int>> trans;
vector<ll> value;
TrieMap(int A) : A(A), trans(1, vector<int>(A, -1)), value(1, -1) {}
ll get(int o) const { return value[o]; }
int getId(const vector<int>& s) const {
int o = 0;
for (int c : s) {
o = trans[o][c];
if (o == -1)
return -1;
}
return o;
}
int extend(int o, int c) {
if (trans[o][c] == -1) {
trans[o][c] = trans.size();
trans.push_back(vector<int>(A, -1));
value.push_back(-1);
}
return trans[o][c];
}
void ins(const vector<int>& s, ll v) {
int o = 0;
for (int c : s) o = extend(o, c);
value[o] = v;
}
};
int main() {
#ifdef LBT
freopen("test.in", "r", stdin);
int nol_cl = clock();
#endif
int T;
scanf("%d", &T);
while (T--) {
int A, B, C;
scanf("%d%d%d", &A, &B, &C);
vector<vector<vector<ll>>> a(A, vector<vector<ll>>(B, vector<ll>(C))), b = a;
ll totA = 0, totB = 0;
for (int i = 0; i < A; ++i)
for (int j = 0; j < B; ++j)
for (int k = 0; k < C; ++k) {
scanf("%lld", &a[i][j][k]);
totA += a[i][j][k];
}
for (int i = 0; i < A; ++i)
for (int j = 0; j < B; ++j)
for (int k = 0; k < C; ++k) {
scanf("%lld", &b[i][j][k]);
totB += b[i][j][k];
}
if (totA != totB) {
puts("NIE");
continue;
}
if (A == 1) {
bool flag = true;
vector<ll> c(C);
for (int i = 0; i < B; ++i) {
ll need = 0;
for (int j = C - 1; j >= 0; --j) {
need += b[0][i][j];
c[j] += a[0][i][j];
ll v = min(need, c[j]);
need -= v;
c[j] -= v;
}
if (need != 0) {
flag = false;
break;
}
}
puts(flag ? "TAK" : "NIE");
continue;
}
TrieMap trieMap(C + 1);
vector<int> stk;
function<void(int, int, int, const function<void(int)>&)> dfs = [&](int i, int j, int o, const function<void(int)>& f) {
if (i == B) {
f(o);
return;
}
for (; j >= 0; --j) {
stk.push_back(j);
dfs(i + 1, j, trieMap.extend(o, j), f);
stk.pop_back();
}
};
dfs(0, C, 0, [&](int o) {
trieMap.value[o] = 0;
trieMap.trans[o].assign(B, -1);
for (int i = 0; i < B; ++i) {
if (stk[i] < C) {
++stk[i];
trieMap.trans[o][i] = trieMap.getId(stk);
--stk[i];
}
}
});
for (int i = 0; i < A; ++i) {
vector<vector<ll>> cost(B, vector<ll>(C + 1));
ll tot = 0;
for (int j = 0; j < B; ++j) {
for (int k = 0; k < C; ++k) {
tot += b[i][j][k];
cost[j][k + 1] = cost[j][k] + a[i][j][k] - b[i][j][k];
}
}
dfs(0, C, 0, [&](int o) {
ll cur = trieMap.get(o) + tot;
for (int i = 0; i < B; ++i)
cur += cost[i][stk[i]];
for (int i = 0; i < B; ++i)
if (stk[i] < C) {
++stk[i];
int p = trieMap.trans[o][i];
if (p != -1)
cur = min(cur, trieMap.get(p) - cost[i][stk[i]] + cost[i][stk[i] - 1]);
--stk[i];
}
trieMap.ins(stk, cur);
});
}
ll minCut = numeric_limits<ll>::max();
dfs(0, C, 0, [&](int o) { minCut = min(minCut, trieMap.get(o)); });
puts((minCut == totA) ? "TAK" : "NIE");
}
#ifdef LBT
LOG("Time: %dms\n", int ((clock()
-nol_cl) / (double)CLOCKS_PER_SEC * 1000));
#endif
return 0;
}
Parzysty deszcz
简要题意:给一个直方图,每个位置有个高度 h i h_i hi,问有多少种方法将其中 k k k 个位置高度抹成 0 0 0,之后剩余部分的极大积水面积是偶数。同余 1 0 9 + 7 10^9 + 7 109+7, n ≤ 25000 , k ≤ 25 n\le 25000, k\le 25 n≤25000,k≤25。
积水的高度就是两个方向看到的
max
\max
max 的较小值,因此我们只需考虑这个结果的前缀
max
\max
max 和前缀
min
\min
min,考虑一个数向后找到下一个比它大的数,我们显然只有
k
+
1
k+1
k+1 种选择,也就是这么多种转移,这个可以排序后用一个 set
轻松找到。转移就是一个多项式乘法。总共时间复杂度
Θ
(
n
log
n
+
n
k
3
)
\Theta(n\log n + nk^3)
Θ(nlogn+nk3)。这个 OJ 真是卡常,这道题 9s 也卡
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 25010, K = 26, P = 1000000007;
const ll P2 = P * (ll)P;
int h[N], cnt[N], fac[N], ifac[N];
int bin[N][K];
int conv[K][2];
int trans[N][K], rtrans[N][K];
int od[N][K], rod[N][K];
int dp[N][K][2], rdp[N][K][2];
pair<int, int> ord[N];
int mpow(int x, int k) {
int ret = 1;
while (k) {
if (k & 1) ret = ret * (ll)x % P;
k >>= 1;
x = x * (ll)x % P;
}
return ret;
}
int norm(int x) { return x >= P ? x - P : x; }
int binom(int n, int m) { return (m < 0 || m > n) ? 0 : bin[n][m]; }
int sbinom(int n, int m) { return (m < 0 || m > n) ? 0 : ((m & 1) ? norm(P - bin[n][m]) : bin[n][m]); }
int main() {
#ifdef LBT
freopen("test.in", "r", stdin);
int nol_cl = clock();
#endif
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; ++i) cin >> h[i];
for (int i = 0; i <= n + 1; ++i) ord[i] = make_pair(h[i], i);
sort(ord, ord + n + 2, greater<pair<int, int>>());
set<int> s;
for (int i = 0; i <= n + 1; ++i) {
int p = ord[i].second;
auto it = s.insert(p).first;
auto jt = make_reverse_iterator(it); --jt;
int odds = 0;
for (int j = 0; j <= k; ++j) {
if (++it == s.end()) {
trans[p][j] = -1;
break;
}
od[p][j] = odds;
trans[p][j] = *it;
odds += h[*it] & 1;
}
odds = 0;
for (int j = 0; j <= k; ++j) {
if (++jt == s.rend()) {
rtrans[p][j] = -1;
break;
}
rod[p][j] = odds;
rtrans[p][j] = *jt;
odds += h[*jt] & 1;
}
}
for (int i = 1; i <= n + 1; ++i) cnt[i] = cnt[i - 1] + (h[i] & 1);
for (int i = 0; i <= n; ++i) {
bin[i][0] = 1;
for (int j = 1; j <= min(i, k); ++j)
bin[i][j] = norm(bin[i - 1][j - 1] + bin[i - 1][j]);
}
dp[0][0][0] = dp[0][0][1] = 1;
rdp[n + 1][0][0] = rdp[n + 1][0][1] = 1;
for (int i = 0; i < n; ++i) {
for (int j = 0; j <= k; ++j) {
if (trans[i][j] == -1) break;
int p = trans[i][j];
memset(conv, 0, sizeof(conv));
bool par = (od[i][j] & 1) ^ ((h[i] & 1) & (p - 1 - i)) ^ ((cnt[p - 1] - cnt[i]) & 1);
int r1 = cnt[p - 1] - cnt[i] - od[i][j], r0 = p - 1 - i - j - r1;
for (int t = 0; t <= k - j; ++t) {
conv[t][0] = binom(r0 + r1, t);
ll v = 0;
for (int u = 0; u <= t; ++u) {
v += sbinom(r1, u) * (ll) binom(r0, t - u);
if (v >= P2) v -= P2;
}
conv[t][1] = v % P;
if (par) conv[t][1] = norm(P - conv[t][1]);
}
for (int x = j; x <= k; ++x)
for (int b = 0; b <= 1; ++b) {
ll ad = 0;
for (int y = 0; y <= x - j; ++y) {
ad += dp[i][x - y - j][b] * (ll) conv[y][b];
if (ad >= P2)
ad -= P2;
}
dp[p][x][b] = (dp[p][x][b] + ad) % P;
}
}
}
for (int i = n + 1; i >= 2; --i) {
for (int j = 0; j <= k; ++j) {
if (rtrans[i][j] == -1) break;
int p = rtrans[i][j];
memset(conv, 0, sizeof(conv));
bool par = (rod[i][j] & 1) ^ ((h[i] & 1) & (i - 1 - p)) ^ ((cnt[i - 1] - cnt[p]) & 1);
int r1 = cnt[i - 1] - cnt[p] - rod[i][j], r0 = i - 1 - p - j - r1;
for (int t = 0; t <= k - j; ++t) {
conv[t][0] = binom(r0 + r1, t);
ll v = 0;
for (int u = 0; u <= t; ++u) {
v += sbinom(r1, u) * (ll) binom(r0, t - u);
if (v >= P2) v -= P2;
}
conv[t][1] = v % P;
if (par) conv[t][1] = norm(P - conv[t][1]);
}
for (int x = j; x <= k; ++x)
for (int b = 0; b <= 1; ++b) {
ll ad = 0;
for (int y = 0; y <= x - j; ++y) {
ad += rdp[i][x - y - j][b] * (ll) conv[y][b];
if (ad >= P2)
ad -= P2;
}
rdp[p][x][b] = (rdp[p][x][b] + ad) % P;
}
}
}
int ans = 0;
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= k; ++j)
for (int t = 0; t <= 1; ++t)
ans = (ans + dp[i][j][t] * (ll) rdp[i][k - j][t]) % P;
ans = ((ans & 1) ? (ans + P) : ans) >> 1;
cout << ans << '\n';
#ifdef LBT
LOG("Time: %dms\n", int ((clock()
-nol_cl) / (double)CLOCKS_PER_SEC * 1000));
#endif
return 0;
}
Zaczarowany Ołówek
简要题意:给出 n n n 个三角形,依次按照给定的点的顺序绘制,当一个点被画过偶数次就会消失,奇数次就会重现,问结果得到的可见线段长度和。 n ≤ 1 0 5 n\le 10^5 n≤105。
容易发现这就是一个线段的异或和问题,每个直线上的各自排序即可。时间复杂度 Θ ( N log N ) \Theta(N\log N) Θ(NlogN)。
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
int main() {
#ifdef LBT
freopen("test.in", "r", stdin);
int nol_cl = clock();
#endif
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<pair<tuple<int, int, ll>, pair<int, int>>> points;
function<void(int, int, int, int)> add = [&](int x1, int y1, int x2, int y2) {
int A = y1 - y2, B = x2 - x1;
ll C = x1 * (ll)(y2 - y1) - y1 * (ll)(x2 - x1);
ll g = gcd(gcd(A, B), C);
A /= g;
B /= g;
C /= g;
if (A < 0) {
A = -A;
B = -B;
C = -C;
} else if (A == 0) {
if (B < 0) {
B = -B;
C = -C;
} else if (B == 0 && C < 0)
C = -C;
}
points.emplace_back(make_tuple(A, B, C), make_pair(x1, y1));
points.emplace_back(make_tuple(A, B, C), make_pair(x2, y2));
};
while (n--) {
int x1, y1, x2, y2, x3, y3;
cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3;
add(x1, y1, x2, y2);
add(x2, y2, x3, y3);
add(x3, y3, x1, y1);
}
sort(points.begin(), points.end());
double ans = 0;
function<ll(int)> sq = [&](int x) { return x * (ll)x; };
for (int i = 0; i < points.size(); i += 2) {
if (points[i].first == points[i + 1].first) {
ans += sqrt(sq(points[i].second.first - points[i + 1].second.first) +
sq(points[i].second.second - points[i + 1].second.second));
}
}
cout.precision(10);
cout << fixed << ans << '\n';
#ifdef LBT
LOG("Time: %dms\n", int ((clock()
-nol_cl) / (double)CLOCKS_PER_SEC * 1000));
#endif
return 0;
}
Łamana 2
简要题意:一个字符串,问如果将每个相同字符替换成同方向的一个箭头(向上或向下),则最大的首尾相连构成这条路径的下方围着的面积是多少? ∣ S ∣ ≤ 3 × 1 0 5 , ∣ A ∣ = 16 |S|\le 3\times 10^5, |A| = 16 ∣S∣≤3×105,∣A∣=16
注意到这个面积等价于逆序对,我们可以在 Θ ( ∣ S ∣ A ) \Theta(|S|A) Θ(∣S∣A) 内算出每对字符,前者当 1,后者当 0 产生的逆序对,最后 Θ ( ∣ A ∣ 2 2 ∣ A ∣ ) \Theta(|A|^22^{|A|}) Θ(∣A∣22∣A∣) 枚举一下就可以通过了。
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int main() {
#ifdef LBT
freopen("test.in", "r", stdin);
int nol_cl = clock();
#endif
ios::sync_with_stdio(false);
cin.tie(nullptr);
string str;
cin >> str;
vector<int> cnt(16);
vector<vector<ll>> tot(16, vector<ll>(16));
for (char c : str) {
++cnt[c - 'a'];
for (int i = 0; i < 16; ++i)
tot[i][c - 'a'] += cnt[i];
}
ll ans = 0;
for (int s = 0; s < (1 << 16); ++s) {
ll cur = 0;
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 16; ++j)
if (((s >> i) & 1) && !((s >> j) & 1))
cur += tot[i][j];
ans = max(ans, cur);
}
cout << ans << '\n';
#ifdef LBT
LOG("Time: %dms\n", int ((clock()
-nol_cl) / (double)CLOCKS_PER_SEC * 1000));
#endif
return 0;
}
Grafy
简要题意:问有多少个 N N N 个顶点有标号的无向简单图,每个点的入度出度都是 2 2 2。答案对 P P P 取模。 N ≤ 500 N\le 500 N≤500
考虑容斥。我们首先看成一个
0
∼
2
N
−
1
0 \sim 2N -1
0∼2N−1 的排列,然后每个节点要求就是
⌊
p
2
i
/
2
⌋
≠
⌊
p
2
i
+
1
/
2
⌋
∧
⌊
p
2
i
/
2
⌋
≠
i
∧
⌊
p
2
i
+
1
/
2
⌋
≠
i
\lfloor p_{2i}/2\rfloor \neq \lfloor p_{2i+1}/2\rfloor \wedge \lfloor p_{2i}/2\rfloor \neq i \wedge \lfloor p_{2i+1}/2\rfloor \neq i
⌊p2i/2⌋=⌊p2i+1/2⌋∧⌊p2i/2⌋=i∧⌊p2i+1/2⌋=i 容斥信息就是
∏
i
=
0
N
−
1
(
1
−
[
⌊
p
2
i
/
2
⌋
=
⌊
p
2
i
+
1
/
2
⌋
]
−
[
⌊
p
2
i
/
2
⌋
=
i
]
−
[
⌊
p
2
i
+
1
/
2
⌋
=
i
]
+
2
[
⌊
p
2
i
/
2
⌋
=
i
∧
⌊
p
2
i
+
1
/
2
⌋
=
i
]
)
\prod_{i=0}^{N-1} (1 - [\lfloor p_{2i} / 2 \rfloor = \lfloor p_{2i + 1} / 2 \rfloor] - [\lfloor p_{2i}/2\rfloor = i] - [\lfloor p_{2i+1}/2\rfloor = i] + 2[\lfloor p_{2i}/2\rfloor = i \wedge \lfloor p_{2i+1}/2\rfloor = i])
∏i=0N−1(1−[⌊p2i/2⌋=⌊p2i+1/2⌋]−[⌊p2i/2⌋=i]−[⌊p2i+1/2⌋=i]+2[⌊p2i/2⌋=i∧⌊p2i+1/2⌋=i]),所以我们可以直接列出一个三变量容斥式子(因为其中两个单自环假设是同质的)
1
2
2
n
∑
i
+
j
+
k
≤
N
2
i
(
−
1
)
j
(
−
1
)
k
(
N
i
,
j
,
k
,
N
−
i
−
j
−
k
)
2
i
2
2
j
(
N
−
i
−
j
k
)
k
!
2
k
(
2
N
−
2
i
−
j
−
2
k
)
!
\frac1{2^{2n}} \sum_{i+j+k\le N} 2^i (-1)^j (-1)^k \binom N{i,j,k,N-i-j-k} 2^i 2^{2j} \binom{N-i-j}k k! 2^k (2N-2i-j-2k)!
22n1i+j+k≤N∑2i(−1)j(−1)k(i,j,k,N−i−j−kN)2i22j(kN−i−j)k!2k(2N−2i−j−2k)!
这个 oj 有点卡常,我们稍微减小一下常数。化简一下就是
n
!
2
2
n
∑
i
+
j
+
k
≤
n
(
2
n
−
2
i
−
j
−
2
k
)
!
(
n
−
i
−
j
)
!
i
!
j
!
k
!
(
n
−
i
−
j
−
k
)
!
2
(
−
1
)
j
+
k
2
2
i
+
2
j
+
k
\frac{n!}{2^{2n}}\sum_{i+j+k\le n} \frac{(2n-2i-j-2k)!(n-i-j)!}{i!j!k!(n-i-j-k)!^2} (-1)^{j+k} 2^{2i+2j+k}
22nn!i+j+k≤n∑i!j!k!(n−i−j−k)!2(2n−2i−j−2k)!(n−i−j)!(−1)j+k22i+2j+k
时间复杂度
Θ
(
N
3
)
\Theta(N^3)
Θ(N3)。
bonus:你能找出整式递推吗?
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 1010;
int P;
int fac[N], ifac[N], pw2[N], sgn[N];
int norm(int x) { return x >= P ? x - P : x; }
void add(int & x, int y) {
if ((x += y) >= P) x -= P;
}
int binom(int n, int m) { return fac[n] * (ll)ifac[m] % P * ifac[n - m] % P; }
int ifac4(int a, int b, int c, int d) { return ifac[a] * (ll)ifac[b] % P * ifac[c] % P * ifac[d] % P; }
int main() {
#ifdef LBT
freopen("test.in", "r", stdin);
int nol_cl = clock();
#endif
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n >> P;
n *= 2;
fac[0] = 1;
for (int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * (ll)i % P;
ifac[1] = 1;
for (int i = 2; i <= n; ++i) ifac[i] = -(P / i) * (ll)ifac[P % i] % P + P;
ifac[0] = 1;
for (int i = 1; i <= n; ++i) ifac[i] = ifac[i - 1] * (ll)ifac[i] % P;
pw2[0] = 1;
for (int i = 1; i <= n; ++i) pw2[i] = norm(pw2[i - 1] << 1);
sgn[0] = 1;
for (int i = 1; i <= n; ++i) sgn[i] = P - sgn[i - 1];
n /= 2;
int ans = 0;
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= n - i; ++j)
for (int k = 0; k <= n - i - j; ++k)
add(ans, (ll)fac[n * 2 - i * 2 - j - k * 2] * ifac4(i, j, k, n - i - j - k) % P * pw2[i * 2 + j * 2 + k] % P * sgn[j + k] % P * fac[n - i - j] % P * ifac[n - i - j - k] % P);
ans = ans * (ll)fac[n] % P;
for (int rep = 0; rep < n * 2; ++rep) {
if (ans & 1) ans = (ans + P) >> 1;
else ans >>= 1;
}
cout << ans << '\n';
#ifdef LBT
LOG("Time: %dms\n", int ((clock()
-nol_cl) / (double)CLOCKS_PER_SEC * 1000));
#endif
return 0;
}
Gdzie jest jedynka? 2
简要题意:交互题,一个 0 ∼ n − 1 0\sim n-1 0∼n−1 的排列,每次可以询问 gcd ( p i , p j ) ( i ≠ j ) \gcd(p_i, p_j) (i\neq j) gcd(pi,pj)(i=j),在 ⌈ 2.5 n ⌉ \lceil 2.5n \rceil ⌈2.5n⌉ 次询问内找出 1 1 1 所在的位置。多组数据, ∑ n ≤ 5 × 1 0 5 \sum n\le 5\times 10^5 ∑n≤5×105。
首先我们假设
n
>
3
n>3
n>3,我们首先考虑从一个数开始算它和每个数的
gcd
\gcd
gcd,如果是
1
1
1 那么得到的都是
1
1
1,否则我们可以知道它的倍数都是谁。但是还有一个例外就是它是
0
0
0,这样就会得到最大的数。我们考虑不停地从留下的数里面筛,直到剩下两个数。这个过程我们已经支付了最多
n
+
n
k
1
+
n
k
1
k
2
+
⋯
<
2
n
n + \frac{n}{k_1} + \frac{n}{k_1k_2} + \cdots < 2n
n+k1n+k1k2n+⋯<2n 次询问。如果我们知道了
0
0
0,那么从第一次筛的数剩下的里面找,总共支付
n
+
n
+
n
k
1
k
2
+
⋯
<
2.5
n
n + n + \frac{n}{k_1k_2} + \cdots < 2.5n
n+n+k1k2n+⋯<2.5n 次询问。但是问题是我们还要用次数来找到剩下两个数里谁是
0
0
0。我们考虑从第一次筛掉的数里面找证据,如果两个数和同一个数的
gcd
\gcd
gcd 不同,则说明这个证据不是那个大数的因子,而
gcd
\gcd
gcd 较大的那个对应是
0
0
0。在特判
1
1
1 的情况下我们之前检测的都不需要再来一遍,那么我们目前最坏情况就是在
k
1
∤
v
k_1 \not | v
k1∣v 的
v
v
v 中,较大数的因子正好在前面全部撞上。当靠前的有
k
j
≠
2
k_j \neq 2
kj=2 时,由于之前的询问空出来一些,可以认为是远够
d
(
n
)
d(n)
d(n) 用的。否则
k
j
k_j
kj 前面全是
2
2
2,那么那个数在奇数里的因子就会非常少。
以上分析并不够具体,但是在实验后发现确实做到了操作次数在范围内。
#include <algorithm>
#include <numeric>
#include <vector>
#include <random>
#include <chrono>
#include "gdzlib.h"
using namespace std;
int main() {
int N;
while ((N = GetN()) != -1) {
vector<int> cnt(N), candidate(N), level(N);
vector<bool> exc(N);
iota(candidate.begin(), candidate.end(), 0);
int x = 1;
bool flag = false;
while (candidate.size() > 2) {
vector<int> res(candidate.size());
for (int j : candidate) ++level[j];
for (int j = 1; j < candidate.size(); ++j) {
++cnt[res[j] = Ask(candidate[0], candidate[j])];
if (res[j] != 1) {
exc[candidate[0]] = exc[candidate[j]] = true;
}
}
if (x == 1 && cnt[1] == N - 1) {
flag = true;
Answer(candidate[0]);
break;
}
bool fl = true;
for (int j = x; j < N; j += x) if (!cnt[j]) fl = false;
if (fl && (x == 1 || candidate.size() > 3)) {
flag = true;
for (int j = 0; j < N; ++j)
if (j != candidate[0] && !exc[j] && Ask(candidate[0], j) == 1) {
Answer(j);
break;
}
break;
}
int k = (N - 1) / x * x;
while (!cnt[k]) k -= x;
for (int j = 1; j < candidate.size(); ++j)
--cnt[res[j]];
vector<int> newCandidate;
for (int j = 1; j < candidate.size(); ++j)
if (res[j] == k)
newCandidate.push_back(candidate[j]);
newCandidate.push_back(candidate[0]);
if (newCandidate.size() == 2 && candidate.size() > 3) {
int pos0 = newCandidate[0];
flag = true;
for (int i = 0; i < N; ++i)
if (!exc[i]) {
if (Ask(pos0, i) == 1) {
Answer(i);
break;
}
}
break;
}
swap(candidate, newCandidate);
x = k;
}
if (flag) continue;
int pos0 = -1;
vector<pair<int, int>> val(N);
for (int j = 0; j < N; ++j) val[j] = make_pair(level[j], j);
sort(val.begin(), val.end());
for (int j = 0; j < N; ++j) {
int iv = val[j].second;
if (iv == candidate[0] || iv == candidate[1]) continue;
exc[iv] = true;
int a = Ask(iv, candidate[0]), b = Ask(iv, candidate[1]);
if (a == 1 && b == 1) {
Answer(iv);
break;
}
if (a == b) continue;
pos0 = (a < b) ? candidate[1] : candidate[0];
for (int i = 0; i < N; ++i)
if (!exc[i]) {
if (Ask(pos0, i) == 1) {
Answer(i);
break;
}
}
break;
}
}
return 0;
}
Floyd-Warshall
简要题意:给一个有向有权图,问使用如下循环方式计算的最短路有多少对是错误的:
for (i : [1, n]) for (j : [1, n]) for (k : [1, n]) G[i][j] = min(G[i][j], G[i][k] + G[k][j])
其中 n ≤ 2 × 1 0 3 , m ≤ 3 × 1 0 3 n\le 2\times 10^3, m\le 3\times 10^3 n≤2×103,m≤3×103。
首先我们跑出所有点对最短路是可以 Θ ( n m log m ) \Theta(nm\log m) Θ(nmlogm),这是可接受的。然后考虑这个循环的本质就是给全体点对按顺序尝试计算,那么我们考虑一个点对是正确的当且仅当:
- 要么 u , v u,v u,v 没有路径
- 要么 u , v u,v u,v 之间有直接连边,且就是最短的
- 在处理 u , v u, v u,v 之前,存在一个点 w w w,使得 d ( u , v ) = d ( u , w ) + d ( w , v ) d(u,v)=d(u,w)+d(w,v) d(u,v)=d(u,w)+d(w,v),且 u , w u,w u,w 和 w , v w,v w,v 间的最短路已经正确计算出了。
所有满足等式的 w w w 可以在最短路 DAG 上传递闭包算出。那么我们只需要按顺序额外维护每个点到哪些点的距离,以及哪些点到这个点是被正确计算的。用 bitset 加速。时间复杂度 Θ ( n m log m + m n 2 w ) \Theta(nm\log m + \frac{mn^2}{w}) Θ(nmlogm+wmn2)。
#include <bits/stdc++.h>
#define LOG(FMT...) fprintf(stderr, FMT)
using namespace std;
struct Node {
int u, step;
Node(int u, int step) : u(u), step(step) {}
bool operator>(const Node& rhs) const { return step > rhs.step; }
};
const int N = 2010;
int n, m;
vector<pair<int, int>> g[N];
bitset<N> in[N], out[N], clo[N];
int dis[N], mat[N][N];
int main() {
#ifdef LBT
freopen("test.in", "r", stdin);
int nol_cl = clock();
#endif
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
while (m--) {
int u, v, w;
cin >> u >> v >> w;
g[u].emplace_back(v, w);
}
for (int i = 1; i <= n; ++i) {
memset(dis, -1, sizeof(dis));
dis[i] = 0;
priority_queue<Node, vector<Node>, greater<Node>> q;
q.emplace(i, 0);
while (!q.empty()) {
Node tmp = q.top(); q.pop();
if (dis[tmp.u] != tmp.step) continue;
for (const auto& [v, w] : g[tmp.u])
if (dis[v] == -1 || dis[v] > tmp.step + w)
q.emplace(v, dis[v] = tmp.step + w);
}
copy(dis + 1, dis + n + 1, mat[i] + 1);
}
for (int i = 1; i <= n; ++i) {
in[i].set(i);
out[i].set(i);
for (const auto& [j, w]: g[i])
if (mat[i][j] == w) {
in[i].set(j);
out[j].set(i);
}
}
int ans = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
clo[j].reset();
clo[j].set(j);
}
vector<pair<int, int>> du;
for (int j = 1; j <= n; ++j)
du.emplace_back(mat[i][j], j);
sort(du.begin(), du.end());
for (const auto& [d, u] : du)
for (const auto& [v, w] : g[u])
if (mat[i][v] == mat[i][u] + w)
clo[v] |= clo[u];
for (int j = 1; j <= n; ++j) {
if (in[i][j] || mat[i][j] == -1) continue;
clo[j].reset(i);
clo[j].reset(j);
if ((in[i] & out[j] & clo[j]).any()) {
in[i].set(j);
out[j].set(i);
} else
++ans;
}
}
cout << ans << '\n';
#ifdef LBT
LOG("Time: %dms\n", int ((clock()
-nol_cl) / (double)CLOCKS_PER_SEC * 1000));
#endif
return 0;
}