@NOIP2018 - D2T3@ 保卫王国


@题目描述@

Z 国有n座城市,n−1 条双向道路,每条双向道路连接两座城市,且任意两座城市 都能通过若干条道路相互到达。

Z 国的国防部长小 Z 要在城市中驻扎军队。驻扎军队需要满足如下几个条件:

(1)一座城市可以驻扎一支军队,也可以不驻扎军队。
(2)由道路直接连接的两座城市中至少要有一座城市驻扎军队。
(3)在城市里驻扎军队会产生花费,在编号为i的城市中驻扎军队的花费是p。

小 Z 很快就规划出了一种驻扎军队的方案,使总花费最小。但是国王又给小 Z 提出了 m 个要求,每个要求规定了其中两座城市是否驻扎军队。小 Z 需要针对每个要求逐一给出回答。
具体而言,如果国王提出的第 j 个要求能够满足上述驻扎条件(不需要考虑第 j 个要求之外的其它要求),则需要给出在此要求前提下驻扎军队的最小开销。如果国王提出的第 j 个要求无法满足,则需要输出−1(1≤j≤m)。现在请你来帮助小 Z。

输入
第 1 行包含两个正整数 n,m 和一个字符串 type,分别表示城市数、要求数和数据类型。type 是一个由大写字母 A,B 或 C 和一个数字 1,2,3 组成的字符串。它可以帮助你获得部分分。你可能不需要用到这个参数。

第 2 行 n 个整数 pi,表示编号 i 城市中驻扎军队的花费。

接下来 n−1 行,每行两个正整数u,v,表示有一条 u 到 v 的双向道路。

接下来 m 行,第 j 行四个整数 a,x,b,y(a≠b),表示第 j 个要求是在城市 a 驻扎 x 支军队, 在城市 b 驻扎 y 支军队。其中,x 、y 的取值只有 0 或 1:若 x 为 0,表示城市 a 不得驻扎军队,若 x 为 1,表示城市 a 必须驻扎军队;若 y 为 0,表示城市 b 不得驻扎军队, 若 y 为 1,表示城市 b 必须驻扎军队。

输入文件中每一行相邻的两个数据之间均用一个空格分隔。

输出
输出共 m 行,每行包含 1 个整数,第 j 行表示在满足国王第 j 个要求时的最小开销, 如果无法满足国王的第 j 个要求,则该行输出 −1。

输入样例#1
5 3 C3
2 4 1 3 9
1 5
5 2
5 3
3 4
1 0 3 0
2 1 3 1
1 0 5 0
输出样例#1
12
7
-1

样例解释
对于第一个要求,在 4 号和 5 号城市驻扎军队时开销最小。
对于第二个要求,在 1 号、2 号、3 号城市驻扎军队时开销最小。
第三个要求是无法满足的,因为在 1 号、5 号城市都不驻扎军队就意味着由道路直接连接的两座城市中都没有驻扎军队。

数据规模与约定
对于 100% 的数据,n,m ≤ 100000,1 ≤ pi ≤ 100000。

@题解@

傻逼倍增题,我竟然不会做,实在是太弱了。

假如不加限制就是一个很简单的 dp。
状态定义为 dp[i][0/1] 表示 i 结点驻扎/不驻扎时,i 为根的子树的最小值。
于是:

\[dp[i][0]=\sum_{j=1}^{j\in\ i's \ child}dp[j][1]\\dp[i][1]=p_i+\sum_{j=1}^{j\in\ i's \ child}min(dp[j][0], dp[j][1]) \]

基于此,我们最简单最粗暴的想法肯定就是:写动态 dp。假如结点 a 一定驻扎,就修改 dp[a][0] = INF;否则修改 dp[a][1] = INF。
没错考场上我也是这么想的……然后发现自己写不来……

