NOIP2018 提高组 题解

【Day1】

【铺设道路】
终极傻题,并且是NOIP2013积木大赛原题
可以用一种分治的思想: d f s ( l , r , h ) dfs(l, r, h) 表示现在站在 h h 的高度上,要把 [ 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 当中删掉一些元素即可。删掉什么元素?当然是能被其它元素表示出来的元素。所以写一个类似于背包的dp即可
f [ i ] f[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 ,然后尽可能多地选出长度大于等于 m i d mid 的链,看看能不能选出至少 m m 条。
可以这样考虑:处理完以 x x 为根的子树之后,留一个长度不足 m i d mid 的线头给 x x 的父亲,为了让我们选出的链的总数尽可能多,肯定要选择“最有潜力”的那个线头,即这个线头的长度应该尽可能大。
考虑处理以 x x 为根的子树,我们可以把每个孩子带来的线头(不要忘记加上这个孩子到 x x 的边权)存进一个数组,先对这个数组从小到大排序,首先那些本身已经大于等于 m i d mid 的就可以不用管了;对于剩下的线头,尽可能让它们配对,即选择两根线头连起来让它们大于等于 m i d mid ;对于那些既不够长也不能配对的线头,挑一个最大的 r e t u r n return 即可。不要忘了在这个过程中每次合成一条新链都要++cnt
这个数组可以用一个 m u l t i s e t multiset 实现。
二分的上界可以选择树的直径。并且我发现似乎有很多人树的直径都写得很长,其实求树的直径最短的写法就是树形dp,这里啰嗦一下。设 f [ x ] f[x] 表示以 x x 为根的子树的直径, g [ x ] g[x] 表示从 x x 往下走的最长路径,那么只要一个简短的 d f s 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 ,在 d f s dfs 的时候注意一下儿子的顺序就好了。那么基环树的话,注意到 n 5000 n\leq 5000 ,只要找出环,枚举断环上的哪条边,然后当树做一遍,答案取最小就好了。复杂度 O ( n 2 ) O(n^2)
另外千万不要以为 n 5000 n\leq 5000 你就能为所欲为,如果你为了省事用邻接矩阵做 d f s dfs 就会T。因为邻接矩阵 d f s dfs 复杂度是 O ( n 2 ) O(n^2) ,总复杂度 O ( n 3 ) O(n^3) ,i7也救不了你。
考场代码,并不是很好看。
另外此题有 O ( n log n ) O(n\log n) 的做法,但超出了本文的范围。我也不知道本文的范围是什么。其实是我不会。

#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 的情况。当时还想过一些对角线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] 表示以 x x 为根的子树,其中 x x 不选/选,最小花费是多少。
f [ x ] [ 0 ] = f [ v ] [ 1 ] f[x][0]=\sum 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]

  • 法1:动态dp
    这几乎就是动态dp求最大独立集的模板题,连方程都差不多。
    事实上这俩题的确有很大的联系。这题求的是最小覆盖集,而最小覆盖集=总点权-最大独立集。
    如果要强制选一个点,就把它的点权改成0;强制不选一个点,就把它的点权改成 i n f inf ,然后如果dp的结果是 i n f inf 就说明不合法,输出 1 -1 ,否则就输出结果。不要忘记把强制选的那个点的点权加上。最后再把点权改回来即可。
    这种做法的好处:一是比较无脑可以直接套模板,二是可以轻易地改成每次询问 k k 个点( k = O ( n ) \sum 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] 的基础上,可以设 g [ x ] [ 0 / 1 ] g[x][0/1] 表示:对于整棵树减去以 x x 为根的子树剩下的部分,当 x x 不选/选的时候,这一部分的dp值。注意,这一部分并不包括 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 ] [ 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 x x 的孩子)
    其实做到这里应该有一个感觉:由于 f [ x ] [ 0 / 1 ] f[x][0/1] 的转移是一个累加的式子,所以我们可以很方便地减去某棵子树的贡献来得到其他部分的贡献。
    考虑倍增:先有 f a [ x ] [ i ] fa[x][i] 表示 x x 2 i 2^i 级祖先,显然转移 f a [ x ] [ i ] = f a [ f a [ 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] 表示以 f a [ x ] [ i ] fa[x][i] 为根的子树去掉以 x x 为根的子树,剩下的部分的dp值,并且必须满足 x x 不选/选, f a [ x ] [ i ] fa[x][i] 不选/选。注意,这个状态所包含的结点同样不包括 x x 本身,但其值与 x x 选/不选有关。
    初始条件是
    h [ v ] [ 0 ] [ 0 ] [ 0 ] = i n f 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 ] [ 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])
    然后转移就枚举 2 i 1 2^{i-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
    其实并不是很难的东西。
    对于一个询问 ( x , q x , y , q y ) ( q x , q y { 0 , 1 } ) (x,qx,y,qy)(qx,qy\in\{0,1\}) ,令 x x 是深度较大的点,分两种情况处理: y y x x 的祖先,或者 y y 不是 x x 的祖先。 y y x x 的祖先时,从 x x 一路倍增跳到 y y 即可; y y 不是 x x 的祖先时,先把 x x 跳到与 y y 同深度,然后再一起跳到 l c a lca 。跳的过程中(以跳 x x 为例)要保存 t x [ 0 / 1 ] tx[0/1] 表示目前 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;
}

猜你喜欢

转载自blog.csdn.net/qq_39677783/article/details/85265967