Solution Set #5
qoj9237. Message(通信,adhoc,基环树)
首先这题字符串长度不固定,考虑在字符串后面加一个 \(100\ldots0\),补成一个长度为 \(1025\) 的串,把这个串传递过去后再把得到的串最后一个 \(1\) 后面的删掉即可。
设传递的信息是 \(R_1,R_2,\ldots,R_{66}\)。注意到这题只有 \(66\times 16=1056\) 个真实信息,所以第一个人必须用至多 \(31\) 次真实信息来让第二个人识别出 \(16\) 个真实位,剩余真实信息用于传递字符串。
一个很有创造性的想法是对于一个真实位 \(x\),设 \(x\) 在真实位中的后继是 \(y\)(末尾的后继就是开头),令 \(d=(y-x)\bmod 31\),让 \(R_{1,x}=R_{2,x}=\ldots=R_{d-1,x}=0,R_{d,x}=1\),\(R_{d+1,x}\sim R_{66,x}\) 传递字符串。由于 \(\sum d=31\),所以传递字符串的信息数量刚好够。
第二个人识别时,对每位 \(x\) 找到最小的 \(i\) 满足 \(R_{i,x}=1\),连一条 \(x\to (x+i)\bmod 31\) 的有向边,容易发现会连出来一个基环树。最终的真实位会在这个基环树上构成一个大小为 \(16\) 的环。注意到一个 \(31\) 个点的基环树至多只有一个大小为 \(16\) 的环,所以真实位构成的环是唯一的!求出这个环之后再依次枚举其余真实信息即可得到传递的字符串。
代码
#include "message.h"
#include <bits/stdc++.h>
#ifdef ORZXKR
#include "grader.cpp"
#endif
void send_message(std::vector<bool> M, std::vector<bool> C) {
M.emplace_back(1);
for (; M.size() < 1025; M.emplace_back(0)) {}
std::vector<std::vector<bool>> R(66, std::vector<bool>(31));
std::vector<int> pos;
for (int i = 0; i < 31; ++i)
if (!C[i])
pos.emplace_back(i);
int now = 0;
for (int i = 0; i < pos.size(); ++i) {
int d = (pos[(i + 1) % pos.size()] - pos[i] + 31) % 31;
assert(d);
R[d - 1][pos[i]] = 1;
for (int j = d; j < R.size(); ++j) R[j][pos[i]] = M[now++];
}
for (auto &x : R) send_packet(x);
}
std::vector<bool> receive_message(std::vector<std::vector<bool>> R) {
static int nxt[31];
for (int i = 0; i < 31; ++i) {
nxt[i] = -1;
for (int j = 0; j < R.size(); ++j) {
if (R[j][i]) {
nxt[i] = (i + j + 1) % 31;
break;
}
}
// std::cerr << i << " : " << nxt[i] << '\n';
}
std::vector<int> pos;
for (int i = 0; i < 31; ++i) {
static bool vis[31];
if (nxt[i] == -1) continue;
memset(vis, 0, sizeof(vis));
std::vector<int> vec;
for (int j = i; !vis[j]; j = nxt[j]) {
vec.emplace_back(j);
vis[j] = 1;
}
if (vec.size() == 16 && nxt[vec.back()] == vec[0]) {
pos = vec;
break;
}
}
assert(pos.size() == 16);
std::vector<bool> M;
for (int i = 0; i < pos.size(); ++i) {
int d = (pos[(i + 1) % pos.size()] - pos[i] + 31) % 31;
for (int j = d; j < R.size(); ++j) M.emplace_back(R[j][pos[i]]);
}
for (; M.back() == 0; M.pop_back()) {}
M.pop_back();
return M;
}
P10873. [COTS 2022] 帽子 Šeširi(构造,adhoc,图论,欧拉回路)
首先考虑限制条件是至少 \(\left\lfloor\frac{n}{2}\right\rfloor\) 个人正确怎么做。对于一个人 \(i\),假设他看到的白色集合是 \(S\),那么他选红色/白色能够分别在总的白色集合是 \(S\) 和 \(S\cup\set i\) 的情况下正确,也就是说他能给 \(S\) 和 \(S\cup\set i\) 中的恰好一个正确。容易想到建图,给 \(S\) 和 \(S\cup\set i\) 连一条无向边,问题变为给无向边定向,使得每个点的入边不少于出边减一。建一个虚点连所有奇度点,再跑欧拉回路即可。
再来做限制条件是每种颜色分别有至少一半的人正确怎么做。考虑将状态变为 \((S,0/1)\) 表示集合 \(S\) 颜色为 \(0/1\) 的限制条件,每次给 \((S,0)\) 和 \((S\cup\set i,1)\) 连边即可。
总时间复杂度是 \(O(2^nn)\)。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 19, kMaxS = (1 << 18) + 5, kMaxE = 3e6 + 5;
int n, ecnt;
int cur[kMaxS << 1];
char res[kMaxN][kMaxS >> 1];
bool vis[kMaxE];
std::vector<std::pair<int, int>> G[kMaxS << 1];
std::vector<int> stk;
int getid(int s, int o) { return s + (o << n); }
void add(int u, int v) {
++ecnt;
G[u].emplace_back(v, ecnt);
G[v].emplace_back(u, ecnt);
}
void dfs(int u) {
for (int i = cur[u]; i < G[u].size(); i = cur[u]) {
cur[u] = i + 1;
auto [v, id] = G[u][i];
if (vis[id]) continue;
vis[id] = 1;
dfs(v);
if (u < (1 << (n + 1)) && v < (1 << (n + 1))) {
int p = __builtin_ctz(u ^ v);
assert((u ^ v) == (1 << p) + (1 << n));
int s = ((u >> (p + 1) << p) | (u & ((1 << p) - 1))) & ((1 << (n - 1)) - 1);
res[p][s] = "BC"[v >> p & 1];
}
}
}
void dickdreamer() {
std::cin >> n;
for (int s = 0; s < (1 << n); ++s) {
for (int i = 0; i < n; ++i) {
if (~s >> i & 1) {
add(getid(s, 0), getid(s ^ (1 << i), 1));
}
}
}
for (int s = 0; s < (1 << (n + 1)); ++s) {
if (G[s].size() & 1) {
add(1 << (n + 1), s);
}
}
for (int s = 0; s <= (1 << (n + 1)); ++s)
if (cur[s] < G[s].size())
dfs(s);
for (int i = 0; i < n; ++i) {
for (int s = 0; s < (1 << (n - 1)); ++s)
std::cout << res[n - 1 - i][s];
std::cout << '\n';
}
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
uoj951. 电阻匹配(计数dp,生成函数,双指针,min-max容斥)
首先想到对答案进行 min-max 容斥,式子是 \(\text{kthmin}(S)=\sum_{T\subseteq S,|T|\geq k}{(-1)^{|T|-k}\binom{|T|-1}{k-1}\max(T)}\)。现在只需要对于每个 \(i\) 和 \(j\),求出任选一个大小为 \(i\) 的匹配,每对数之和不超过 \(j\) 的方案数即可,贡献系数是容易推的。
先对序列排序,再将两两之和离散化。我们枚举每个和 \(w=a_x+a_y\),需要对每个 \(i\),求出任选 \(i\) 个匹配,每个匹配之和不超过 \(w\) 的方案数。
将 \(a_i\leq \frac{w}{2}\) 的 \(a_i\) 称作小数,其余的称作大数。那么小数匹配小数一定合法,大数匹配大数一定不合法,所以只需要对每个 \(j\),求出小数与大数匹配数量为 \(j\) 的方案数,再枚举小数内部的匹配数 \(k\) 就能组合数求答案了。设 \(r_i\) 表示第 \(i\) 个大数能匹配的小数数量,然后就能 dp 了。从大到小枚举大数,设 \(f_{i,j}\) 表示考虑到了第 \(i\) 个大数,匹配数量为 \(j\) 的方案数,转移方程为:\(f_{i,j}=f_{i-1,j}+(r_i-j+1)f_{i-1,j-1}\)。
单次 dp 和枚举小数内部贡献都是 \(O(n^2)\) 的,而不同的 \(w\) 有 \(O(n^2)\) 种,所以复杂度是 \(O(n^4)\)。
考虑优化。注意到对于小数数量相同的 \(w\),小数内部贡献的系数是相同的,可以对这些 \(w\) 的 \(f\) 数组求和后一起转移。但是 dp 求 \(f\) 数组还是要 \(O(n^2)\)。
直接看起来已经无法优化了,考虑更改状态,即设 \(g_{i,j}=f_{i,i-j}\),那么 \(g_{i,j}=g_{i-1,j-1}+(r_i-i+j+1)g_{i-1,j}\)。设 \(G_i(x)=\sum g_{i,j}x^j\),则有 \(G_i(x)=xG_{i-1}(x)+(r_i-i+1)G_{i-1}(x)+xG_{i-1}'(x)=(r_i-i+1)G_{i-1}(x)+x(G_{i-1}(x)+G_{i-1}'(x))\)。再设一个 \(H_i(x)=e^xG_i(x)\),那么 \(H_i(x)=(r_i-i+1)H_{i-1}(x)+xH_{i-1}'(x)\)。
系数的变化变为 \([x^j]H_i(x)=(r_i-i+1+j)[x^j]H_{i-1}(x)\)!这么变换之后 dp 数组每个位置的值就是个乘积的形式了,每个位置的值只有在某个 \(r_i\) 变化的时候会变化,而 \(r_i\) 的总变化量只有 \(O(n^2)\),所以可以用 \(O(n^3)\) 的复杂度对每个 \(w\) 求出 \(H_{*}(x)\)。求答案时,对每种小数数量,\(O(n^2)\) 求出对应 \(w\) 的 \(H_{*}(x)\) 的和,再除以 \(e^x\) 即可得到 \(f\) 数组的和。
时间复杂度是 \(O(n^3)\)。
总结:对于生成函数系数中出现类似 \(F(x)+F'(x)\) 的东西,要想到 \([e^xF(x)]'=e^x(F(x)+F'(x))\),再用这个式子进行尝试。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 805, kMaxT = kMaxN * (kMaxN - 1) / 2, kMod = 998244353;
int n, t;
int a[kMaxN], val[kMaxT], r[kMaxN], c[kMaxN][kMaxN], coef[kMaxN][kMaxN], res[kMaxN];
int s[kMaxN][kMaxN], sh[kMaxN][kMaxN];
int fac[kMaxN], ifac[kMaxN], inv[kMaxN], C[kMaxN][kMaxN];
int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1)
ret = (int64_t)ret * bs % kMod;
return ret;
}
inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }
inline int getop(int x) { return (~x & 1) ? 1 : (kMod - 1); }
inline int getinv(int x) {
assert(x);
if (x < kMod / 2) return inv[x];
else return kMod - inv[kMod - x];
}
struct Info {
int val, cnt0;
Info(int _val = 1, int _cnt0 = 0) : val(_val), cnt0(_cnt0) {}
friend Info operator *(Info a, Info b) { return {1ll * a.val * b.val % kMod, a.cnt0 + b.cnt0}; };
friend Info operator /(Info a, Info b) { return {1ll * a.val * getinv(b.val) % kMod, a.cnt0 - b.cnt0}; };
int getval() {
assert(cnt0 >= 0);
return !cnt0 ? val : 0;
}
} h[kMaxN];
Info trans(int v) {
v %= kMod;
if (!v) return {1, 1};
else return {v, 0};
}
void prework(int n = 800) {
fac[0] = ifac[0] = C[0][0] = 1;
for (int i = 1; i <= n; ++i) {
fac[i] = 1ll * fac[i - 1] * i % kMod;
ifac[i] = qpow(fac[i]);
inv[i] = qpow(i);
C[i][0] = 1;
for (int j = 1; j <= i; ++j) C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
}
}
void getcoef() {
for (int i = 0; i <= 2 * n; ++i)
for (int j = 0; j <= i / 2; ++j)
c[i][j] = 1ll * fac[i] * ifac[i - 2 * j] % kMod * ifac[j] % kMod * qpow(qpow(2, j)) % kMod;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= i; ++j)
coef[i][j] = 1ll * getop(i - j) * C[i - 1][j - 1] % kMod * c[2 * (n - i)][n - i] % kMod;
}
void rebuild(int w) {
int lim = 0;
for (int i = 1; i <= 2 * n; ++i) lim += (a[i] > w / 2);
for (int i = 0; i <= lim; ++i) h[i] = Info{ifac[i], 0};
for (int i = 2 * n, ct = 0; i; --i) {
if (a[i] <= w / 2) break;
++ct;
for (r[i] = r[i + 1]; r[i] < 2 * n && a[i] + a[r[i] + 1] <= w && a[r[i] + 1] <= w / 2; ++r[i]) {}
for (int j = 0; j <= lim; ++j) h[j] = h[j] * trans(r[i] - ct + 1 + j + kMod);
}
}
void solve(int w, int det, int &lst) {
// static int f[2][kMaxN], g[kMaxN];
// int o = 0, ct = 0;
// std::fill_n(f[o], 2 * n + 1, 0);
// f[o][0] = 1;
// for (int i = 2 * n; i; --i) {
// if (a[i] <= w / 2) continue;
// ++ct;
// for (r[i] = 0; r[i] < 2 * n && a[i] + a[r[i] + 1] <= w && a[r[i] + 1] <= w / 2; ++r[i]) {}
// // std::cerr << r[i] << ' ';
// o ^= 1;
// std::fill_n(f[o], 2 * n + 1, 0);
// for (int j = 0; j <= ct; ++j) {
// if (j) f[o][j] = f[o ^ 1][j - 1];
// inc(f[o][j], 1ll * f[o ^ 1][j] * (r[i] - ct + j + 1) % kMod);
// }
// }
// for (int i = 0; i <= ct; ++i) inc(s[ct][i], 1ll * det * f[o][i] % kMod);
// static int h[kMaxN];
// for (int i = 0; i <= 2 * n; ++i) h[i] = ifac[i];
// int ct = 0;
// for (int i = 2 * n; i; --i) {
// if (a[i] <= w / 2) continue;
// ++ct;
// for (r[i] = 0; r[i] < 2 * n && a[i] + a[r[i] + 1] <= w && a[r[i] + 1] <= w / 2; ++r[i]) {}
// for (int j = 0; j <= 2 * n; ++j) h[j] = 1ll * h[j] * (r[i] - ct + 1 + j + kMod) % kMod;
// }
int lim = 0;
for (int i = 1; i <= 2 * n; ++i) lim += (a[i] > w / 2);
if (lst != lim) {
rebuild(w);
lst = lim;
} else {
// rebuild(w);
int ct = 0;
for (int i = 2 * n; i; --i) {
if (a[i] <= w / 2) continue;
++ct;
if (r[i] < 2 * n && a[i] + a[r[i] + 1] <= w && a[r[i] + 1] <= w / 2) {
for (int j = 0; j <= lim; ++j) h[j] = h[j] / trans(r[i] - ct + 1 + j + kMod);
for (r[i] = 0; r[i] < 2 * n && a[i] + a[r[i] + 1] <= w && a[r[i] + 1] <= w / 2; ++r[i]) {}
for (int j = 0; j <= lim; ++j) h[j] = h[j] * trans(r[i] - ct + 1 + j + kMod);
}
}
}
for (int i = 0; i <= lim; ++i) inc(sh[lim][i], 1ll * det * h[i].getval() % kMod);
}
void update(int ct) {
static int g[kMaxN];
for (int i = 0; i <= ct; ++i) {
s[ct][i] = sh[ct][i];
for (int j = 0; j < i; ++j) dec(s[ct][i], 1ll * s[ct][j] * ifac[i - j] % kMod);
// std::cerr << s[ct][i] << " \n"[i == 2 * n];
}
int sz = 2 * n - ct;
std::fill_n(g, 2 * n + 1, 0);
for (int i = 0; i <= std::min(ct, sz); ++i) {
for (int j = 0; j <= (sz - i) / 2; ++j) {
inc(g[i + j], 1ll * s[ct][ct - i] * c[sz - i][j] % kMod);
}
}
// std::cerr << w << ' ' << det << " : ";
// for (int i = 1; i <= n; ++i) std::cerr << g[i] << " \n"[i == n];
for (int i = 1; i <= n; ++i) {
if (!g[i]) continue;
for (int j = 1; j <= i; ++j)
inc(res[j], 1ll * g[i] * coef[i][j] % kMod);
}
}
void dickdreamer() {
std::cin >> n;
for (int i = 1; i <= 2 * n; ++i) std::cin >> a[i];
std::sort(a + 1, a + 1 + 2 * n);
for (int i = 1; i <= 2 * n; ++i)
for (int j = i + 1; j <= 2 * n; ++j)
val[++t] = a[i] + a[j];
std::sort(val + 1, val + 1 + t);
t = std::unique(val + 1, val + 1 + t) - (val + 1);
getcoef();
for (int i = 0; i <= 2 * n; ++i) h[i] = Info{ifac[i], 0};
int lst = -1;
for (int i = 1; i <= t; ++i) {
solve(val[i], i == t ? val[i] : sub(val[i], val[i + 1]), lst);
}
for (int i = 0; i <= 2 * n; ++i) update(i);
for (int i = 1; i <= n; ++i) std::cout << res[i] << " \n"[i == n];
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
prework();
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
P14515 [NFLSPC #8] 小 P,小 W,棒棒糖(计数dp,容斥,状态压缩)
首先 \(\prod(deg_i+t)\) 可以看成对于每个点 \(i\),选择一个存在的出边连出去,或者不选,代价为 \(t\) 的方案总代价和。设 \(sp_i\) 表示 \(i\) 的邻域中的边保留的概率之和,直接算 \(\prod(sp_i+t)\) 会算重,因为如果一条出现概率是 \(p\) 的边被钦定出现两次,这个东西把概率算成 \(p^2\) 而不是 \(p\)。
解决方案是简单的,容斥重边,即对于重边,其代价是 \(p-p^2\),对于不在钦定的重边上的所有点 \(i\),代价还是 \(sp_i+t\)。根据这个容斥方式,可以得到一个 \(O(2^nn)\) 的状压 dp 做法。由于对 \(k\) 分块后,第 \(i\) 个块内的点只会和 \(i-1,i,i+1\) 这些块有连边,所以可以按块进行 dp,从第 \(i-1\) 个块转移到第 \(i\) 个块时,状态里只需要记录 \(i-1\) 和 \(i\) 两个块里的 \(2k\) 个点是否被选进重边里就行了。复杂度是 \(O(4^{k}(n+m))\)。
考虑优化。上面的做法仍然有很多冗余状态,因为在依次加入第 \(i\) 个块的点进行转移的过程中,如果一个属于块 \(i-1\) 的点,其邻域中所有在块 \(i\) 中的点都已经被加入了,那么它之后就不会再参与转移了,也就可以删掉。记没有被删掉的点为关键点,现在的目标是尽可能最小化任意一个时刻关键点数量的最大值。
看起来并不好做,注意到块 \(i-1\) 的点邻域在块 \(i\) 中的点至多有 \(2\) 个,这启发我们对这两个点连虚边,如果只有一个就连自环。那么当块 \(i\) 加入点的集合是 \(S\) 时,关键点数量就是 \(|S|+k-S\text{导出子图虚边数量}\)。放到虚边的图上看,就是要尽可能满足新加入的点和已经加入到点有连边,因为加入这样的点之后关键点数量不会增加。容易发现对于一棵树,直接按照 dfs 序加点可以保证关键点的增加量恰好为 \(1\)。对于一个非树连通块,随便选择一个生成树的 dfs 序加入,可以保证关键点增加量的历史最大值不超过 \(1\),且最终的增加量不超过 \(|V|-|E|\leq 0\)。
所以先按照 dfs 序加入虚图非树连通块的点,再加入树连通块的点。加入非树连通块点的过程中关键点增加量不会超过 \(1\),加入树连通块的过程中每次都会增加,但是由于最终的关键点数量是 \(k\),所以这部分关键点数量也不会超过 \(k+O(1)\)。总的时间复杂度就是 \(O(2^k(n+m))\)。
代码
#include <bits/stdc++.h>
// #define int int64_t
const int kMaxN = 105, kMaxM = 505, kMaxS = (1 << 22) + 5, kMod = 998244353;
int n, m, k, t, sid;
int u[kMaxM], v[kMaxM], p[kMaxM], g[kMaxN][kMaxN], w[kMaxN][kMaxN];
int sump[kMaxN], f[2][kMaxS];
int pos[kMaxN];
bool vis[kMaxN];
std::vector<int> nxt[kMaxN], id;
int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1)
ret = (int64_t)ret * bs % kMod;
return ret;
}
inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }
struct DSU {
int fa[kMaxN], val[kMaxN];
void init(int n) {
for (int i = 1; i <= n; ++i) fa[i] = i, val[i] = 1;
}
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
void unionn(int x, int y) {
int fx = find(x), fy = find(y);
if (fx != fy) fa[fx] = fy, val[fy] += val[fx];
--val[fy];
}
} dsu;
void del(std::vector<int> &id, int *f, int x) {
static int tmp[kMaxS];
int len = id.size(), p = -1;
for (int i = 0; i < id.size(); ++i) {
if (id[i] == x) {
p = i;
break;
}
}
for (int s = 0; s < (1 << id.size()); ++s) tmp[s] = f[s], f[s] = 0;
assert(p != -1);
id.erase(id.begin() + p), vis[x] = 0;
for (int s = 0; s < (1 << id.size()); ++s) f[s] = tmp[(s & ((1 << p) - 1)) | (s >> p << (p + 1))];
for (int i = 0; i < id.size(); ++i) pos[id[i]] = i;
}
std::vector<int> build(int c) {
static std::vector<int> G[kMaxN];
static bool vis[kMaxN] = {0};
int L = c * k, R = std::min((c + 1) * k - 1, n - 1);
dsu.init(n);
for (int i = L; i <= R; ++i) G[i].clear(), vis[i] = 0;
if (c) {
for (int i = L - k; i < L; ++i) {
if (nxt[i].size() == 1) {
dsu.unionn(nxt[i][0], nxt[i][0]);
} else if (nxt[i].size() == 2) {
dsu.unionn(nxt[i][0], nxt[i][1]);
G[nxt[i][0]].emplace_back(nxt[i][1]);
G[nxt[i][1]].emplace_back(nxt[i][0]);
}
}
}
std::vector<int> ret;
std::vector<std::pair<int, int>> vec;
for (int i = L; i <= R; ++i)
if (dsu.find(i) == i)
vec.emplace_back(dsu.val[i], i);
std::sort(vec.begin(), vec.end());
std::function<void(int)> dfs = [&] (int u) {
ret.emplace_back(u);
vis[u] = 1;
for (auto v : G[u]) {
if (!vis[v]) dfs(v);
}
};
for (auto [p, i] : vec) dfs(i);
return ret;
}
void dickdreamer() {
std::cin >> n >> m >> k >> t >> sid;
for (int i = 1; i <= m; ++i) {
std::cin >> u[i] >> v[i] >> p[i];
if (u[i] / k == v[i] / k + 1) nxt[v[i]].emplace_back(u[i]);
if (v[i] / k == u[i] / k + 1) nxt[u[i]].emplace_back(v[i]);
p[i] = sub(1, p[i]);
inc(sump[u[i]], p[i]);
inc(sump[v[i]], p[i]);
g[u[i]][v[i]] = g[v[i]][u[i]] = p[i];
w[u[i]][v[i]] = w[v[i]][u[i]] = 1ll * p[i] * sub(1, p[i]) % kMod;
}
int o = 0;
f[o][0] = 1;
std::vector<int> id;
for (int c = 0; c <= (n - 1) / k; ++c) {
int L = c * k, R = std::min((c + 1) * k - 1, n - 1);
auto arr = build(c);
for (auto i : arr) {
// std::cerr << "ins " << i << '\n';
vis[i] = 1;
o ^= 1;
id.emplace_back(i);
for (int j = 0; j < id.size(); ++j) pos[id[j]] = j;
std::fill_n(f[o], 1 << id.size(), 0);
for (int s = 0; s < (1 << (id.size() - 1)); ++s) {
inc(f[o][s], 1ll * f[o ^ 1][s] * (sump[i] + t) % kMod);
inc(f[o][s + (1 << (id.size() - 1))], f[o ^ 1][s]);
for (int j = 0; j < id.size() - 1; ++j) {
if (id[j] >= L && (s >> j & 1) && w[id[j]][i]) {
inc(f[o][s ^ (1 << j)], 1ll * f[o ^ 1][s] * w[id[j]][i] % kMod);
}
}
// if (i == 0) std::cerr << "md " << f[o ^ 1][s] << '\n';
}
// if (i == 1) {
// std::cerr << "made ";
// for (auto x : id) std::cerr << x << ' ';
// std::cerr << " : ";
// for (int s = 0; s < (1 << id.size()); ++s) std::cerr << f[o][s] << ' ';
// std::cerr << '\n';
// }
std::vector<int> dv;
for (int j = 0; j < id.size(); ++j) {
if (id[j] < L && id[j] >= L - k) {
bool fl = 1;
for (auto x : nxt[id[j]]) fl &= vis[x];
if (fl) dv.emplace_back(id[j]);
}
}
for (auto x : dv) {
o ^= 1;
std::fill_n(f[o], 1 << id.size(), 0);
if (!nxt[x].size()) {
for (int s = 0; s < (1 << id.size()); ++s)
if (~s >> pos[x] & 1)
f[o][s] = f[o ^ 1][s];
} else if (nxt[x].size() == 1) {
for (int s = 0; s < (1 << id.size()); ++s) {
if (~s >> pos[x] & 1) {
inc(f[o][s], f[o ^ 1][s]);
if (~s >> pos[nxt[x][0]] & 1)
inc(f[o][s], 1ll * f[o ^ 1][s | (1 << pos[x]) | (1 << pos[nxt[x][0]])] * w[x][nxt[x][0]] % kMod);
}
}
} else {
assert(nxt[x].size() == 2);
for (int s = 0; s < (1 << id.size()); ++s) {
if (~s >> pos[x] & 1) {
inc(f[o][s], f[o ^ 1][s]);
if (~s >> pos[nxt[x][0]] & 1)
inc(f[o][s], 1ll * f[o ^ 1][s | (1 << pos[x]) | (1 << pos[nxt[x][0]])] * w[x][nxt[x][0]] % kMod);
if (~s >> pos[nxt[x][1]] & 1)
inc(f[o][s], 1ll * f[o ^ 1][s | (1 << pos[x]) | (1 << pos[nxt[x][1]])] * w[x][nxt[x][1]] % kMod);
}
}
}
}
for (auto x : dv) del(id, f[o], x);
// {
// std::cerr << "hyw ";
// for (auto x : id) std::cerr << x << ' ';
// std::cerr << " : ";
// for (int s = 0; s < (1 << id.size()); ++s) std::cerr << f[o][s] << ' ';
// std::cerr << '\n';
// }
}
}
std::cout << f[o][0] << '\n';
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}
P13758. 【MX-X17-T7】夏终(根号分治,最小生成树,凸包)
首先最终的最小生成树一定由 \(E\) 和 \(E'\) 分别最小生成树上的边构成。容易发现 \(E'\) 最小生成树一定是取 \(b_i\) 最小的 \(x\),每条边形如 \((x,i,b_x+b_i+C)\)。而 \(E\) 的最小生成树就会很普通,放到修改上会很难求答案。注意到我们只关心最小生成树的权值和,所以具体形态并不重要,只要保证连通性正确,也就是说我们可以对 \(E\) 的边进行一些改造,只需要满足按照从小到大加边权的过程中任意时刻连通性正确即可。显然改造成链会让问题看起来最简单,具体改造方式就是建出 kruskal 重构树,按照 kruskal 重构树的 dfs 序求出一个序列。那么在这个序列上每次一定是合并两个相邻区间,将原边权放在这两个区间之间即可。
现在问题变为给定一条链,\(i-1\) 到 \(i\) 的边权是 \(w_i\),求这条链和 \(E'\) 合并之后的最小生成树。把最终最小生成树中链部分的边拿出来,会构成若干个区间 \([l,r]\),贡献是 \(\sum_{i=l+1}^{r}{w_i}+\min_{i=l}^{r}{b_i+b_x+C}\),最终再减去 \(b_x+C\)。根据这个,容易得到 dp:设 \(f_{i,0/1}\) 表示考虑了前 \(i\) 个点,\(i\) 所在段是否确定与 \(x\) 连边的点的最小贡献和。转移方程是:
直接做时间复杂度是 \(O(nq)\)。由于 \(C\) 带修,所以一定是要维护出所有 \(f_{n,i,0/1}\) 表示其中 \(C\) 贡献了 \(i\) 次,其余的最小贡献。经过打表会发现这个是凸的(证明见官方题解,好像是费用流解释的),有了这个性质就可以直接用线段树维护了,每个子树的信息形如 \(2\times 2\) 个大小为子树大小 \(len\) 的凸包,每次 pushup 时间复杂度是 \(O(len)\)。直接维护还是 \(O(nq)\) 的,再对序列根号分治一下即可。假设块长是 \(B\),时间复杂度为 \(O\left(n\log n+q\left(B+\frac{n\log n}{B}\right)\right)\),取 \(B=\sqrt{n\log n}\) 最优,最终时间复杂度就是 \(O(n\log n+q\sqrt{n\log n})\),可以通过。
注意到瓶颈在于 \(\frac{qn}{B}\) 次二分查询 \(\max\{x\cdot i+a_i\}\) 信息,离线下来用指针维护可以把 \(\log\) 去掉,做到 \(O(n\log n+q\sqrt n)\)。
对于这类只需要求最小生成树权值或者说做最小生成树合并的题目,都可以通过 kruskal 重构树把不变的部分变成链,且保证连通性不变。
对于这类需要用线段树带修维护区间凸包信息的题目,如果需要求的 dp 状态不过,可以考虑根号分治。
代码
#include <bits/stdc++.h>
#define int int64_t
using Hull = std::vector<int>;
const int kMaxN = 2e5 + 5, kMaxM = 3e5 + 5, kMaxQ = 5e4 + 5, kMaxB = 1300 + 5, kMaxT = kMaxN / (kMaxB - 5) + 5;
struct Edge {
int u, v, w;
} e[kMaxM];
int cid, n, m, q, c, B, tot;
int b[kMaxN], id[kMaxN], pos[kMaxN], w[kMaxN];
int bel[kMaxN], L[kMaxT], R[kMaxT];
int cc[kMaxQ], det[kMaxQ], qx[kMaxQ], qy[kMaxQ];
int f[kMaxQ][2];
std::vector<int> T[kMaxN * 2], qr[kMaxT];
inline void chkmax(int &x, int y) { x = (x > y ? x : y); }
inline void chkmin(int &x, int y) { x = (x < y ? x : y); }
struct DSU {
int cnt, fa[kMaxN], id[kMaxN], val[kMaxN * 2];
void init(int n) {
cnt = n;
for (int i = 1; i <= n; ++i) fa[i] = id[i] = i;
}
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
bool unionn(int x, int y, int w) {
int fx = find(x), fy = find(y);
if (fx != fy) {
fa[fx] = fy, T[++cnt] = {id[fx], id[fy]};
id[fy] = cnt, val[cnt] = w;
return 1;
} else {
return 0;
}
}
} dsu;
void build() {
dsu.init(n);
std::vector<std::tuple<int, int, int>> ev;
for (int i = 1; i <= m; ++i) ev.emplace_back(e[i].w, e[i].u, e[i].v);
std::sort(ev.begin(), ev.end());
for (auto [w, u, v] : ev) {
// std::cerr << "!!! " << u << ' ' << v << ' ' << w << '\n';
dsu.unionn(u, v, w);
}
// std::cerr << "le\n";
static bool vis[kMaxN * 2] = {0};
std::fill_n(w + 1, n, 1e12);
int dfn_cnt = 0;
std::function<void(int)> dfs = [&] (int u) {
assert(!vis[u]);
vis[u] = 1;
if (u <= n) {
id[++dfn_cnt] = u, pos[u] = dfn_cnt;
} else {
dfs(T[u][0]);
w[dfn_cnt + 1] = dsu.val[u];
dfs(T[u][1]);
}
};
for (int i = dsu.cnt; i; --i)
if (!vis[i])
dfs(i);
}
Hull operator +(Hull &a, Hull &b) {
if (!a.size()) return b;
if (!b.size()) return a;
std::vector<int> c(a.size() + b.size() - 1, (int)1e18);
// for (int i = 2; i < a.size(); ++i) assert(a[i - 1] - a[i - 2] <= a[i] - a[i - 1]);
// for (int i = 2; i < b.size(); ++i) assert(b[i - 1] - b[i - 2] <= b[i] - b[i - 1]);
for (int i = 0, j = 0; i + j < c.size();) {
c[i + j] = a[i] + b[j];
if (i + 1 < a.size() && (j == b.size() - 1 || a[i + 1] - a[i] < b[j + 1] - b[j])) ++i;
else ++j;
}
// for (int i = 0; i < a.size(); ++i)
// for (int j = 0; j < b.size(); ++j)
// chkmin(c[i + j], a[i] + b[j]);
return c;
}
Hull operator +(Hull a, int b) {
for (auto &x : a) x += b;
return a;
}
Hull min(Hull a, Hull b) {
Hull c(std::max(a.size(), b.size()));
for (int i = 0; i < c.size(); ++i) {
c[i] = 1e18;
if (i < a.size()) chkmin(c[i], a[i]);
if (i < b.size()) chkmin(c[i], b[i]);
}
return c;
}
int getval(Hull &a, int b) {
if (!a.size()) return 1e18;
int L = 0, R = a.size(), res = 0;
std::function<int(int)> calc = [&] (int i) {
return b * i + a[i];
};
while (L + 1 < R) {
int mid = (L + R) >> 1;
if (calc(mid) <= calc(mid - 1)) L = res = mid;
else R = mid;
}
return calc(res);
// int ret = 1e18;
// for (int i = 0; i < a.size(); ++i) chkmin(ret, i * b + a[i]);
// return ret;
}
struct SGT {
int base;
Hull t[kMaxB * 4][2][2];
void pushup(int x) {
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
t[x][i][j] = min(t[x << 1][i][0] + t[x << 1 | 1][0][j], t[x << 1][i][1] + t[x << 1 | 1][1][j]);
}
void build(int x, int l, int r) {
if (l == r) {
t[x][0][0] = {w[base + l]};
t[x][1][0] = {0};
t[x][0][1] = {(int)1e18, w[base + l] + b[id[base + l]]};
t[x][1][1] = {w[base + l], b[id[base + l]]};
return;
}
int mid = (l + r) >> 1;
build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);
pushup(x);
}
void update(int x, int l, int r, int ql) {
if (l == r) {
t[x][0][0] = {w[base + l]};
t[x][1][0] = {0};
t[x][0][1] = {(int)1e18, w[base + l] + b[id[base + l]]};
t[x][1][1] = {w[base + l], b[id[base + l]]};
return;
}
int mid = (l + r) >> 1;
if (ql <= mid) update(x << 1, l, mid, ql);
else update(x << 1 | 1, mid + 1, r, ql);
pushup(x);
}
std::vector<std::vector<int>> getmat(int c) {
std::vector<std::vector<int>> ret;
ret.resize(2);
ret[0].resize(2), ret[1].resize(2);
for (int o1 = 0; o1 < 2; ++o1) {
for (int o2 = 0; o2 < 2; ++o2) {
ret[o1][o2] = getval(t[1][o1][o2], c);
}
}
return ret;
}
} sgt;
// int calc(int c) {
// int f[2] = {(int)1e18, 0};
// for (int i = 1; i <= tot; ++i) {
// auto mat = sgt[i].getmat(c);
// int tmp[2] = {f[0], f[1]};
// f[0] = std::min(tmp[0] + mat[0][0], tmp[1] + mat[1][0]);
// f[1] = std::min(tmp[0] + mat[0][1], tmp[1] + mat[1][1]);
// }
// return f[1];
// }
void dickdreamer() {
std::cin >> cid >> n >> m >> q >> c;
for (int i = 1; i <= n; ++i) std::cin >> b[i];
for (int i = 1; i <= m; ++i) std::cin >> e[i].u >> e[i].v >> e[i].w;
build();
std::multiset<int> st;
for (int i = 1; i <= n; ++i) st.emplace(b[i]);
static int _b[kMaxN];
for (int i = 1; i <= n; ++i) _b[i] = b[i];
for (int i = 1; i <= q; ++i) {
int x, y;
std::cin >> x >> y;
qx[i] = x, qy[i] = y;
// if (i % 5000 == 0) std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
if (x == 0) {
c = y;
} else {
st.erase(st.lower_bound(_b[x])), st.emplace(_b[x] = y);
}
cc[i] = *st.begin() + c, det[i] = c - 2 * cc[i];
// std::cout << calc(*st.begin() + c) << '\n';
}
for (int i = 1; i <= q; ++i) f[i][0] = 1e18, f[i][1] = 0;
B = std::min<int>(n, kMaxB - 5), tot = (n + B - 1) / B;
// B = n, tot = 1;
for (int i = 1; i <= tot; ++i) {
L[i] = (i - 1) * B + 1;
R[i] = std::min(i * B, n);
for (int j = L[i]; j <= R[i]; ++j) bel[j] = i;
}
for (int i = 1; i <= tot; ++i) {
sgt.base = L[i] - 1;
sgt.build(1, 1, R[i] - L[i] + 1);
for (int j = 1; j <= q; ++j) {
int x = qx[j], y = qy[j];
if (bel[pos[x]] == i) {
b[x] = y;
sgt.update(1, 1, R[i] - L[i] + 1, pos[x] - L[i] + 1);
}
auto mat = sgt.getmat(cc[j]);
int tmp[2] = {f[j][0], f[j][1]};
f[j][0] = std::min(tmp[0] + mat[0][0], tmp[1] + mat[1][0]);
f[j][1] = std::min(tmp[0] + mat[0][1], tmp[1] + mat[1][1]);
}
}
for (int i = 1; i <= q; ++i) {
std::cout << f[i][1] + det[i] << '\n';
}
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

浙公网安备 33010602011771号