P7246 手势密码
P7246 手势密码
除 EI 外的其它所有题解并没有严谨地证明贪心结论,本题解作为补充。
一道 DP 与贪心相结合的好题。
考虑树形 DP,设
根据贪心思想,我们希望让尽量多的路径以
可以猜到这样一条结论:在
考虑
- 选择
v_1\neq v_2 ,将a_u, f_{v_1}, f_{v_2} 同时减去1 ,并将总代价b_u 减少2 ,表示通过u 将两条分别以v_1 和v_2 为端点的链相连。 - 选择
v ,将a_u, f_v 同时减去1 ,并将总代价b_u 减少1 ,表示将u 和一条以v 为端点的链相连。
不妨先执行所有操作 1,再执行所有操作 2。设
-
- 操作 1 执行完毕后,操作 2 执行的最大次数为
\min(a_u, S) 。 - 根据摩尔投票法和经典结论,操作 1 执行的最大次数为
Q = \min(a_u, \lfloor \frac S 2 \rfloor, S - \max f_v) 。
结合观察 1 与观察 2,因为操作 1 使得
进一步地,可以计算得出操作 1 执行次数
对上述过程应用之前的结论,因为
那么如何证明这一结论呢?也很容易:
- 若
a_u\leq S ,每撤销一次操作 1 使得\min(a_u, S) 即操作 2 次数增加1 ,但b_u 增加2 ,不优。 - 若
a_u > S ,必然没有执行任何一次操作 1,否则撤销一次操作 1 使得b_u 不变,f_u 增加 1,与f_u 的最大性矛盾。因此f_u 已经取到最大值a_u ,无法增大。
综上,时间复杂度
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
bool Mbe;
constexpr int mod = 1e9;
constexpr int N = 3e6 + 5;
int seed;
int Rand(int x) {
seed = (1ll * seed * 0x66CCF + 19260817) % x + 1;
seed = (1ll * seed * 0x77CCF + 20060428) % x + 1;
seed = (1ll * seed * 0x88CCF + 12345678) % x + 1;
seed = (1ll * seed * 0x33CCCCFF + 10086001) % x + 1;
return seed;
}
int cnt, hd[N], nxt[N], to[N];
void add(int u, int v) {nxt[++cnt] = hd[u], hd[u] = cnt, to[cnt] = v;}
int op, n, dn, a[N], id[N], fa[N];
ll f[N];
void dfs(int x, int ff) {
fa[x] = ff, id[++dn] = x;
for(int i = hd[x]; i; i = nxt[i]) if(to[i] != ff) dfs(to[i], x);
}
bool Med;
int main() {
fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
#ifdef ALEX_WEI
freopen("1.in", "r", stdin);
freopen("1.out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin >> op;
if(op == 1) {
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
dfs(1, 0);
}
else {
cin >> seed >> n;
for(int i = 1; i <= n; i++) a[i] = Rand(mod), id[i] = i;
for(int i = 2; i <= n; i++) add(fa[i] = Rand(i - 1), i);
}
ll ans = 0;
for(int _ = n; _; _--) {
int x = id[_];
ll sumf = 0, mxf = 0;
for(int i = hd[x]; i; i = nxt[i]) {
int y = to[i];
if(fa[x] == y) continue;
sumf += f[y], mxf = max(mxf, f[y]);
}
ans += a[x];
ll P = min((ll) a[x], min(sumf / 2, sumf - mxf));
P = min(P, max(0ll, sumf - a[x]));
f[x] = a[x] - P, ans -= 2 * P + min(f[x], sumf - 2 * P);
}
cout << ans << "\n";
cerr << TIME << " ms\n";
return 0;
}
/*
2022/8/9
Author: Alex_Wei
start coding at 10:45
finish debugging at 19:39
*/