【NOIP2018提高组 day2】保卫王国

在这里插入图片描述
在这里插入图片描述

分析

树形DPlca树上倍增

问题转换为:
一棵树上,可以花代价把P[I]代价选取顶,要求任2个相邻点至少有1个被选取。给出m组询问,每次强制两个点的状态(选/不选),求出每次的最小花费。

用dp[0/1][u]表示点u不选/选,以u为根节点的子树的答案的最小值。

设v为u的一个儿子,就得到一个状态转移方程:
dp[0][u]=∑dp[1][v]
dp[1][u]=∑min{dp[0][v],dp[1][v]}.
(因为相邻两点至少选取一个)
对于每次强制不选的点,dp[][]=inf (定义的一个极大值)

设 f[0/1][0/1][i][u] 表示u不选/选,u往上跳2^i步的祖先不选/选时,从u开始转移到那个祖先的答案。

dfs预处理(可用类似lca树上倍增的方法)dp数组和f数组(倍增数组)后,开始处理每次询问:
对于节点u,v:
1.将深度大的点u往v的深度跳(类似ca树上倍增)同时更新dp的值;
2.u,v同时往上跳至lca的儿子处同时更新新的dp值;
3.得到lca的dp值,再往上跳至根节点的儿子处同时更新新的dp值;
(一句话概括从下至上更新dp[][]和f[][][][])
4.讨论根节点的状态得出最终答案。

代码如下

#include<bits/stdc++.h>
typedef long long ll;
const ll inf=1e15;
using namespace std;
const int N=100010;
ll tot,head[N];
ll n,m,a[N],fa[20][N],deep[N],Log[N];
ll dp[3][N],f[3][3][20][N];
struct edge{
int ver,to;
}e[N*2]; 
void add(ll x,ll y){
     e[++tot].ver =y;
     e[tot].to =head[x];
    head[x]=tot;
}
void dfs(ll x){
    deep[x]=deep[fa[0][x]]+1;
    dp[1][x]=a[x];
    f[0][0][0][x]=inf;
    for(ll i=1; (1<<(i-1))<deep[x]; i++) fa[i][x]=fa[i-1][fa[i-1][x]];
    for(ll i=head[x]; i; i=e[i].to){
        ll y=e[i].ver;
        if(y!=fa[0][x]){
            fa[0][y]=x;
            dfs(y);
            dp[0][x]+=dp[1][y];
            dp[1][x]+=min(dp[0][y],dp[1][y]);
        }
    }
}
const ll cur[18]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072};
void pre(ll x){
    f[1][0][0][x]=dp[0][fa[0][x]]-dp[1][x],
    f[0][1][0][x]=f[1][1][0][x]=dp[1][fa[0][x]]-min(dp[0][x],dp[1][x]);
    for(ll i=1; cur[i]<deep[x]; i++){
        ll y=fa[i-1][x];
        f[0][0][i][x]=min(f[0][0][i-1][x]+f[0][0][i-1][y],f[0][1][i-1][x]+f[1][0][i-1][y]),
        f[0][1][i][x]=min(f[0][0][i-1][x]+f[0][1][i-1][y],f[0][1][i-1][x]+f[1][1][i-1][y]),
        f[1][0][i][x]=min(f[1][0][i-1][x]+f[0][0][i-1][y],f[1][1][i-1][x]+f[1][0][i-1][y]),
        f[1][1][i][x]=min(f[1][0][i-1][x]+f[0][1][i-1][y],f[1][1][i-1][x]+f[1][1][i-1][y]);
    }
    for(ll i=head[x]; i; i=e[i].to){
        if(e[i].ver!=fa[0][x]) pre(e[i].ver);
    }
}

void lca(ll u,ll x,ll v,ll y){
    if(deep[u]<deep[v]){
        swap(u,v);
        swap(x,y);
    }
    ll L,u0=inf,u1=inf,v0=inf,v1=inf,l0=inf,l1=inf,ans;
    if(x)u1=dp[1][u];
	else u0=dp[0][u];
    if(y)v1=dp[1][v];
	else v0=dp[0][v];
    for(ll i=Log[deep[u]-deep[v]]; i>=0; i--){
        if(deep[u]-cur[i]>=deep[v]){
            ll t0=u0,t1=u1;
            u0=min(t0+f[0][0][i][u],t1+f[1][0][i][u]);
            u1=min(t0+f[0][1][i][u],t1+f[1][1][i][u]);
            u=fa[i][u];
        }   
    }

    if(u==v){
        L=u;
        if(y)l1=u1;
		else l0=u0;
    }
    else{
        for(ll i=Log[deep[u]-1]; i>=0; i--){
            if(fa[i][u]!=fa[i][v]){
                ll t0=u0,t1=u1,p0=v0,p1=v1;
                u0=min(t0+f[0][0][i][u],t1+f[1][0][i][u]);
                u1=min(t0+f[0][1][i][u],t1+f[1][1][i][u]);
                v0=min(p0+f[0][0][i][v],p1+f[1][0][i][v]);
                v1=min(p0+f[0][1][i][v],p1+f[1][1][i][v]);
                u=fa[i][u]; 
				v=fa[i][v];
            }
        } 
        L=fa[0][u];
        l0=dp[0][L]-dp[1][u]-dp[1][v]+u1+v1;
        l1=dp[1][L]-min(dp[0][u],dp[1][u])-min(dp[0][v],dp[1][v])+min(u0,u1)+min(v0,v1);
    }
    if(L==1) ans=min(l0,l1);
    else{
        for(ll i=Log[deep[L]-2]; i>=0; i--){
            if(deep[L]-cur[i]>1){
                ll t0=l0,t1=l1;
                l0=min(t0+f[0][0][i][L],t1+f[1][0][i][L]);
                l1=min(t0+f[0][1][i][L],t1+f[1][1][i][L]);
                L=fa[i][L];
            }   
        } 
        ans=min(dp[0][1]-dp[1][L]+l1,dp[1][1]-min(dp[0][L],dp[1][L])+min(l0,l1));
    }
    printf("%lld\n",ans<inf?ans:-1);
}
ll read(){
	ll sum=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		sum=(sum<<3)+(sum<<1)+ch-'0';
		ch=getchar();
	}
	return sum*f;
}
int main(){
   string s;
   n=read();
   m=read();
    cin>>s;
    for(ll i=1; i<=n; i++) a[i]=read();
    for(ll i=1; i<n; i++){
        ll x,y;
        scanf("%lld %lld",&x,&y);
        add(x,y);
        add(y,x);
        Log[i]=Log[i>>1]+1;
    }
    dfs(1);
    pre(1);
    while(m--){
        ll u,x,v,y;
        u=read();
        x=read();
        v=read();
        y=read();
        lca(u,x,v,y);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值