但其实这道题不需要写动态 dp,因为没有修改操作,而只有查询操作。
我们再看看我们按照动态 dp 的想法而实现的暴力:如果修改结点 a, b 的权,就从 a, b 开始往上爬父亲,更新沿途的 dp 值。
可不可以用某种方法加速这个爬父亲的过程呢?
当然可以。我们利用我们求 LCA 用的方法:树上倍增。

具体来说,令\(fa[i][j]\):i 的第 2^j 个父亲;令 \(f[i][j][0/1][0/1]\) :当固定 i 驻扎/不驻扎时,i 的第 2^j 个父亲的 dp[0/1] 的最小值。
如果已知 i 的第 2^(j-1) 个父亲的信息,可以通过枚举 i 的 2^(j-1) 个父亲的驻扎情况,推导出 i 的第 2^j 个父亲的信息。假如令 k = i 的 2^(j-1) 个父亲,举一个转移的例子:

\[f[i][j][0][0] = \min(f[k][j-1][0][0]-dp[k][0]+f[i][j-1][0][0], f[k][j-1][1][0]-dp[k][1]+f[i][j-1][0][1]) \]

min 的前半部分表示 i 的第 2^(j-1) 个父亲不驻扎,后半部分表示 i 的第 2^(j-1) 个父亲要驻扎。大体来说就是:总答案-不受限制的贡献+受限制的贡献
边界情况 f[i][0][0/1][0/1] 的求解可以直接借助 dp 数组来搞。

假如询问 a 和 b,我们可以先跳到两者的 LCA 再继续跳到根,求出根的值。因为由上面我们可以 f 是可以合并相互之间的信息的,所以只需要仿照上面的方法就可以实现倍增跳父亲。

具体实现细节可看看代码。

