P5024 保卫王国[倍增+dp]

窝当然不会ddp啦,要写这题当然是考虑优化裸dp啦,但是这题非常麻烦,于是变成了黑题。

首先,这个是没有上司的舞会模型,求图的带权最大独立集。

不考虑国王的限制条件,有
\[
dp[x][0]+=dp[y][1]\dp[x][1]+=min(dp[y][1],dp[y][0])
\]
现在考虑限制条件,如果对每一个限制条件都做一次dp,复杂度达到\(O(n^2)\),无法承受。

显然,对于这些限制条件,每一次的变动不会影响其它大多数的状态。

对于一个限制条件,我们分开考虑,先考虑只对一个城市进行限制的情况。

若该城市被要求驻扎军队,那么如果在最优情况下它本来就需要军队,则没有影响;如果它本来不需要军队,由于此时所有子树都是最优解,那么它只会对从它到根节点的路径的最优解产生影响

若该城市被要求不能驻扎,那么与上面的情况类似,如果它本来需要驻扎,那么会对它到根节点的路径造成影响。

综上所述,我们是否可以考虑对于每个限制条件,只对两个点到根节点之间的路径的某些状态进行修改呢?答案是肯定的。

假设待修改节点为\(x\)。首先,此时除了\(x\sim root\)的路径上的节点所表示的状态,其它状态都是最优的。对于这条路径的更新,实际上就是又对这条路径做了一次dp,并强制\(x\)选或不选,从而限制转移。

显然这是可行的,但是复杂度仍为\(O(n^2)\),我们需要一些手段进行优化。

容易发现我们更新的是一条链,对于一条链,我们自然可以想到用倍增或者树剖(它们维护的都是树链)来处理每个点的转移。

以倍增为例。

首先考虑裸dp的步骤,一个一个跳,每次从儿子转移。那我们能不能一段一段地跳,每次从已有信息中转移呢?当然可以。

先考虑只有一个点的情况。

如果我们要对\(x\)进行限制,那么我们不妨把整个图变成这样,方便分析

其中绿色的部分是\(x\)的子树的最优解。

显然我们在修改一个点之后,造成的对原最优解的影响是一定的,产生的新最优解是一定的,这意味着我们可以通过一些手段预处理出来。

不妨考虑通过倍增预处理出最优情况下每个点产生变动(选变成不选,不选变成选)在这条\(x\sim root\)的链上产生的新最优解,也就是预处理出修改后dp时跳某一段的该段最优解。

然后从\(x\)树上倍增跳到\(root\),合并所得的新的最优解,就是这次限制条件下的答案。

实际上,我们的倍增预处理是在一次裸dp的基础上进行的。换句话说,倍增时用到的信息就是每个点表示的状态的原来的最优解,然后加一个限制条件,再做一个dp。

形象的讲,这个倍增预处理,就是dp套dp。

那这个倍增怎么做呢?

考虑状态的刻画,显然由几个较小的子问题合并成较大的子问题时,这些较小的子问题不能有交叉,否则无法基于二进制划分合并成更大的子问题。再者,子问题必须覆盖整个状态空间。也就是说,在状态定义时,我们要不重不漏。

既然如此,对于第一点,不难想到倍增时我们不仅要记录\(x\)的状态(0/1),还要记录它跳\(2^k\)步的祖先的状态。

对于第二点,不难想出状态包含的范围。

设\(f[0/1][0/1][k][x]\)表示\(x\)节点变为0不可选、1可选时,向上\(2^k\)步的祖先\(y\)为0可选、1不可选时,\(y\)除去\(x\)以及\(x\)的子树的原最优解的其它所有子树的新最优解

如下图,假设节点3是\(x\)的\(2^k\)祖先,状态为红色部分

而3的\(2^k\)祖先(假设是\(root\))表示的状态就是蓝色部分。

这样就可以很好的合并状态,之后统计答案的过程中,我们只需枚举\(x\)的状态并倍增跳即可。

