【Day1】
【铺设道路】
终极傻题,并且是NOIP2013积木大赛原题
可以用一种分治的思想:
d
f
s
(
l
,
r
,
h
)
dfs(l, r, h)
dfs(l,r,h)表示现在站在
h
h
h的高度上,要把
[
l
,
r
]
[l,r]
[l,r]全部铺平的最少次数。可以先查询
[
l
,
r
]
[l,r]
[l,r]的最小值以及其位置,然后对左右递归调用即可。涉及到区间最小值查询,用线段树或ST表或笛卡尔树都行,只要不是暴力找最小值就行。
#include <cctype>
#include <cstdio>
#include <climits>
#include <algorithm>
#define rep(I, A, B) for (int I = (A); I <= (B); ++I)
#define dwn(I, A, B) for (int I = (A); I >= (B); --I)
#define erp(I, X) for (int I = head[X]; I; I = next[I])
template <typename T> inline void read(T& t) {
int f = 0, c = getchar(); t = 0;
while (!isdigit(c)) f |= c == '-', c = getchar();
while (isdigit(c)) t = t * 10 + c - 48, c = getchar();
if (f) t = -t;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + 48);
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
template <typename T> inline bool chkMin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkMax(T& x, const T& y) { return x < y ? (x = y, true) : false; }
const int maxn = 1e5 + 207;
int a[maxn], lg[maxn], f[20][maxn];
int n;
inline void init() {
rep(i, 2, n) lg[i] = lg[i >> 1] + 1;
rep(i, 1, n) f[0][i] = i;
for (int i = 1; 1 << i <= n; ++i)
for (int j = 1; j + (1 << i) - 1 <= n; ++j) {
int k = j + (1 << (i - 1));
if (a[f[i - 1][j]] < a[f[i - 1][k]])
f[i][j] = f[i - 1][j];
else
f[i][j] = f[i - 1][k];
}
}
inline int query(int l, int r) {
int k = lg[r - l + 1];
if (a[f[k][l]] < a[f[k][r - (1 << k) + 1]]) return f[k][l];
else return f[k][r - (1 << k) + 1];
}
int dfs(int l, int r, int h) {
if (l > r) return 0;
int mp = query(l, r);
int mv = a[mp] - h;
return mv + dfs(l, mp - 1, h + mv) + dfs(mp + 1, r, h + mv);
}
int main() {
read(n);
rep(i, 1, n) read(a[i]);
init();
writeln(dfs(1, n, 0));
return 0;
}
【货币系统】
首先一个显然的结论是,要想让新的货币系统与原来的货币系统能表示出来的集合完全一样,新的货币系统必须是原货币系统的子集。也就是说我们只要在
a
a
a当中删掉一些元素即可。删掉什么元素?当然是能被其它元素表示出来的元素。所以写一个类似于背包的dp即可
f
[
i
]
f[i]
f[i]表示
i
i
i能不能被表示出来,随便转移一下就行了…
#include <cstdio>
#include <algorithm>
const int maxn = 107, maxa = 25207;
int a[maxn], n, T;
bool f[maxa];
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
std::sort(a + 1, a + n + 1);
int ans = n;
for (int i = 1; i <= n; ++i) {
if (f[a[i]]) --ans;
f[a[i]] = 1;
for (int j = a[1]; j <= a[n]; ++j)
if (j >= a[i]) f[j] |= f[j - a[i]];
}
printf("%d\n", ans);
for (int i = 1; i <= a[n]; ++i) f[i] = 0;
}
return 0;
}
【赛道修建】
day1唯一一道稍微上得了台面的题。
然而我考试的时候一直觉得这题跟【林克卡特树】怎么这么像啊…于是就试着用林克卡特树的套路写树形dp,烧了大把时间,最后只写了55分的部分分…
首先看到题目描述“最小的长度最大”,这已经是非常强烈的暗示了,没错就是二分答案。我们二分一个最小长度
m
i
d
mid
mid,然后尽可能多地选出长度大于等于
m
i
d
mid
mid的链,看看能不能选出至少
m
m
m条。
可以这样考虑:处理完以
x
x
x为根的子树之后,留一个长度不足
m
i
d
mid
mid的线头给
x
x
x的父亲,为了让我们选出的链的总数尽可能多,肯定要选择“最有潜力”的那个线头,即这个线头的长度应该尽可能大。
考虑处理以
x
x
x为根的子树,我们可以把每个孩子带来的线头(不要忘记加上这个孩子到
x
x
x的边权)存进一个数组,先对这个数组从小到大排序,首先那些本身已经大于等于
m
i
d
mid
mid的就可以不用管了;对于剩下的线头,尽可能让它们配对,即选择两根线头连起来让它们大于等于
m
i
d
mid
mid;对于那些既不够长也不能配对的线头,挑一个最大的
r
e
t
u
r
n
return
return即可。不要忘了在这个过程中每次合成一条新链都要++cnt
。
这个数组可以用一个
m
u
l
t
i
s
e
t
multiset
multiset实现。
二分的上界可以选择树的直径。并且我发现似乎有很多人树的直径都写得很长,其实求树的直径最短的写法就是树形dp,这里啰嗦一下。设
f
[
x
]
f[x]
f[x]表示以
x
x
x为根的子树的直径,
g
[
x
]
g[x]
g[x]表示从
x
x
x往下走的最长路径,那么只要一个简短的
d
f
s
dfs
dfs(见代码get
函数)就好了
#include <cctype>
#include <cstdio>
#include <climits>
#include <algorithm>
#include <set>
template <typename T> inline void read(T& x) {
int f = 0, c = getchar(); x = 0;
while (!isdigit(c)) f |= c == '-', c = getchar();
while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
if (f) x = -x;
}
template <typename T, typename... Args>
inline void read(T& x, Args&... args) {
read(x); read(args...);
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + 48);
}
template <typename T> void writeln(T x) { write(x); puts(""); }
template <typename T> inline bool chkmin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkmax(T& x, const T& y) { return x < y ? (x = y, true) : false; }
const int maxn = 1e5 + 207;
int v[maxn << 1], w[maxn << 1], head[maxn], next[maxn << 1], tot;
int n, m;
inline void ae(int x, int y, int z) {
v[++tot] = y; w[tot] = z; next[tot] = head[x]; head[x] = tot;
v[++tot] = x; w[tot] = z; next[tot] = head[y]; head[y] = tot;
}
int dfs(int x, int fa, int lim, int &cnt) {
std::multiset<int> st;
for (int i = head[x]; i; i = next[i])
if (v[i] != fa) {
int val = w[i] + dfs(v[i], x, lim, cnt);
if (val >= lim) ++cnt;
else st.insert(val);
}
int ret = 0;
while (!st.empty()) {
int val = *st.begin();
st.erase(st.begin());
std::multiset<int>::iterator it = st.lower_bound(lim - val);
if (it != st.end()) {
++cnt;
st.erase(it);
} else chkmax(ret, val);
}
return ret;
}
int f[maxn], g[maxn];
void get(int x, int fa) {
for (int i = head[x]; i; i = next[i])
if (v[i] != fa) {
get(v[i], x);
chkmax(f[x], std::max(f[v[i]], g[x] + w[i] + g[v[i]]));
chkmax(g[x], g[v[i]] + w[i]);
}
}
int main() {
read(n, m);
for (int i = 1; i != n; ++i) {
int x, y, z;
read(x, y, z);
ae(x, y, z);
}
get(1, 0);
int left = 0, right = f[1], ans;
while (left <= right) {
int mid = (left + right) >> 1;
int cnt = 0;
dfs(1, 0, mid, cnt);
if (cnt >= m) ans = mid, left = mid + 1;
else right = mid - 1;
}
writeln(ans);
return 0;
}
【Day2】
【旅行】
基环树,看起来很厉害,但其实是傻题。首先对于树的部分分只要一遍
d
f
s
dfs
dfs,在
d
f
s
dfs
dfs的时候注意一下儿子的顺序就好了。那么基环树的话,注意到
n
≤
5000
n\leq 5000
n≤5000,只要找出环,枚举断环上的哪条边,然后当树做一遍,答案取最小就好了。复杂度
O
(
n
2
)
O(n^2)
O(n2)。
另外千万不要以为
n
≤
5000
n\leq 5000
n≤5000你就能为所欲为,如果你为了省事用邻接矩阵做
d
f
s
dfs
dfs就会T。因为邻接矩阵
d
f
s
dfs
dfs复杂度是
O
(
n
2
)
O(n^2)
O(n2),总复杂度
O
(
n
3
)
O(n^3)
O(n3),i7也救不了你。
考场代码,并不是很好看。
另外此题有
O
(
n
log
n
)
O(n\log n)
O(nlogn)的做法,但超出了本文的范围。我也不知道本文的范围是什么。其实是我不会。
#include <cctype>
#include <climits>
#include <cstdio>
#include <algorithm>
#include <vector>
#define rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define dwn(i, a, b) for (int i = (a); i >= (b); --i)
#define erp(i, x) for (int i = head[x]; i; i = next[i])
template <typename T> inline void read(T& t) {
int f = 0, c = getchar(); t = 0;
while (!isdigit(c)) f |= c == '-', c = getchar();
while (isdigit(c)) t = t * 10 + c - 48, c = getchar();
if (f) t = -t;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + 48);
}
template <typename T> inline void writeln(T x) {
write(x); puts("");
}
template <typename T> inline bool chkMin(T& x, const T& y) { return y < x ? (x = y, 1) : 0; }
template <typename T> inline bool chkMax(T& x, const T& y) { return x < y ? (x = y, 1) : 0; }
const int maxn = 5e3 + 207;
std::vector<int> G[maxn];
int n, m;
inline void ae(int x, int y) {
G[x].push_back(y);
G[y].push_back(x);
}
namespace atree {
int dfn[maxn], index;
void dfs(int x, int fa) {
dfn[++index] = x;
for (unsigned i = 0; i < G[x].size(); ++i) {
int v = G[x][i];
if (v != fa) dfs(v, x);
}
}
inline void work() {
dfs(1, 0);
rep(i, 1, n) printf("%d ", dfn[i]);
puts("");
}
}
namespace tree_with_circle {
bool vis[maxn];
int u[maxn << 1], v[maxn << 1], head[maxn], next[maxn << 1], tot;
int circle[maxn], ccnt;
int from, to;
int ans[maxn], index, curr[maxn];
inline void ae(int x, int y) {
v[++tot] = y; u[tot] = x; next[tot] = head[x]; head[x] = tot;
}
inline bool lesss() {
if (!ans[1]) return 1;
rep(i, 1, n) {
if (curr[i] < ans[i]) return 1;
if (curr[i] > ans[i]) return 0;
}
return 0;
}
int dfs(int x, int fa) {
vis[x] = 1;
erp(i, x) if (v[i] != fa) {
if (vis[v[i]]) {
circle[++ccnt] = i;
return v[i];
}
int ret = dfs(v[i], x);
if (ret) {
circle[++ccnt] = i;
if (ret == x) return 0;
else return ret;
}
}
return 0;
}
inline void rebuild() {
rep(i, 1, n)
dwn(j, G[i].size() - 1, 0) ae(i, G[i][j]);
}
void dfs2(int x, int fa) {
curr[++index] = x;
erp(i, x) if (v[i] != fa) {
if (x == from && v[i] == to) continue;
if (x == to && v[i] == from) continue;
dfs2(v[i], x);
}
}
inline void work() {
rebuild();
dfs(1, 0);
rep(i, 1, ccnt) {
from = u[circle[i]];
to = v[circle[i]];
index = 0;
dfs2(1, 0);
if (lesss()) std::copy(curr + 1, curr + n + 1, ans + 1);
}
rep(i, 1, n) printf("%d ", ans[i]);
puts("");
}
}
int main() {
read(n); read(m);
rep(i, 1, m) {
int x, y;
read(x); read(y); ae(x, y);
}
rep(i, 1, n) std::sort(G[i].begin(), G[i].end());
if (m == n - 1) {
atree::work();
return 0;
} else {
tree_with_circle::work();
return 0;
}
return 0;
}
【填数游戏】
考场上想了一个状压DP的做法,但是只能过
n
=
2
n=2
n=2的情况。当时还想过一些对角线DP,但是感觉并不能写得出来。幸好没去写
后来出考场之后我才听说这题又双叒叕是个打表找规律题。
此处省去一万句吐槽。
那具体怎么找规律我也不想说了,反正就写暴力+各种观察+猜结论吧。
这题真的好没劲…考个正常点的dp多好为什么要考这种东西
#include <cctype>
#include <cstdio>
#include <climits>
#include <algorithm>
#define rep(I, A, B) for (int I = (A); I <= (B); ++I)
#define dwn(I, A, B) for (int I = (A); I >= (B); --I)
#define erp(I, X) for (int I = head[X]; I; I = next[I])
template <typename T> inline void read(T& t) {
int f = 0, c = getchar(); t = 0;
while (!isdigit(c)) f |= c == '-', c = getchar();
while (isdigit(c)) t = t * 10 + c - 48, c = getchar();
if (f) t = -t;
}
template <typename T, typename... Args>
inline void read(T& t, Args&... args) {
read(t); read(args...);
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + 48);
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
template <typename T> inline bool chkMin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkMax(T& x, const T& y) { return x < y ? (x = y, true) : false; }
typedef long long LL;
const LL mod = 1e9 + 7;
int n, m;
inline LL qpow(LL x, LL k) {
LL s = 1;
for (; k; x = x * x % mod, k >>= 1)
if (k & 1) s = s * x % mod;
return s;
}
int main() {
read(n, m);
if (n > m) std::swap(n, m);
if (n == 1) writeln(qpow(2, m));
else if (n == 2) writeln(4ll * qpow(3, m - 1) % mod);
else if (n == 3) writeln(112ll * qpow(3, m - 3) % mod);
else if (n == 4) {
if (m == 4) writeln(912);
else writeln(2688ll * qpow(3, m - 5) % mod);
} else if (n == 5) {
if (m == 5) writeln(7136);
else writeln(21312ll * qpow(3, m - 6) % mod);
} else if (n == 6) {
if (m == 6) writeln(56768);
else writeln(170112ll * qpow(3, m - 7) % mod);
} else if (n == 7) {
if (m == 7) writeln(453504);
else writeln(1360128ll * qpow(3, m - 8) % mod);
} else {
if (m == 8) writeln(3626752);
else writeln(10879488ll * qpow(3, m - 9) % mod);
}
return 0;
}
【保卫王国】
个人认为本场考试做起来最舒服的一道题。
在考试前一天晚上,也就是day1下午,我在酒店里五脊六兽非常无聊,正想着做个什么题。要不做个动态dp模板题吧。算了,这么毒瘤的东西NOIP不会考的。
然后当我看到这道题的时候无比后悔。
再加上考场上全程调T2的DP调到崩溃,这题就只写了个44暴力。
那么说说正解。
首先你要会这题的暴力dp:它几乎就是没有上司的舞会。
f
[
x
]
[
0
/
1
]
f[x][0/1]
f[x][0/1]表示以
x
x
x为根的子树,其中
x
x
x不选/选,最小花费是多少。
f
[
x
]
[
0
]
=
∑
f
[
v
]
[
1
]
f[x][0]=\sum f[v][1]
f[x][0]=∑f[v][1]
f
[
x
]
[
1
]
=
∑
min
(
f
[
v
]
[
0
]
,
f
[
v
]
[
1
]
)
+
v
a
l
[
x
]
f[x][1]=\sum\min(f[v][0],f[v][1])+val[x]
f[x][1]=∑min(f[v][0],f[v][1])+val[x]
- 法1:动态dp
这几乎就是动态dp求最大独立集的模板题,连方程都差不多。
事实上这俩题的确有很大的联系。这题求的是最小覆盖集,而最小覆盖集=总点权-最大独立集。
如果要强制选一个点,就把它的点权改成0;强制不选一个点,就把它的点权改成 i n f inf inf,然后如果dp的结果是 i n f inf inf就说明不合法,输出 − 1 -1 −1,否则就输出结果。不要忘记把强制选的那个点的点权加上。最后再把点权改回来即可。
这种做法的好处:一是比较无脑可以直接套模板,二是可以轻易地改成每次询问 k k k个点( ∑ k = O ( n ) \sum k=O(n) ∑k=O(n))的情况,并且如果真的要修改点权的话也可以解决。总的来说就是通用性比较强。
但是劣势在于动态dp的实现,由于我太菜了不会全局平衡二叉树…我是用lct做的,无论lct还是树剖,常数都比较大。但是反正有i7,无所畏惧。
#include <cctype>
#include <cstdio>
#include <climits>
#include <algorithm>
#define rep(I, A, B) for (int I = (A); I <= (B); ++I)
#define dwn(I, A, B) for (int I = (A); I >= (B); --I)
#define erp(I, X) for (int I = head[X]; I; I = next[I])
template <typename T> inline void read(T& t) {
int f = 0, c = getchar(); t = 0;
while (!isdigit(c)) f |= c == '-', c = getchar();
while (isdigit(c)) t = t * 10 + c - 48, c = getchar();
if (f) t = -t;
}
template <typename T, typename... Args>
inline void read(T& t, Args&... args) {
read(t); read(args...);
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + 48);
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
template <typename T> inline bool chkMin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkMax(T& x, const T& y) { return x < y ? (x = y, true) : false; }
typedef long long LL;
const int maxn = 1e5 + 207;
const LL inf = 1e10;
struct Matrix {
LL data[2][2];
Matrix() { data[0][0] = data[0][1] = data[1][0] = data[1][1] = inf; }
};
inline Matrix mul(const Matrix &A, const Matrix &B) {
Matrix C;
rep(k, 0, 1) rep(i, 0, 1) rep(j, 0, 1)
chkMin(C.data[i][j], A.data[i][k] + B.data[k][j]);
return C;
}
// g[x][0] = sum{ f[v][1] }
// g[x][1] = value[x] + sum{ min(f[v][0], f[v][1]) }
// f[x][0] = min(inf + f[u][0], g[x][0] + f[u][1])
// f[x][1] = min(g[x][1] + f[u][0], g[x][1] + f[u][1])
// inf g[x][0] * f[u][0] = f[x][0]
// g[x][1] g[x][1] f[u][1] f[x][1]
LL dp[maxn][2];
int v[maxn << 1], next[maxn << 1], head[maxn], tot;
LL value[maxn];
Matrix val[maxn], prd[maxn];
int fa[maxn], ch[maxn][2];
int n, m;
inline void ae(int x, int y) {
v[++tot] = y; next[tot] = head[x]; head[x] = tot;
v[++tot] = x; next[tot] = head[y]; head[y] = tot;
}
void dfs(int x) {
dp[x][1] = value[x];
erp(i, x) if (v[i] != fa[x]) {
fa[v[i]] = x;
dfs(v[i]);
dp[x][0] += dp[v[i]][1];
dp[x][1] += std::min(dp[v[i]][0], dp[v[i]][1]);
}
val[x].data[0][1] = dp[x][0];
val[x].data[1][0] = val[x].data[1][1] = dp[x][1];
prd[x] = val[x];
}
inline void update(int x) {
prd[x] = val[x];
if (ch[x][0]) prd[x] = mul(prd[ch[x][0]], prd[x]);
if (ch[x][1]) prd[x] = mul(prd[x], prd[ch[x][1]]);
}
inline int iden(int x) {
return ch[fa[x]][0] == x ? 0 : (ch[fa[x]][1] == x ? 1 : -1);
}
inline void rotate(int x) {
int d = iden(x), y = fa[x];
if (~iden(y)) ch[fa[y]][iden(y)] = x;
fa[x] = fa[y];
if ((ch[y][d] = ch[x][d ^ 1])) fa[ch[x][d ^ 1]] = y;
fa[ch[x][d ^ 1] = y] = x;
update(y); update(x);
}
inline void splay(int x) {
while (~iden(x)) {
int y = fa[x];
if (~iden(y)) rotate(iden(y) ^ iden(x) ? x : y);
rotate(x);
}
}
inline void access(int x) {
for (int y = 0; x; x = fa[y = x]) {
splay(x);
if (ch[x][1]) {
val[x].data[0][1] += prd[ch[x][1]].data[1][1];
val[x].data[1][1] += std::min(prd[ch[x][1]].data[0][1], prd[ch[x][1]].data[1][1]);
}
if (y) {
val[x].data[0][1] -= prd[y].data[1][1];
val[x].data[1][1] -= std::min(prd[y].data[0][1], prd[y].data[1][1]);
}
val[x].data[1][0] = val[x].data[1][1];
ch[x][1] = y;
update(x);
}
}
inline void modify(int x, LL y) {
access(x); splay(x);
val[x].data[1][0] -= value[x] - y;
val[x].data[1][1] -= value[x] - y;
update(x);
value[x] = y;
}
int main() {
read(n, m);
{ char XuYuShuILoveYou[10]; scanf("%s", XuYuShuILoveYou); }
rep(i, 1, n) read(value[i]);
rep(i, 1, n - 1) {
int x, y;
read(x, y);
ae(x, y);
}
dfs(1);
rep(i, 1, m) {
int a, x, b, y;
read(a, x, b, y);
LL va = value[a], vb = value[b];
if (x) modify(a, 0);
else modify(a, va + inf);
if (y) modify(b, 0);
else modify(b, vb + inf);
splay(1);
LL ans = std::min(prd[1].data[0][1], prd[1].data[1][1]);
if (ans >= inf) puts("-1");
else {
if (x) ans += va;
if (y) ans += vb;
writeln(ans);
}
modify(a, va);
modify(b, vb);
}
return 0;
}
- 法2:倍增
在原先的 f [ x ] [ 0 / 1 ] f[x][0/1] f[x][0/1]的基础上,可以设 g [ x ] [ 0 / 1 ] g[x][0/1] g[x][0/1]表示:对于整棵树减去以 x x x为根的子树剩下的部分,当 x x x不选/选的时候,这一部分的dp值。注意,这一部分并不包括 x x x结点,但是这个值的确与 x x x选/不选有关。那么转移:
g [ v ] [ 0 ] = g [ x ] [ 1 ] + f [ x ] [ 1 ] − min ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) g[v][0]=g[x][1]+f[x][1]-\min(f[v][0],f[v][1]) g[v][0]=g[x][1]+f[x][1]−min(f[v][0],f[v][1])
g [ v ] [ 1 ] = min ( g [ v ] [ 0 ] , g [ x ] [ 0 ] + f [ x ] [ 0 ] − f [ v ] [ 1 ] ) g[v][1]=\min(g[v][0],g[x][0]+f[x][0]-f[v][1]) g[v][1]=min(g[v][0],g[x][0]+f[x][0]−f[v][1])
( v v v是 x x x的孩子)
其实做到这里应该有一个感觉:由于 f [ x ] [ 0 / 1 ] f[x][0/1] f[x][0/1]的转移是一个累加的式子,所以我们可以很方便地减去某棵子树的贡献来得到其他部分的贡献。
考虑倍增:先有 f a [ x ] [ i ] fa[x][i] fa[x][i]表示 x x x的 2 i 2^i 2i级祖先,显然转移 f a [ x ] [ i ] = f a [ f a [ x ] [ i − 1 ] ] [ i − 1 ] fa[x][i]=fa[fa[x][i - 1]][i - 1] fa[x][i]=fa[fa[x][i−1]][i−1]。然后设 h [ x ] [ i ] [ 0 / 1 ] [ 0 / 1 ] h[x][i][0/1][0/1] h[x][i][0/1][0/1]表示以 f a [ x ] [ i ] fa[x][i] fa[x][i]为根的子树去掉以 x x x为根的子树,剩下的部分的dp值,并且必须满足 x x x不选/选, f a [ x ] [ i ] fa[x][i] fa[x][i]不选/选。注意,这个状态所包含的结点同样不包括 x x x本身,但其值与 x x x选/不选有关。
初始条件是
h [ v ] [ 0 ] [ 0 ] [ 0 ] = i n f h[v][0][0][0]=inf h[v][0][0][0]=inf
h [ v ] [ 0 ] [ 0 ] [ 1 ] = f [ x ] [ 1 ] − min ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) h[v][0][0][1]=f[x][1]-\min(f[v][0],f[v][1]) h[v][0][0][1]=f[x][1]−min(f[v][0],f[v][1])
h [ v ] [ 0 ] [ 1 ] [ 0 ] = f [ x ] [ 0 ] − f [ v ] [ 1 ] h[v][0][1][0]=f[x][0]-f[v][1] h[v][0][1][0]=f[x][0]−f[v][1]
h [ v ] [ 0 ] [ 1 ] [ 1 ] = f [ x ] [ 1 ] − min ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) h[v][0][1][1]=f[x][1]-\min(f[v][0],f[v][1]) h[v][0][1][1]=f[x][1]−min(f[v][0],f[v][1])
然后转移就枚举 2 i − 1 2^{i-1} 2i−1级祖先的状态
h [ x ] [ i ] [ j ] [ k ] = min { h [ x ] [ i − 1 ] [ j ] [ t ] + h [ f a [ x ] [ i − 1 ] ] [ i − 1 ] [ t ] [ k ] } , t = 0 , 1 h[x][i][j][k]=\min\{h[x][i-1][j][t]+h[fa[x][i-1]][i-1][t][k]\},t=0,1 h[x][i][j][k]=min{h[x][i−1][j][t]+h[fa[x][i−1]][i−1][t][k]},t=0,1
其实并不是很难的东西。
对于一个询问 ( x , q x , y , q y ) ( q x , q y ∈ { 0 , 1 } ) (x,qx,y,qy)(qx,qy\in\{0,1\}) (x,qx,y,qy)(qx,qy∈{0,1}),令 x x x是深度较大的点,分两种情况处理: y y y是 x x x的祖先,或者 y y y不是 x x x的祖先。 y y y是 x x x的祖先时,从 x x x一路倍增跳到 y y y即可; y y y不是 x x x的祖先时,先把 x x x跳到与 y y y同深度,然后再一起跳到 l c a lca lca。跳的过程中(以跳 x x x为例)要保存 t x [ 0 / 1 ] tx[0/1] tx[0/1]表示目前 x x x所在的这个结点不选/选的时候的答案。具体的细节很多,而且难度不大,建议自行完成代码。
#include <cctype>
#include <cstdio>
#include <climits>
#include <algorithm>
template <typename T> inline void read(T& t) {
int f = 0, c = getchar(); t = 0;
while (!isdigit(c)) f |= c == '-', c = getchar();
while (isdigit(c)) t = t * 10 + c - 48, c = getchar();
if (f) t = -t;
}
template <typename T, typename... Args>
inline void read(T& t, Args&... args) {
read(t); read(args...);
}
template <typename T> void write(T x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) write(x / 10);
putchar(x % 10 + 48);
}
template <typename T> inline void writeln(T x) {
write(x); puts("");
}
template <typename T> inline bool chkmin(T& x, const T& y) { return y < x ? (x = y, 1) : 0; }
template <typename T> inline bool chkmax(T& x, const T& y) { return x < y ? (x = y, 1) : 0; }
typedef long long LL;
const int maxn = 1e5 + 207;
const LL inf = 1e10;
int v[maxn << 1], head[maxn], next[maxn << 1], tot;
LL f[maxn][2], g[maxn][2], h[maxn][30][2][2], val[maxn];
int fa[maxn][30], dep[maxn];
int n, m;
inline void ae(int x, int y) {
v[++tot] = y; next[tot] = head[x]; head[x] = tot;
v[++tot] = x; next[tot] = head[y]; head[y] = tot;
}
void dfs1(int x) {
using std::min;
f[x][1] = val[x]; dep[x] = dep[fa[x][0]] + 1;
for (int i = 1; i <= 20; ++i) fa[x][i] = fa[fa[x][i - 1]][i - 1];
for (int i = head[x]; i; i = next[i])
if (v[i] != fa[x][0]) {
fa[v[i]][0] = x;
dfs1(v[i]);
f[x][0] += f[v[i]][1];
f[x][1] += min(f[v[i]][0], f[v[i]][1]);
}
}
void dfs2(int x) {
using std::min;
for (int i = 1; i <= 20; ++i)
for (int j = 0; j <= 1; ++j)
for (int k = 0; k <= 1; ++k)
h[x][i][j][k] = min(h[x][i - 1][j][0] + h[fa[x][i - 1]][i - 1][0][k],
h[x][i - 1][j][1] + h[fa[x][i - 1]][i - 1][1][k]);
for (int i = head[x]; i; i = next[i])
if (v[i] != fa[x][0]) {
h[v[i]][0][0][0] = inf;
h[v[i]][0][0][1] = f[x][1] - min(f[v[i]][0], f[v[i]][1]);
h[v[i]][0][1][0] = f[x][0] - f[v[i]][1];
h[v[i]][0][1][1] = f[x][1] - min(f[v[i]][0], f[v[i]][1]);
g[v[i]][0] = g[x][1] + f[x][1] - min(f[v[i]][0], f[v[i]][1]);
g[v[i]][1] = min(g[v[i]][0], g[x][0] + f[x][0] - f[v[i]][1]);
dfs2(v[i]);
}
}
inline bool isAncestor(int x, int y) {
for (int i = 20; ~i; --i) if (dep[fa[y][i]] >= dep[x]) y = fa[y][i];
return x == y;
}
inline LL getAns(int x, int qx, int y, int qy) {
using std::min;
using std::swap;
if (dep[x] < dep[y]) { swap(x, y); swap(qx, qy); }
if (isAncestor(y, x)) {
LL tmp[2] = {f[x][0], f[x][1]}; tmp[qx ^ 1] = inf;
for (int i = 20; ~i; --i) if (dep[fa[x][i]] > dep[y]) {
LL t0 = min(tmp[0] + h[x][i][0][0], tmp[1] + h[x][i][1][0]);
LL t1 = min(tmp[0] + h[x][i][0][1], tmp[1] + h[x][i][1][1]);
tmp[0] = t0; tmp[1] = t1; x = fa[x][i];
}
LL ans = min(tmp[0] + h[x][0][0][qy], tmp[1] + h[x][0][1][qy]) + g[y][qy];
return ans < inf ? ans : -1;
} else {
LL tx[2] = {f[x][0], f[x][1]}; tx[qx ^ 1] = inf;
LL ty[2] = {f[y][0], f[y][1]}; ty[qy ^ 1] = inf;
for (int i = 20; ~i; --i) if (dep[fa[x][i]] >= dep[y]) {
LL t0 = min(tx[0] + h[x][i][0][0], tx[1] + h[x][i][1][0]);
LL t1 = min(tx[0] + h[x][i][0][1], tx[1] + h[x][i][1][1]);
tx[0] = t0; tx[1] = t1; x = fa[x][i];
}
for (int i = 20; ~i; --i) if (fa[x][i] != fa[y][i]) {
LL t0 = min(tx[0] + h[x][i][0][0], tx[1] + h[x][i][1][0]);
LL t1 = min(tx[0] + h[x][i][0][1], tx[1] + h[x][i][1][1]);
tx[0] = t0; tx[1] = t1; x = fa[x][i];
t0 = min(ty[0] + h[y][i][0][0], ty[1] + h[y][i][1][0]);
t1 = min(ty[0] + h[y][i][0][1], ty[1] + h[y][i][1][1]);
ty[0] = t0; ty[1] = t1; y = fa[y][i];
}
LL ans = min(f[fa[x][0]][0] - f[y][1] - f[x][1] + tx[1] + ty[1] + g[fa[x][0]][0],
f[fa[x][0]][1] - min(f[x][0], f[x][1]) - min(f[y][0], f[y][1])
+ min(tx[0], tx[1]) + min(ty[0], ty[1]) + g[fa[x][0]][1]);
return ans < inf ? ans : -1;
}
}
int main() {
read(n, m);
{ char XuYuShuILoveYou[10]; scanf("%s", XuYuShuILoveYou); }
for (int i = 1; i <= n; ++i) read(val[i]);
for (int i = 1; i != n; ++i) {
int x, y; read(x, y);
ae(x, y);
}
dfs1(1);
dfs2(1);
while (m--) {
int x, qx, y, qy;
read(x, qx, y, qy);
writeln(getAns(x, qx, y, qy));
}
return 0;
}