P5024 保卫王国[倍增+dp]
窝当然不会ddp啦,要写这题当然是考虑优化裸dp啦,但是这题非常麻烦,于是变成了黑题。
首先,这个是没有上司的舞会模型,求图的带权最大独立集。
不考虑国王的限制条件,有
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\),有
\]
得到转移(cao)
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;
}
P5024 保卫王国[倍增+dp]的更多相关文章
- P5024 保卫王国(动态dp/整体dp/倍增dp)
做法(倍增) 最好写的一种 以下0为不选,1为选 \(f_{i,0/1}\)为\(i\)子树的最小值,\(g_{i,0/1}\)为除i子树外的最小值 \(fh_{i,j,0/1,0/1}\)为确定\( ...
- [倍增][换根DP]luogu P5024 保卫王国
题面 https://www.luogu.com.cn/problem/P5024 分析 可以对有限制的点对之间的链进行在倍增上的DP数组合并. 需要通过一次正向树形DP和一次换根DP得到g[0][i ...
- 【洛谷】P5024 保卫王国 (倍增)
前言 传送门 很多人写了题解了,我就懒得写了,推荐一篇博客 那就分享一下我的理解吧(说得好像有人看一样 对于每个点都只有选与不选两种情况,所以直接用倍增预处理出来两种情况的子树之内,子树之外的最值,最 ...
- luogu5024 [NOIp2018]保卫王国 (动态dp)
可以直接套动态dp,但因为它询问之间相互独立,所以可以直接倍增记x转移到fa[x]的矩阵 #include<bits/stdc++.h> #define CLR(a,x) memset(a ...
- [BZOJ5466][NOIP2018]保卫王国 倍增
题面 首先可以写一个暴力dp的式子,非常经典的树形dp \(dp[i][0]\)表示\(i\)这个点没有驻军,\(dp[i][1]\)就是有驻军,\(j\)是\(i\)的孩子.那么显然: \[ \be ...
- 【NOIP2018】保卫王国 动态dp
此题场上打了一个正确的$44pts$,接着看错题疯狂$rush$“正确”的$44pts$,后来没$rush$完没将之前的代码$copy$回去,直接变零分了..... 这一题我们显然有一种$O(nm)$ ...
- luoguP5024 保卫王国 动态dp
题目大意: emmmmm 题解: QAQ #include <cstdio> #include <cstring> #include <iostream> usin ...
- P5024 保卫王国
传送门 我现在还是不明白为什么NOIPd2t3会是一道动态dp-- 首先关于动态dp可以看这里 然后这里就是把把矩阵给改一改,改成这个形式\[\left[dp_{i-1,0},dp_{i-1,1}\r ...
- JZOJ5966. [NOIP2018TGD2T3] 保卫王国 (动态DP做法)
题目大意 这还不是人尽皆知? 有一棵树, 每个节点放军队的代价是\(a_i\), 一条边连接的两个点至少有一个要放军队, 还有\(q\)次询问, 每次规定其中的两个一定需要/不可放置军队, 问这样修改 ...
随机推荐
- 协程介绍, Greenlet模块,Gevent模块,Genvent之同步与异步
昨日内容回顾 I/O模型,面试会问到I/O操作,不占用CPU.它内部有一个专门的处理I/O模块.print和写log 属于I/O操作,它不占用CPU 线程GIL保证一个进程中的多个线程在同一时刻只有一 ...
- 最近公共祖先 LCA 递归非递归
给定一棵二叉树,找到两个节点的最近公共父节点(LCA).最近公共祖先是两个节点的公共的祖先节点且具有最大深度.假设给出的两个节点都在树中存在. dfs递归写法 查找两个node的最近公共祖先,分三种情 ...
- Java操作JSON数据(3)--fastjson操作JSON数据
fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean.本文介绍下fastjs ...
- 深度学习-DCGAN论文的理解笔记
训练方法DCGAN 的训练方法跟GAN 是一样的,分为以下三步: (1)for k steps:训练D 让式子[logD(x) + log(1 - D(G(z)) (G keeps still)]的值 ...
- DS 图解堆排
堆排其实就是选择排序,只不过用了完全二叉树特性. 堆排思想 : 利用完全二叉树特性建堆和重复选择调整来得到有序数组. 完全二叉树有什么特性呢? 节点左对齐 ---> 层序遍历不会出现空,可以用数 ...
- Source Insight4.0软件破解版
安装source insightt4.0 1.将下载好的sourceinsight4.exe替换安装在program file(x86)目录下的sourceinsight4.exe; 2.启动sour ...
- Go基础编程实践(三)—— 日期和时间
日期和时间 package main import ( "fmt" "time" ) func main() { // 获取当前时间 current := ti ...
- 《JAVA高并发编程详解》-Thread start方法的源码
Thread start方法的源码:
- Flanne
容器面临的问题 物理机A上的应用A看到的IP地址是容器A的,是172.17.0.2,在物理机B上的应用B看到的IP地址是容器B的,不巧也是172.17.0.2,当它们都注册到注册中心的时候,注册中心就 ...
- java基础 Math
package cn.mantishell.day08.demo04; /** * java.util.Math类是数学相关的工具类,里面提供类大量的静态方法,完成与数学运算相关的操作 * * pub ...