对于节点\(y\),它的父节点为\(x\),有
\[
f[1][0][0][y]=dp[0][x]-dp[1][y]\\ f[0][1][0][y]=f[1][1][0][y]=dp[1][x]-min(dp[1][y],dp[0][y])
\]

得到转移(cao)
\[
f[0][0][j][y]=min(f[0][0][j-1][y]+f[0][0][j-1][fa],f[0][1][j-1][y]+f[1][0][j-1][fa])\ f[0][1][j][y]=min(f[0][0][j-1][y]+f[0][1][j-1][fa],f[0][1][j-1][y]+f[1][1][j-1][fa])\ f[1][0][j][y]=min(f[1][0][j-1][y]+f[0][0][j-1][fa],f[1][1][j-1][y]+f[1][0][j-1][fa])\ f[1][1][j][y]=min(f[1][0][j-1][y]+f[0][1][j-1][fa],f[1][1][j-1][y]+f[1][1][j-1][fa])
\]
然后就是统计答案,当然是倍增统计答案。

从待修改点的两种不同状态开始往上倍增合并新最优解,直到根节点。

对于两个待修改点\(x,y\)的情况,我们像求LCA一样,先统计\(x\sim lca(x,y),y\sim lca(x,y)\),再统计\(lca(x,y)\sim root\)即可。

注意一个细节,当\(x,y\)两点跳到\(lca\)的儿子处我们需要同时减去它们两个的原最优解,再往上跳。

复杂度降至\(O(nlogn)\)。

树剖也是一个道理,预处理变动后最优解。

