P5024 保卫王国

Link

题目描述

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

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

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

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

输入格式

第一行有两个整数和一个字符串,依次表示城市数 \(n\),要求数 \(m\) 和数据类型 \(type\)\(type\) 是一个由大写字母 ABC 和一个数字 123 组成的字符串。它可以帮助你获得部分分。你可能不需要用到这个参数。这个参数的含义在【数据规模与约定】中 有具体的描述。

第二行有 \(n\) 个整数,第 \(i\) 个整数表示编号 \(i\) 的城市中驻扎军队的花费 \(p_i\)

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

接下来 \(m\) 行,每行四个整数 \(a,x,b,y\),表示一个要求是在城市 \(a\) 驻扎 \(x\) 支军队,在城市 \(b\) 驻扎 \(y\) 支军队。其中,\(x,y\) 的取值只有 \(0\)\(1\)

  • \(x\)\(0\),表示城市 \(a\) 不得驻扎军队。
  • \(x\)\(1\),表示城市 \(a\) 必须驻扎军队。
  • \(y\)\(0\),表示城市 \(b\) 不得驻扎军队。
  • \(y\)\(1\),表示城市 \(b\) 必须驻扎军队。

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

输出格式

输出共 \(m\) 行,每行包含一个个整数,第 \(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

说明/提示

样例 1 解释

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

数据规模与约定

测试点编号 type n=m
1,2 A3 10
3,4 C3 10
5,6 A3 100
7 C3 100
8,9 A3 \(2\times 10^3\)
10,11 C3 \(2\times 10^3\)
12,13 A1 \(10^5\)
14,15,16 A2 \(10^5\)
17 A3 \(10^5\)
18,19 B1 \(10^5\)
20,21 C1 \(10^5\)
22 C2 \(10^5\)
23,24,25 C3 \(10^5\)

数据类型的含义:

  • A:城市 \(i\) 与城市 \(i+1\) 直接相连。
  • B:任意城市与城市 1 的距离不超过 100(距离定义为最短路径上边的数量),即如果这 棵树以 1 号城市为根,深度不超过 100。
  • C:在树的形态上无特殊约束。
  • 1:询问时保证 \(a=1,x=1\),即要求在城市 1 驻军。对 \(b,y\) 没有限制。
  • 2:询问时保证 \(a,b\) 是相邻的(由一条道路直接连通)
  • 3:在询问上无特殊约束。

对于 \(100%\)的数据,保证\(1 \leq n,m ≤ 10^5\)\(1 ≤ p_i ≤ 10^5\) , \(1 \leq u, v, a, b \leq n\), \(a \neq b\)\(x, y \in \{0, 1\}\)

题解

什么 \(Noip\) 居然考模板题,哦是 \(动态dp\), 那没事了.

动态dp,板子题了。

首先,我们考虑不带修改的时候,转移方程可以写成这样

\(f[i][0] = \displaystyle \sum_{to \in son[i]} f[to][1]\)

\(f[i][1] = \displaystyle \sum_{to \in son[i]} min(f[to][1],f[to][0]) \ + w[i]\)

还是把轻儿子和重儿子的贡献分开来考虑。

\(g[i][0] = \displaystyle \sum_{to \in 轻儿子} f[to][1]\) \(g[i][1] = \displaystyle \sum_{to \in 轻儿子} min(f[to][1],f[to][0]) \ + w[x]\)

解释一下 \(g[i][0]\) 表示必须选他轻儿子的总的价值和, \(g[i][1]\) 表示 他轻儿子选或不选的最大价值加上这个点的权值

转移方程就可以转化为:

\(f[i][0] = g[i][0] + f[son[x]][1]\)

\(f[i][1] = g[i][1] + min(f[to][1],f[to][0]) \ +w[x]\)

这里定义广义矩阵乘法为

矩阵 \(C = A \times B\)

那么 \(c[i][j] = min(a[i][k] + b[k][j])\)

上面的那个转移方程转化为矩阵就可以写成:

\[\left[ \begin{matrix} \infin & g[i][0] \\ g[i][1] & g[i][1] \\ \end{matrix} \right] \tag{2} \times \left[ \begin{matrix} f[to][0] \\ f[to][1] \\ \end{matrix} \right] = \left[ \begin{matrix} \ f[i][0] \\ \ f[i][1] \\ \end{matrix} \right] \]

然后就可以愉快的套动态dp 的板子了

每个点必须放城市可以通过 \(-\infin\) 来实现,反之不能放就可以通过 \(+ \infin\) 实现。

但最后计算答案时,要必须放城市的点要加上减去的 \(\infin\).

因为每个询问是独立的,所以每次询问不要忘了,输出答案之后,要把之前的点的权值还原回来

无解的情况就是当两个点相邻且都不能被选的情况。

Code(不开o2,过不去系列):

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int inf = 5e9;
const int N = 1e5+10;
char type[2];
int n,m,tot,x,y,num,val,u,v,a,b,w1,w2;
int head[N],top[N],dep[N],siz[N],fa[N],son[N];
int end[N],f[N][2],g[N][2],dfn[N],w[N],ord[N];
struct node
{
    int a[2][2];
    node() {a[0][0] = a[0][1] = a[1][0] = a[1][1] = inf;}//矩阵初始化为极大值
}tr[N<<2],base[N];
struct bian
{
	int to,net;
}e[N<<1];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
    return s * w;
}
void add(int x,int y)
{
	e[++tot].to = y;
	e[tot].net = head[x];
	head[x] = tot;
}
node operator * (node x,node y)//广义矩阵乘法
{
    node res;
    for(int i = 0; i <= 1; i++)
    {
        for(int j = 0; j <= 1; j++)
        {
            for(int k = 0; k <= 1; k++)
            {
                res.a[i][j] = min(res.a[i][j],x.a[i][k] + y.a[k][j]);
            }
        }
    }
    return res;
}
void get_tree(int x)//树剖预处理
{
    dep[x] = dep[fa[x]] + 1; siz[x] = 1;
    for(int i = head[x]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(to == fa[x]) continue;
        fa[to] = x;
        get_tree(to);
        siz[x] += siz[to];
        if(siz[to] > siz[son[x]]) son[x] = to;
    }
}
void dfs(int x,int topp)//处理出每条链的链顶
{    
    dfn[x] = ++num; top[x] = topp; ord[num] = x;
    if(son[x] == -1)
    {
        end[topp] = num;//预处理每条重链的链末
        return;
    }
    else dfs(son[x],topp);
    for(int i = head[x]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(to == fa[x] || to == son[x]) continue;
        dfs(to,to);
    }
}
void dp(int x,int fa)//先预处理出 f,g 值
{
    g[x][1] = f[x][1] = w[x];
    for(int i = head[x]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(to == fa) continue;
        dp(to,x);
        f[x][0] += f[to][1];
        f[x][1] += min(f[to][0],f[to][1]);
        if(to != son[x])
        {
            g[x][0] += f[to][1];
            g[x][1] += min(f[to][0],f[to][1]);
        }
    }
}
void up(int o)
{
    tr[o] = tr[o<<1] * tr[o<<1|1];
}
void build(int o,int L,int R)//建树操作
{
    if(L == R)
    {
        int tmp = ord[L];
        tr[o].a[0][1] = g[tmp][0];//构建转移矩阵
        tr[o].a[1][0] = tr[o].a[1][1] = g[tmp][1];
        base[L] = tr[o];
        return;
    }
    int mid = (L + R)>>1;
    build(o<<1,L,mid);
    build(o<<1|1,mid+1,R);
    up(o);
}
void chenge(int o,int L,int R,int x)
{
    if(L == R)
    {
        tr[o] = base[L];
        return;
    }
    int mid = (L + R)>>1;
    if(x <= mid) chenge(o<<1,L,mid,x);
    if(x > mid) chenge(o<<1|1,mid+1,R,x);
    up(o);
}
node query(int o,int l,int r,int L,int R)
{
    if(L <= l && R >= r) return tr[o];
    int mid = (l + r)>>1;
    if(R <= mid) return query(o<<1,l,mid,L,R);
    if(L > mid) return query(o<<1|1,mid+1,r,L,R);
    return query(o<<1,l,mid,L,R) * query(o<<1|1,mid+1,r,L,R);
}
node get_node(int x)
{
    return query(1,1,n,dfn[x],end[top[x]]);
}
void modify(int x,int val)//增量法修改每个点的转移矩阵
{
    base[dfn[x]].a[1][0] += val - w[x];
    base[dfn[x]].a[1][1] = base[dfn[x]].a[1][0];
    w[x] = val;
    while(x)
    {
        node Old = get_node(top[x]);
        chenge(1,1,n,dfn[x]);
        node New = get_node(top[x]);
        int tmp = dfn[fa[top[x]]];
        base[tmp].a[0][1] += New.a[1][1] - Old.a[1][1];
        base[tmp].a[1][0] += min(New.a[0][1],New.a[1][1]) - min(Old.a[0][1],Old.a[1][1]);
        base[tmp].a[1][1] = base[tmp].a[1][0];
        x = fa[top[x]];
    }
}
signed main()
{
    n = read(); m = read(); 
    scanf("%s",type+1);
    for(int i = 1; i <= n; i++)
    {
        w[i] = read();
        son[i] = -1;
    }
    for(int i = 1; i <= n-1; i++)
    {
        u = read(); v = read();
        add(u,v); add(v,u);
    }
    get_tree(1); dfs(1,1); dp(1,1); build(1,1,n);
    for(int i = 1; i <= m; i++)
    {
        a = read(); w1 = read();
        b = read(); w2 = read();
        if(!w1 && !w2 &&(fa[a] == b || fa[b] == a))//无解的情况
        {
        	puts("-1");
        	continue;
        }
        int v1 = w[a], v2 = w[b];
        if(w1 == 0) modify(a,v1+inf);
        if(w1 == 1) modify(a,v1-inf);
        if(w2 == 0) modify(b,v2+inf);
        if(w2 == 1) modify(b,v2-inf);
        node ans = get_node(1);
        int res = min(ans.a[0][1],ans.a[1][1]);
        if(w1 == 1) res += inf;//加回减去的 inf
        if(w2 == 1) res += inf;
        modify(a,v1);//改回原值
        modify(b,v2);
        printf("%lld\n",res);
    }
    return 0;
}

这题实现起来还是比较麻烦的,自己调了一下午才能勉强调出来(一定是自己太菜了)。

另外还有一种倍增的做法,然鹅我并不会。

posted @ 2020-09-06 08:50  genshy  阅读(203)  评论(0编辑  收藏  举报