GZEZ "六校NOI集训" Day3
T1(pompom demo)
考虑设状态 \(dp[n,c,x]\) 表示将 \(n\) 个球最终变成 \(c\) 个 \(x\) 的方案数。
转移分情况。
当 \(c = 1\) 时,只需要枚举 \(y\) 使得 \(B_y=x\) ,有 \(dp[n,1,x]=\sum_y dp[n,A_y,y]\) 。
当 \(c\ne 1\) 时,使用类似背包的思想枚举划分, \(dp[n,c,x] = \sum_i dp[i,1,x]\times dp[n-i,c-1,x]\) 。
但我们发现这样是有问题的,在下面那种情况中,\(dp[n-i,c-1,x]\) 可能本身就是由 \(x\) 转移来的,而那些 \(x\) 应该先和 \(dp[i,1,x]\) 合并,而不是作为后面的组成部分。
所以改进状态为 \(dp[n,c,x, b]\) 表示将 \(n\) 个球最终变成 \(c\) 个 \(x\) 的方案数,且第一个球不能有 \(b\) 球参与转移。
改进转移。
当 \(c = 1\) 时, \(dp[n,1,x,b] = \sum_{y\ne b} dp[n,A_y,y,b]\) 。
当 \(c\ne 1\) 时, \(dp[n,c,x,b] = \sum_i dp[i,1,x,b] \times dp[n-i,c-1,x,x]\) 。
时间复杂度 \(O(n^2m^3)\) 。
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
const int _ = 500 + 7;
const int mod = 1e9 + 7;
typedef long long ll;
int n, m, A[_], B[_];
ll dp[_][11][11][11], ans;
int main() {
scanf("%d%d", & n, & m);
lep(i, 1, m) scanf("%d%d", A + i, B + i);
lep(x, 1, m) lep(b, 0, m) dp[1][1][x][b] = 1;
lep(i, 2, n) {
lep(x, 1, m) lep(b, 0, m) {
lep(c, 2, A[x]) lep(j, 1, i - 1)
(dp[i][c][x][b] += dp[j][1][x][b] * dp[i - j][c - 1][x][x] % mod) %= mod;
}
lep(x, 1, m) lep(b, 0, m)
lep(y, 1, m) if (y != b and B[y] == x)
(dp[i][1][x][b] += dp[i][A[y]][y][b]) %= mod;
}
lep(x, 1, m) (ans += dp[n][1][x][0]) %= mod;
printf("%lld\n", ans);
return 0;
}
T2(4 the king)
如果不考虑是个联通块,我们想要选出一个集合 \(S\) 最大化
即最大化
其中, \(x_i = \left[i \in S\right]\) 。
我们发现其类似最小割模型。
一般形式为将 \(n\) 个点划分为两个集合 \(A\) 和 \(B\),如果 \(i\notin A\) 将付出代价 \(a_i\) , \(i\notin B\) 将付出代价 \(b_i\) ,如果 \(i\) 和 \(j\) 不在同一个集合将付出代价 \(w_{ij}\) 。
建立网络流模型,与 \(S\) 连一条 \(a_i\) 的边(如果不属于 \(A\) 需要付出 \(a_i\) 的代价),与 \(T\) 连一条 \(b_i\) (同理) ,并将 \(i\) 与 \(j\) 相互连一条 \(w_{ij}\) 的边,表示 \(i\) 与 \(j\) 属于不同集合需要付出 \(w_{ij}\) 的代价,跑最小割即可。
另一种形式为有向图上选择某个点会获得一定权值(可正可负),某个点选了,则其后继点一定要选,最大化收益(如果权值足够小,就是一个背包问题)。
我们设法将收益转变为代价问题后使用最小割。
类似上面的模型,收益为正的,我们直接获得其收益,将其与 \(S\) 连一条容量为收益的边,为负的,与 \(T\) 连一条容量为收益相反数的边。
分别表示不选它所付出的代价,以及选择它所付出的代价。
至于后继点的贡献,我们对于 \(i\) 及其后继 \(nxt\) ,从 \(nxt\) 向 \(i\) 连一条容量为 \(inf\) 的边,表示 \(nxt\) 选了而 \(i\) 未选的方案是不合法的。
记初始收益为 \(sum\) , 则 \(val_{max} = sum - c(S, T)\) 。
\(c(S, T)\) 为最小割。
我们再看上面需要我们求的问题,其实是两个问题的综合。
记 \(\bar{x}_i = 1-x_i\) 表示不选择 \(i\) ,上述式子可变成:
即如果选择 \(i\) 会获得 \(a_i\sum b_j-c_i\) 的收益,如果 \(i\) 选了而 \(j\) 不选会付出 \(a_ib_j\) 的代价。
通过上述的模型即可转化。
那么,如果加上联通块的性质呢?
如果我们选定一个根,并要求根必须选,那么这个联通块的限制就变成了,如果一个点选了,那么它的父亲必须选。
又是上面的模型,从 \(i\) 向 \(fa_i\) 连一条 \(inf\) 边。
我们需要对每一个根都进行如上操作,套点分治即可。
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
const int _ = 4000 + 7;
typedef long long ll;
const ll inf = 1e18;
int S, T, Data;
int n, fa[_]; ll a[_], b[_], c[_], Ans, dis[_][_], pos[_]; bool ans[_];
int rt, sz[_], tmp[_]; bool vis[_];
int dep[_];
std::vector <int> g[_], op;
bool Bfs() { std::queue <int> d;
lep(i, S, T) dep[i] = 0;
dep[S] = 1, d.push(S);
while (!d.empty()) {
int u = d.front(); d.pop();
if (u == T) return true;
lep(i, S, T) if (dis[u][i] and !dep[i])
dep[i] = dep[u] + 1, d.push(i);
}
return false;
}
ll Dfs(int u, ll flow) {
if (u == T or !flow) return flow;
ll tot = 0, f;
lep(i, pos[u], T) if (dis[u][i] and dep[i] == dep[u] + 1) { pos[u] = i;
f = Dfs(i, std::min(dis[u][i], flow));
tot += f, flow -= f;
dis[u][i] -= f, dis[i][u] += f;
if (!flow) break;
}
if (!tot) dep[u] = 0;
return tot;
}
ll Dinic() { ll res = 0;
while (Bfs()) {
lep(i, S, T) pos[i] = S;
res += Dfs(S, inf);
}
return res;
}
void GetRt(int u, int f, int total) {
sz[u] = 1, tmp[u] = 0;
for (int v : g[u]) if (!vis[v] and v != f) {
GetRt(v, u, total), sz[u] += sz[v];
tmp[u] = std::max(tmp[u], sz[v]);
}
tmp[u] = std::max(tmp[u], total - sz[u]);
if (!rt or tmp[u] < tmp[rt]) rt = u;
}
void Dfs(int u, int f) {
op.push_back(u), fa[u] = f;
for (int v : g[u]) if (!vis[v] and v != f) Dfs(v, u);
}
void Calc(int u) {
op.clear(); Dfs(u, 0);
S = 0; T = op.size() + 1; ll B = 0, delta = 0;
lep(i, S, T) lep(j, S, T) dis[i][j] = 0;
lep(i, 1, op.size()) {
B += b[op[i - 1]];
lep(j, 1, op.size()) {
if (i != j) dis[i][j] = a[op[i - 1]] * b[op[j - 1]];
if (fa[op[i - 1]] == op[j - 1]) dis[i][j] = inf;
}
}
lep(i, 1, op.size()) { ll k = c[op[i - 1]] - a[op[i - 1]] * B;
if (k > 0) dis[i][T] = k;
else delta += (dis[S][i] = -k);
}
ll res = delta - Dinic();
if (res > Ans) {
lep(i, 1, n) ans[i] = false;
lep(i, 1, op.size())
if (dep[i]) ans[op[i - 1]] = true;
Ans = res;
}
}
void Solve(int u, int total) {
rt = 0, GetRt(u, 0, total);
vis[rt] = true, Calc(rt);
for (int v : g[rt]) if (!vis[v])
Solve(v, total - tmp[v]);
}
int main() {
scanf("%d", & Data);
while (Data--) {
scanf("%d", & n); int u, v;
lep(i, 1, n) scanf("%lld%lld%lld", a + i, b + i, c + i);
lep(i, 2, n) scanf("%d%d", & u, & v),
g[u].push_back(v), g[v].push_back(u);
Solve(1, n);
printf("%lld\n", Ans); Ans = 0;
lep(i, 1, n) putchar('0' + ans[i]), ans[i] = vis[i] = false, g[i].clear(); puts("");
}
return 0;
}
T3(sheep simulator 3)
称可选的点为关键点。
如果所有点都是关键点,那我们需要找到带权重心。
记所有点的权值和为 \(Alls\), \(i\) 点的子树权值和为 \(sum_i\) ,带权重心为 \(rt\) 。
则有 \(sum_{rt}\ge \left\lfloor\frac{Alls}{2} \right\rfloor + 1\) ,且其任意一个子树 \(v\) 都满足 \(sum_v \le \left\lfloor\frac{Alls}{2} \right\rfloor\) 。
由于这个性质,所以我们可以通过先寻找到 \(dfn\) 序列的带权中位数,而这个点一定在 \(rt\) 的子树内,在二分找到最深的 \(sum\ge \left\lfloor\frac{Alls}{2} \right\rfloor + 1\) 的点,即为树的带权重心。
那么,如何获取其答案呢?
父亲方向可以直接查询,而维护所有儿子的答案太劣了,可以用 \(multiset\) 维护所有轻儿子,重儿子单独查询,使用树剖。
但是如果带权重心不是关键点呢?
对于 \(rt\) 到根路径上的所有点,我们容易发现只有最深的那一个才会作为答案,二分找到。
对于不在路径上的点,我们发现在跳重链时已经把它们分割成了 \(\log n\) 个区间,查询子树权值和的最大值即可,因为它们的贡献一定为 \(Alls - sum\) 。
至于如何维护关键点子树权值和的最大值,可以将非关键点的答案减去 \(inf\) ,维护关键点变化的时候注意 \(\pm inf\) 即可。
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
const int _ = 1e6 + 7;
typedef long long ll;
const ll inf = 4e17;
struct node {
ll mx, tag, val; int cnt;
node(ll _mx = -inf, ll _val = 0, ll _cnt = 0, ll _tag = 0) { mx = _mx, tag = _tag, val = _val, cnt = _cnt; }
friend node operator + (const node& x, const node& y) { return node(std::max(x.mx, y.mx), x.val + y.val, x.cnt + y.cnt); }
friend void operator += (node& x, const node& y) { x = node(std::max(x.mx, y.mx), x.val + y.val, x.cnt + y.cnt); }
friend node operator * (const node& x, const ll& y) { return node(x.mx + y, x.val, x.cnt, x.tag + y); }
friend void operator *= (node& x, const ll& y) { x = node(x.mx + y, x.val, x.cnt, x.tag + y); }
}tr[_];
int n, m, P[_], c[_];
int dfn[_], ud[_], fa[_], dep[_], idx, son[_], sz[_], top[_];
std::vector <int> e[_];
std::multiset <ll> S[_]; ll Alls;
void Dfs1(int u, int f) {
dep[u] = dep[fa[u] = f] + 1, sz[u] = 1;
for (int v : e[u]) if (v != f) {
Dfs1(v, u); sz[u] += sz[v];
if (sz[v] > sz[son[u]]) son[u] = v;
}
}
void Dfs2(int u, int tp) {
top[u] = tp, dfn[u] = ++idx; ud[idx] = u;
if (!son[u]) return; Dfs2(son[u], tp);
for (int v : e[u]) if (v != fa[u] and v != son[u]) Dfs2(v, v);
}
#define ls p << 1
#define rs p << 1 | 1
void PushUp(int p) { tr[p] = tr[ls] + tr[rs]; }
void Build(int l, int r, int p) {
if (l == r) return tr[p] = node(-inf, c[ud[l]], P[ud[l]]), void(); int mid = (l + r) >> 1;
Build(l, mid, ls), Build(mid + 1, r, rs); PushUp(p);
}
void PushDown(int p) { if (tr[p].tag) tr[ls] *= tr[p].tag, tr[rs] *= tr[p].tag, tr[p].tag = 0; }
void Modify(int l, int r, int s, int t, ll y, int p) {
if (r < s or t < l) return;
if (l <= s and t <= r) return tr[p] *= y; PushDown(p); int mid = (s + t) >> 1;
Modify(l, r, s, mid, y, ls), Modify(l, r, mid + 1, t, y, rs); PushUp(p);
}
void Modify(int d, int s, int t, node y, int p) {
if (s == t) return tr[p] += y; PushDown(p); int mid = (s + t) >> 1;
d <= mid ? Modify(d, s, mid, y, ls) : Modify(d, mid + 1, t, y, rs); PushUp(p);
}
ll Query(int l, int r, int s, int t, int p) {
if (r < s or t < l) return 0;
if (l <= s and t <= r) return tr[p].val; PushDown(p); int mid = (s + t) >> 1;
return Query(l, r, s, mid, ls) + Query(l, r, mid + 1, t, rs);
}
int Gets(int l, int r, int s, int t, int p) {
if (r < s or t < l) return 0;
if (l <= s and t <= r) return tr[p].cnt; PushDown(p); int mid = (s + t) >> 1;
return Gets(l, r, s, mid, ls) + Gets(l, r, mid + 1, t, rs);
}
ll Max(int l, int r, int s, int t, int p) {
if (r < s or t < l) return -inf;
if (l <= s and t <= r) return tr[p].mx; PushDown(p); int mid = (s + t) >> 1;
return std::max(Max(l, r, s, mid, ls), Max(l, r, mid + 1, t, rs));
}
//-----------------------------------------------------------------------------
ll Query(int x) { return Query(dfn[x], dfn[x] + sz[x] - 1, 1, n, 1); }
void Init(int u) {
Modify(dfn[u], dfn[u], 1, n, Query(u) + inf * P[u], 1);
if (!son[u]) return; Init(son[u]);
for (int v : e[u]) if (v != fa[u] and v != son[u])
S[u].insert(Query(v)), Init(v);
}
void Modify(int x, ll y, int p) {
if (p) {
if (P[x]) Modify(dfn[x], dfn[x], 1, n, -inf, 1);
else Modify(dfn[x], dfn[x], 1, n, inf, 1);
Modify(dfn[x], 1, n, node(-inf, 0, (P[x] ^ 1) - P[x]), 1); P[x] ^= 1;
return;
}
int nw = x;
while (nw) {
Modify(dfn[top[nw]], dfn[nw], 1, n, y - c[x], 1);
if (fa[top[nw]]) S[fa[top[nw]]].erase(S[fa[top[nw]]].find(Query(top[nw])));
nw = fa[top[nw]];
}
Modify(dfn[x], 1, n, node(-inf, y - c[x], 0), 1); c[x] = y;
nw = x;
while (nw) {
if (fa[top[nw]]) S[fa[top[nw]]].insert(Query(top[nw]));
nw = fa[top[nw]];
}
}
void Query() {
int p = 1, s = 1, t = n; ll dl = 0, ans = inf;
while (s < t) { int mid = (s + t) >> 1;
if (dl + tr[ls].val >= Alls / 2) p = ls, t = mid;
else dl += tr[ls].val, p = rs, s = mid + 1;
}
p = ud[s]; t = -1;
while (true) {
if (Query(top[p]) >= Alls / 2 + 1) {
int l = dfn[top[p]], r = dfn[p] + 1;
while (l < r) { int mid = (l + r) >> 1;
if (Query(ud[mid]) < Alls / 2 + 1) r = mid;
else l = mid + 1;
}
if (l != dfn[p] + 1) t = ud[l];
break;
}
else t = top[p], p = fa[top[p]];
}
if (~t) p = fa[t];
bool flag = true; int R = n, nw = p;
while (nw) {
if (flag and Gets(dfn[top[nw]], dfn[nw], 1, n, 1)) {
int l = dfn[top[nw]], r = dfn[nw];
while (l < r) { int mid = (l + r + 1) >> 1;
if (Gets(mid, dfn[nw], 1, n, 1)) l = mid;
else r = mid - 1;
}
l = ud[l];
if (l != p) ans = std::min(ans, Query(son[l]));
else {
ll val = std::max(Alls - Query(l), Query(son[l]));
if (!S[l].empty()) val = std::max(val, *S[l].rbegin());
ans = std::min(ans, val);
}
flag = false;
}
ans = std::min(ans, Alls - Max(dfn[nw] + 1, R, 1, n, 1)), R = dfn[top[nw]] - 1;
nw = fa[top[nw]];
}
printf("%lld\n", ans);
}
#undef ls
#undef rs
int main() {
scanf("%d%d", & n, & m);
lep(i, 1, n) scanf("%d%d", P + i, c + i), Alls += c[i]; int u, v;
lep(i, 2, n) scanf("%d%d", & u, & v),
e[u].push_back(v), e[v].push_back(u);
Dfs1(1, 0), Dfs2(1, 1); Build(1, n, 1);
Init(1);
int op, x, y;
while (m--) {
scanf("%d", & op);
if (op == 1) scanf("%d", & x), Modify(x, 0, 1);
else if (op == 2) scanf("%d", & x), Modify(x, 0, 1);
else if (op == 3) scanf("%d%d", & x, & y), Alls += y - c[x], Modify(x, y, 0);
else Query();
}
return 0;
}
时间仓促,如有错误欢迎指出,欢迎在评论区讨论,如对您有帮助还请点个推荐、关注支持一下

Day3
浙公网安备 33010602011771号