@代码@

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 100000;
const ll INF = (1LL<<60);
struct edge{
    int to;
    edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v) {
    edge *p=(++ecnt);
    p->to = v, p->nxt = adj[u], adj[u] = p;
    p = (++ecnt);
    p->to = u, p->nxt = adj[v], adj[v] = p;
}
int a, x, b, y;
bool g[MAXN + 5];
int p[MAXN + 5], dep[MAXN + 5], fa[MAXN + 5][20];
ll dp[MAXN + 5][2], f[MAXN + 5][20][2][2];
void dfs1(int rt, int pre) {
    dep[rt] = dep[pre] + 1;
    dp[rt][0] = 0, dp[rt][1] = p[rt];
    for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
        if( p->to == pre ) continue;
        dfs1(p->to, rt);
        dp[rt][0] = (dp[rt][0] + dp[p->to][1]);
        dp[rt][1] = (dp[rt][1] + min(dp[p->to][0], dp[p->to][1]));
    }
}
void dfs2(int rt, int pre) {
    fa[rt][0] = pre;
    f[rt][0][0][0] = INF;
    f[rt][0][1][0] = dp[pre][0];
    f[rt][0][0][1] = dp[pre][1] - min(dp[rt][0], dp[rt][1]) + dp[rt][0];
    f[rt][0][1][1] = dp[pre][1] - min(dp[rt][0], dp[rt][1]) + dp[rt][1];
    for(int i=1;i<20;i++) {
    	int anc = fa[rt][i-1];
        fa[rt][i] = fa[anc][i-1];
        f[rt][i][0][0] = min(f[anc][i-1][0][0]-dp[anc][0]+f[rt][i-1][0][0], f[anc][i-1][1][0]-dp[anc][1]+f[rt][i-1][0][1]);
        f[rt][i][1][0] = min(f[anc][i-1][0][0]-dp[anc][0]+f[rt][i-1][1][0], f[anc][i-1][1][0]-dp[anc][1]+f[rt][i-1][1][1]);
        f[rt][i][0][1] = min(f[anc][i-1][0][1]-dp[anc][0]+f[rt][i-1][0][0], f[anc][i-1][1][1]-dp[anc][1]+f[rt][i-1][0][1]);
        f[rt][i][1][1] = min(f[anc][i-1][0][1]-dp[anc][0]+f[rt][i-1][1][0], f[anc][i-1][1][1]-dp[anc][1]+f[rt][i-1][1][1]);
    }
    for(edge *p=adj[rt];p!=NULL;p=p->nxt) {
        if( p->to == pre ) continue;
        dfs2(p->to, rt);
    }
}
char type[3];
int main() {
    int n, m;
    scanf("%d%d%s", &n, &m, type);
    for(int i=1;i<=n;i++)
        scanf("%d", &p[i]);
    for(int i=1;i<n;i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        addedge(u, v);
    }
    dfs1(1, 0); dfs2(1, 0);
    for(int i=1;i<=m;i++) {
        scanf("%d%d%d%d", &a, &x, &b, &y);
        if( x == 0 && y == 0 ) {
            if( fa[a][0] == b || fa[b][0] == a ) {
                printf("-1\n");
                continue;
            }
        }
        if( dep[a] < dep[b] )
        	swap(a, b), swap(x, y);
        ll v1, v2, v3, v4;
        v1 = x? dp[a][1] : INF;
        v2 = x? INF : dp[a][0];
        v3 = y? dp[b][1] : INF;
        v4 = y? INF : dp[b][0];
        for(int i=19;i>=0;i--) {
        	if( dep[fa[a][i]] >= dep[b] ) {
        		ll tmp1, tmp2;
        		tmp1 = min(f[a][i][1][1] - dp[a][1] + v1, f[a][i][0][1] - dp[a][0] + v2);
        		tmp2 = min(f[a][i][1][0] - dp[a][1] + v1, f[a][i][0][0] - dp[a][0] + v2);
        		v1 = tmp1, v2 = tmp2;
        		a = fa[a][i];
            }
        }
        if( a == b ) {
            if( y ) v2 = INF;
            else v1 = INF;
        }
        else {
            for(int i=19;i>=0;i--)
                if( fa[a][i] != fa[b][i] ) {
                	ll tmp1, tmp2, tmp3, tmp4;
                	tmp1 = min(f[a][i][1][1] - dp[a][1] + v1, f[a][i][0][1] - dp[a][0] + v2);
                	tmp2 = min(f[a][i][1][0] - dp[a][1] + v1, f[a][i][0][0] - dp[a][0] + v2);
                	tmp3 = min(f[b][i][1][1] - dp[b][1] + v3, f[b][i][0][1] - dp[b][0] + v4);
                	tmp4 = min(f[b][i][1][0] - dp[b][1] + v3, f[b][i][0][0] - dp[b][0] + v4);
                	v1 = tmp1, v2 = tmp2, v3 = tmp3, v4 = tmp4;
                	a = fa[a][i], b = fa[b][i];
                }
            int pr = fa[a][0];
            ll tmp1, tmp2, tmp3 = dp[pr][1]-min(dp[b][0], dp[b][1])-min(dp[a][0], dp[a][1]), tmp4 = dp[pr][0]-dp[a][1]-dp[b][1];
            tmp1 = tmp3+min(v1, v2)+min(v3, v4);
            tmp2 = tmp4+v1+v3;
            v1 = tmp1, v2 = tmp2;
            a = pr;
        }
        for(int i=19;i>=0;i--)
            if( dep[fa[a][i]] >= dep[1] ) {
            	ll tmp1, tmp2;
            	tmp1 = min(f[a][i][1][1] - dp[a][1] + v1, f[a][i][0][1] - dp[a][0] + v2);
            	tmp2 = min(f[a][i][1][0] - dp[a][1] + v1, f[a][i][0][0] - dp[a][0] + v2);
            	v1 = tmp1, v2 = tmp2;
            	a = fa[a][i];
            }
        printf("%lld\n", min(v1, v2));
    }
}

posted @ 2018-12-25 10:09  Tiw_Air_OAO  阅读(255)  评论(0编辑  收藏  举报