参考代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
#include<cstdlib>
#include<queue>
#include<vector>
#define INF 0x3f3f3f3f
#define PI acos(-1.0)
#define N 100010
#define MOD 2520
#define E 1e-12
#define ll long long
using namespace std;
inline ll read()
{
    ll f=1,x=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
struct rec{
    int next,ver;
}g[N<<1];
int head[N],tot,n,m;
ll dp[2][N],f[2][2][21][N],gi[21][N],t,dep[N],w[N];
inline void add(int x,int y)
{
    g[++tot].ver=y;
    g[tot].next=head[x],head[x]=tot;
}
inline void dfs(int x,int fa)
{
    dp[1][x]=w[x];dep[x]=dep[fa]+1;
    f[0][0][0][x]=INF,gi[0][x]=fa;
    for(int j=1;j<t;++j) gi[j][x]=gi[j-1][gi[j-1][x]];
    for(int i=head[x];i;i=g[i].next){
        int y=g[i].ver;
        if(y==fa) continue;
        dfs(y,x);
        dp[0][x]+=dp[1][y];
        dp[1][x]+=min(dp[1][y],dp[0][y]);
    }
}
inline void init()
{
    queue<ll> q;
    q.push(1);
    f[1][0][0][1]=dp[0][0]-dp[1][1];
    f[0][1][0][1]=f[1][1][0][1]=dp[1][0]-min(dp[1][1],dp[0][1]);
    while(q.size()){
        ll x=q.front();q.pop();
        for(int i=head[x];i;i=g[i].next){
            int y=g[i].ver;
            if(y==gi[0][x]) continue;
            f[1][0][0][y]=dp[0][x]-dp[1][y];
            f[0][1][0][y]=f[1][1][0][y]=dp[1][x]-min(dp[1][y],dp[0][y]);
            for(int j=1;j<t;++j){
                int fa=gi[j-1][y];
                f[0][0][j][y]=min(f[0][0][j-1][y]+f[0][0][j-1][fa],f[0][1][j-1][y]+f[1][0][j-1][fa]);
                f[0][1][j][y]=min(f[0][0][j-1][y]+f[0][1][j-1][fa],f[0][1][j-1][y]+f[1][1][j-1][fa]);
                f[1][0][j][y]=min(f[1][0][j-1][y]+f[0][0][j-1][fa],f[1][1][j-1][y]+f[1][0][j-1][fa]);
                f[1][1][j][y]=min(f[1][0][j-1][y]+f[0][1][j-1][fa],f[1][1][j-1][y]+f[1][1][j-1][fa]);
            }
            q.push(y);
        }
    }
}
inline ll get(ll x,int a,ll y,int b)
{
    ll ans=0,lca;
    if(dep[x]<dep[y]) swap(x,y),swap(a,b);
    ll x0=INF,x1=INF,y0=INF,y1=INF,l0=INF,l1=INF;
    a?x1=dp[1][x]:x0=dp[0][x],b?y1=dp[1][y]:y0=dp[0][y];
    for(int j=t;j>=0;--j){
        ll t0=x0,t1=x1;
        if(dep[gi[j][x]]>=dep[y]){
            x0=min(t0+f[0][0][j][x],t1+f[1][0][j][x]);
            x1=min(t0+f[0][1][j][x],t1+f[1][1][j][x]);
            x=gi[j][x];
        }
    }
    if(x==y) lca=x,b?l1=x1:l0=x0;
    else {
        for(int j=t;j>=0;--j){
            if(gi[j][x]!=gi[j][y]){
                ll t0=x0,t1=x1,p0=y0,p1=y1;
                x0=min(t0+f[0][0][j][x],t1+f[1][0][j][x]);
                x1=min(t0+f[0][1][j][x],t1+f[1][1][j][x]);
                y0=min(p0+f[0][0][j][y],p1+f[1][0][j][y]);
                y1=min(p0+f[0][1][j][y],p1+f[1][1][j][y]);
                x=gi[j][x],y=gi[j][y];
            }
        }
        lca=gi[0][x];
        l0=dp[0][lca]-dp[1][x]-dp[1][y]+x1+y1;
        l1=dp[1][lca]-min(dp[0][x],dp[1][x])-min(dp[0][y],dp[1][y])+min(x1,x0)+min(y1,y0);
    }
    if(lca==1) ans=min(l0,l1);
    else{
        for(int j=t;j>=0;--j){
            if(dep[gi[j][lca]]>1){
                ll t0=l0,t1=l1;
                l0=min(t0+f[0][0][j][lca],t1+f[1][0][j][lca]);
                l1=min(t0+f[0][1][j][lca],t1+f[1][1][j][lca]);
                lca=gi[j][lca];
            }
        }
        ans=min(dp[0][1]-dp[1][lca]+l1,dp[1][1]-min(dp[0][lca],dp[1][lca])+min(l1,l0));
    }
    return ans<INF?ans:-1;
}
int main()
{
    n=read(),m=read();
    char op[5];
    cin>>op;
    t=log2(n)+1;
    for(int i=1;i<=n;++i) w[i]=read();
    for(int i=1;i<n;++i){
        ll u=read(),v=read();
        add(u,v),add(v,u);
    }
    dfs(1,0);
    init();
    while(m--){
        ll x=read(),a=read(),y=read(),b=read();
        if(a==0&&b==0&&(gi[0][x]==y||gi[0][y]==x)){
            puts("-1");continue;
        }
        printf("%lld\n",get(x,a,y,b));
    }
    return 0;
}

原文地址:https://www.cnblogs.com/DarkValkyrie/p/11770516.html

时间: 2024-10-28 07:37:59

P5024 保卫王国[倍增+dp]的相关文章

P5024 保卫王国

——————————————————————————————————————————————- 考前练习打打部分分 设置权值这个方法需要记住 ———————————————————————————————————————————————————— PT:44 ———— #include<bits/stdc++.h> using namespace std; const int inf=1000000000; char ch[5]; int n,m,head[101000],ne,a,b,c,d

LuoguP5024 保卫王国(动态DP,树形DP,矩阵加速,LCT)

最小权覆盖集 = 全集 - 最大权独立集 强制取点.不取点可以使用把权值改成正无穷或负无穷实现 接下来就是经典的"动态最大权独立集"了 O(nlogn). 这不是我说的,是immortalCO大佬说的 于是我调了一万年极值,终在\(\frac{LLONG\_MAX}{3}\)时\(11s\)卡过... 知道最小权覆盖集 = 全集 - 最大权独立集,就是模板\(DDP\)了 #include <cstdio> #include <iostream> #includ

[luogu 5024] 保卫王国

luogu 5024(保卫王国) Problem Here Solution 这大概是一篇重复累赘的blog吧. 最小覆盖集=全集-最大独立集 强制取或不取,可以通过将权值修改成inf或者-inf 然后就用动态dp的套路就行了 #include<bits/stdc++.h> #define ll long long #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) inline ll read

zoj 3649 lca与倍增dp

参考:http://www.xuebuyuan.com/609502.html 先说题意: 给出一幅图,求最大生成树,并在这棵树上进行查询操作:给出两个结点编号x和y,求从x到y的路径上,由每个结点的权值构成的序列中的极差大小——要求,被减数要在减数的后面,即形成序列{a1,a2…aj …ak…an},求ak-aj (k>=j)的最大值. 求路径,显然用到lca. 太孤陋寡闻,才知道原来倍增dp能用来求LCA. 用p[u][i]表示结点u的第1<< i 个祖先结点,则有递推如下: for

Codeforces 1140G Double Tree 倍增 + dp

刚开始, 我以为两个点肯定是通过树上最短路径过去的, 无非是在两棵树之间来回切换, 这个可以用倍增 + dp 去维护它. 但是后来又发现, 它可以不通过树上最短路径过去, 我们考虑这样一种情况, 起点在奇树里面, 终点在偶树里面, 然后这两个点最短路径里面点到对应点的距离都很大, 这种情况下我们就需要从别的地方绕过去, 这样 就不是走树上最短路径了, 但是如果我们将对应点的距离更新成最短距离, 上面这个倍增 + dp的方法就可行了, 所以 我们可以用最短路去更新对应点之间的距离, 将它变成最小值

第七周 Leetcode 466. Count The Repetitions 倍增DP (HARD)

Leetcode 466 直接给出DP方程 dp[i][k]=dp[i][k-1]+dp[(i+dp[i][k-1])%len1][k-1]; dp[i][k]表示从字符串s1的第k位开始匹配2^k个s2串需要的长度 最后通过一个循环 累积最多可以匹配多少个s2串 除以n2下取整就是答案 用倍增加速后 总的复杂度nlogn 而本题的n非常小 轻松AC 体会到倍增的魅力了吧. const int maxn=100+1,INF=1e+9; long long int dp[maxn][30]; cl

【noip2018】【luogu5024】保卫王国

题目描述 Z 国有nn座城市,n - 1n−1条双向道路,每条双向道路连接两座城市,且任意两座城市 都能通过若干条道路相互到达. Z 国的国防部长小 Z 要在城市中驻扎军队.驻扎军队需要满足如下几个条件: 一座城市可以驻扎一支军队,也可以不驻扎军队. 由道路直接连接的两座城市中至少要有一座城市驻扎军队. 在城市里驻扎军队会产生花费,在编号为i的城市中驻扎军队的花费是p_ipi?. 小 Z 很快就规划出了一种驻扎军队的方案,使总花费最小.但是国王又给小 Z 提出 了mm个要求,每个要求规定了其中两

Dividing 多重背包 倍增DP

Dividing 给出n个物品的价值和数量,问是否能够平分. 1 #include <iostream> 2 #include <cstring> 3 #include <cmath> 4 #include <cstdio> 5 #include <algorithm> 6 #include <vector> 7 #include <string> 8 #include <map> 9 #include <

【题解】Luogu P3509 [POI 2010] ZAB-Frog 倍增dp

单调队列处理第k远的点 倍增跳点 滚(动数组)一维空间就能开下了 注意$m≤10^{18}$的读入 code 1 #include <bits/stdc++.h> 2 using namespace std; 3 namespace gengyf{ 4 #define ll long long 5 const int maxn=1e6+10; 6 inline ll read(){ 7 ll x=0,f=1; 8 char c=getchar(); 9 while(c<'0'||c>