从今天起,关心粮食和蔬菜。
发现这种计算方式不存在进位,计算过程中只有每个相加的位在得数中对应一个长度为 1 或 2 的段,对原字符串进行每个划分的方案数 dp 即可。 Θ ( log n ) \Theta(\log n) Θ(logn)
代码
考虑最高位的贡献, 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)。
代码
由于仅仅是最小化目标,考虑一个数能否被取到,用于更新答案即可。 Θ ( n 2 ) \Theta(n^2) Θ(n2)
代码
考虑对 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),这个求和的结果其实并不大。
代码
首先至少要保证总共热量相同,也就是 ∑ 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)
代码
注意到 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)。
代码
首先需注意到两个维度是独立的,接下来我们要做的就是分别计算。考虑一个维度上给出了 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
代码
显然每次吃比自己小的鱼中最重的那条。假设下一条体重 ≥ \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)。
代码
坑
考虑至少某个满足的方案数 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)。
代码
转为对偶图的连通性问题,但是并不是无向边。我们维护 S 可达区域和可达 T 的区域,S 的区域是一个右上角的阶梯形,T 的区域是左下角的一个阶梯形,加一条边的时候如果恰好切断,说明一端在 S 中一端在 T 中。用树状数组可以在加入一条边的时候扩展边界线,每条线维护一个堆用于找到扩展时新的可以可用边。
时间复杂度 Θ ( k log k ) \Theta(k\log k) Θ(klogk)。
代码
值域非常大,考虑随机二分。我们首先边分治,然后考虑每个 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)。
代码
坑
接下来的部分是 PA 2019 Final,由于并不公开数据,在 LOJ 上是没有的。由于是波兰语,本文也做简要题意叙述。
简要题意:给一颗有根树,问找到一个最长的不重复点序列使得相邻两个点有祖先关系。 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
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;
}
简要题意:给出 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
#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;
}
简要题意:给一个三维棋盘,每个格子上有一些棋子,可以将棋子沿 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 的。时间复杂度 Θ ( 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
#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;
}
简要题意:给一个直方图,每个位置有个高度 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
#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;
}
简要题意:给出 n n n 个三角形,依次按照给定的点的顺序绘制,当一个点被画过偶数次就会消失,奇数次就会重现,问结果得到的可见线段长度和。 n ≤ 1 0 5 n\le 10^5 n≤105。
容易发现这就是一个线段的异或和问题,每个直线上的各自排序即可。时间复杂度 Θ ( N log N ) \Theta(N\log N) Θ(NlogN)。
#include
#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;
}
简要题意:一个字符串,问如果将每个相同字符替换成同方向的一个箭头(向上或向下),则最大的首尾相连构成这条路径的下方围着的面积是多少? ∣ 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
#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;
}
简要题意:问有多少个 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)。
#include
#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;
}
简要题意:交互题,一个 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
#include
#include
#include
#include
#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;
}
简要题意:给一个有向有权图,问使用如下循环方式计算的最短路有多少对是错误的:
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),这是可接受的。然后考虑这个循环的本质就是给全体点对按顺序尝试计算,那么我们考虑一个点对是正确的当且仅当:
所有满足等式的 w w w 可以在最短路 DAG 上传递闭包算出。那么我们只需要按顺序额外维护每个点到哪些点的距离,以及哪些点到这个点是被正确计算的。用 bitset 加速。时间复杂度 Θ ( n m log m + m n 2 w ) \Theta(nm\log m + \frac{mn^2}{w}) Θ(nmlogm+wmn2)。
#include
#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;
}