[BZOJ5466][NOIP2018]保卫王国 倍增
首先可以写一个暴力dp的式子,非常经典的树形dp
\(dp[i][0]\)表示\(i\)这个点没有驻军,\(dp[i][1]\)就是有驻军,\(j\)是\(i\)的孩子。那么显然:
dp[i][0]&=dp[j][1]\\
dp[i][1]&=\min\{dp[j][0],dp[j][1]\}
\end{align*}
\]
然后我们发现,对于一个孩子\(j\),它的转移与其他孩子无关。也就是其他孩子的值对他没有影响。
这样的性质决定了这道题目的可倍增性。
在说到倍增前,我们再做个预处理,设\(h[i][0/1]\)分别表示无或有驻军的时候,除了\(i\)的子树外的最优值。那么有
h[j][0]&=h[i][1]+dp[i][1]-\min\{dp[j][0],dp[j][1]\}\\
h[j][1]&=\min\{h[i][0]+dp[i][0]-dp[j][1]\ ,\ h[i][1]+dp[i][1]-\min\{dp[j][0],dp[j][1]\}\}
\end{align*}
\]
下面,我们再回去看一下题目。可以发现,每一次会影响到的点,只在\(x\)到\(y\)的路径上还有在\(lca\)到跟的路径上。
对于其它点,根据我们之前的结论,对\(x\)与\(y\)的操作,与它们无关。所以我们只需要搞定会影响到的点。
我们考虑先把路径外的点得该用的值先做出来。对于树上的路径,倍增是很实用的。
定义\(f[i][j]\)表示\(i\)的第\(2^j\)层祖先,\(p[i][j][0/1][0/1]\)表示\(i\)取不取,那个祖先取不取的不计算上\(i\)的最优解。
初始化:
f[i][0]&=fa\\
p[i][0][0][0]&=INF\\
p[i][0][1][0]&=dp[fa][0]-dp[i][1]\\
p[i][0][0][1]&=dp[fa][1]-\min\{dp[i][0],dp[i][1]\}\\
p[i][0][1][1]&=dp[fa][1]-\min\{dp[i][0],dp[i][1]\}
\end{align*}
\]
转移的话找相同的中间值转移就可以了。
f[i][j]&=f[f[i][j-1]][j-1]\\
p[i][j][0][0]&=\min\{p[i][j-1][0][0]+p[f[i][j-1]][j-1][0][0],p[i][j-1][0][1]+p[f[i][j-1]][j-1][1][0]\}\\
p[i][j][0][1]&=\min\{p[i][j-1][0][0]+p[f[i][j-1]][j-1][0][1],p[i][j-1][0][1]+p[f[i][j-1]][j-1][1][1]\}\\
p[i][j][1][0]&=\min\{p[i][j-1][1][0]+p[f[i][j-1]][j-1][0][0],p[i][j-1][1][1]+p[f[i][j-1]][j-1][1][0]\}\\
p[i][j][1][1]&=\min\{p[i][j-1][1][0]+p[f[i][j-1]][j-1][0][1],p[i][j-1][1][1]+p[f[i][j-1]][j-1][1][1]\}
\end{align*}
\]
然后处理询问的时候,设置\(x0,x1,y0,y1\)几个值,存下到目前的\(x,y\)分别选/不选的答案。
如果被禁止了选还是不选,那么就设置对应的值为\(INF\)
也还是找相同的中间值。这里跟这个差不多,就略去转移过程。
为什么这样是对的呢?
我们来看一下一种情况的不可能性。
倍增一条从上到下的链,中间虽然在不考虑最下面那个点的时候通过\(0\)转移,但是考虑上\(i\)以后通过\(1\)转移。
这就给归功于我们限制了最底下的点的选择类型了。只要限制下了最底下的选择类型,那么我们的计入这个点和不计入的话每一个状态的差值都是一样的,也就是说相对大小也是一样的。因此,转移的方式也是不变的。
如果上面不太好理解的话,这里有一个比较抽象的理解方式:
如果给了一条链,告诉你某一个边缘上的点选和不选时候的两个答案,那么我们不管dp的过程,直接想一想,可以直接对应的拼上去,绝对是不会错的。
#include<cstdio>
#include<algorithm>
#include<cstring>
#define REP(i,a,n) for(register int i(a);i<=(n);++i)
#define PER(i,a,n) for(register int i(a);i>=(n);--i)
#define FEC(i,x,y) for(register int i=head[x],y=g[i].to;i;i=g[i].ne,y=g[i].to)
#define dbg(...) fprintf(stderr,__VA_ARGS__)
const int SZ=(1<<21)+1;char ibuf[SZ],*iS,*iT,obuf[SZ+128],*oS=obuf,*oT=obuf+SZ-1;
#ifdef ONLINE_JUDGE
#define gc() (iS==iT?(iT=(iS=ibuf)+fread(ibuf,1,SZ,stdin),(iS==iT?EOF:*iS++)):*iS++)
#else
#define gc() getchar()
#endif
template<typename I>inline void read(I&x){char c=gc();int f=0;for(;c<'0'||c>'9';c=gc())c=='-'?f=1:0;for(x=0;c>='0'&&c<='9';c=gc())x=(x<<1)+(x<<3)+(c&15);f?x=-x:0;}
inline void flush(){fwrite(obuf,1,oS-obuf,stdout);oS=obuf;}
#define printf(...) (oS>oT&&(flush(),1),oS+=sprintf(oS,__VA_ARGS__))
template<typename A,typename B>inline char SMAX(A&a,const B&b){return a<b?a=b,1:0;}
template<typename A,typename B>inline char SMIN(A&a,const B&b){return a>b?a=b,1:0;}
typedef long long ll;typedef unsigned long long ull;typedef std::pair<int,int>pii;
const int N=100000+7,LOG=18;const ll INF=0x3f3f3f3f3f3f3f3f;
int n,m,x,y,ha,hb,a[N],f[N][LOG],dep[N];ll dp[N][2],h[N][2],p[N][LOG][2][2],abc;//错误笔记:要开ll
struct Edge{int to,ne;}g[N<<1];int head[N],tot;
inline void Addedge(int x,int y){g[++tot].to=y;g[tot].ne=head[x];head[x]=tot;}
inline void DP(int x,int fa=0){
dp[x][0]=0;dp[x][1]=a[x];f[x][0]=fa;dep[x]=dep[fa]+1;
FEC(i,x,y)if(y!=fa)DP(y,x),dp[x][0]+=dp[y][1],dp[x][1]+=std::min(dp[y][0],dp[y][1]);
}
inline void DP2(int x,int fa=0){
FEC(i,x,y)if(y!=fa)h[y][0]=h[x][1]+dp[x][1]-std::min(dp[y][0],dp[y][1]),//错误笔记:还有一个小错误,因为这里用的是刷表转移的,所以下一行中没注意把x,y打混掉了。
h[y][1]=std::min(h[x][0]+dp[x][0]-dp[y][1],h[x][1]+dp[x][1]-std::min(dp[y][0],dp[y][1])),DP2(y,x);//错误笔记:把dp打成f 这不是最重要的,最重要的是要先更新数组再递归!!!
}
inline void Preprocess(){
REP(i,1,n){
p[i][0][0][0]=INF;
p[i][0][1][0]=dp[f[i][0]][0]-dp[i][1];
p[i][0][1][1]=p[i][0][0][1]=dp[f[i][0]][1]-std::min(dp[i][0],dp[i][1]);//错误笔记:i,x不分
}
REP(j,1,LOG-1)REP(i,1,n)f[i][j]=f[f[i][j-1]][j-1],
p[i][j][0][0]=std::min(p[i][j-1][0][0]+p[f[i][j-1]][j-1][0][0],p[i][j-1][0][1]+p[f[i][j-1]][j-1][1][0]),
p[i][j][0][1]=std::min(p[i][j-1][0][0]+p[f[i][j-1]][j-1][0][1],p[i][j-1][0][1]+p[f[i][j-1]][j-1][1][1]),
p[i][j][1][0]=std::min(p[i][j-1][1][0]+p[f[i][j-1]][j-1][0][0],p[i][j-1][1][1]+p[f[i][j-1]][j-1][1][0]),
p[i][j][1][1]=std::min(p[i][j-1][1][0]+p[f[i][j-1]][j-1][0][1],p[i][j-1][1][1]+p[f[i][j-1]][j-1][1][1]);
}
inline ll Solve(int x,int a,int y,int b){
if(dep[x]<dep[y])std::swap(x,y),std::swap(a,b);
ll x0=INF,x1=INF,y0=INF,y1=INF,tx0,ty0,tx1,ty1;
if(!a)x0=dp[x][0];else x1=dp[x][1];
if(!b)y0=dp[y][0];else y1=dp[y][1];
PER(i,LOG-1,0)if(dep[f[x][i]]>=dep[y])tx0=std::min(x0+p[x][i][0][0],x1+p[x][i][1][0]),SMIN(tx0,INF),
tx1=std::min(x0+p[x][i][0][1],x1+p[x][i][1][1]),SMIN(tx1,INF),
x0=tx0,x1=tx1,x=f[x][i];
if(x==y)return (b?x1:x0)+h[y][b];
PER(i,LOG-1,0)if(f[x][i]!=f[y][i])tx0=std::min(x0+p[x][i][0][0],x1+p[x][i][1][0]),SMIN(tx0,INF),
tx1=std::min(x0+p[x][i][0][1],x1+p[x][i][1][1]),SMIN(tx1,INF),
ty0=std::min(y0+p[y][i][0][0],y1+p[y][i][1][0]),SMIN(ty0,INF),
ty1=std::min(y0+p[y][i][0][1],y1+p[y][i][1][1]),SMIN(ty1,INF),
x0=tx0,x1=tx1,x=f[x][i],y0=ty0,y1=ty1,y=f[y][i];
return std::min(std::min(h[f[x][0]][0]-dp[x][1]-dp[y][1]+dp[f[x][0]][0]+x1+y1,(ll)h[f[x][0]][1]-std::min(dp[x][0],dp[x][1])-std::min(dp[y][0],dp[y][1])+dp[f[x][0]][1]+std::min(x0,x1)+std::min(y0,y1)),(ll)INF);//错误笔记:防止加的过程中会爆ll
}
int main(){
read(n),read(m);while(gc()!='\n');
REP(i,1,n)read(a[i]);
REP(i,1,n-1)read(x),read(y),Addedge(x,y),Addedge(y,x);
DP(1);Preprocess();DP2(1);
REP(i,1,m)read(x),read(ha),read(y),read(hb),abc=Solve(x,ha,y,hb),printf("%lld\n",abc>=INF?-1:abc);
return flush(),0;
}
[BZOJ5466][NOIP2018]保卫王国 倍增的更多相关文章
- BZOJ5466 NOIP2018保卫王国(倍增+树形dp)
暴力dp非常显然,设f[i][0/1]表示i号点不选/选时i子树内的答案,则f[i][0]=Σf[son][1],f[i][1]=a[i]+Σmin(f[son][0],f[son][1]). 注意到 ...
- 2019.02.16 bzoj5466: [Noip2018]保卫王国(链分治+ddp)
传送门 题意简述: mmm次询问,每次规定两个点必须选或者不选,求树上的带权最小覆盖. 思路: 考虑链分治+ddpddpddp 仍然是熟悉的套路,先考虑没有修改的状态和转移: 令fi,0/1f_{i, ...
- 竞赛题解 - NOIP2018 保卫王国
\(\mathcal{NOIP2018}\) 保卫王国 - 竞赛题解 按某一个炒鸡dalao名曰 taotao 的话说: \(\ \ \ \ \ \ \ \ \ "一道sb倍增题" ...
- [NOIP2018]保卫王国(树形dp+倍增)
我的倍增解法吊打动态 \(dp\) 全局平衡二叉树没学过 先讲 \(NOIP\) 范围内的倍增解法. 我们先考虑只有一个点取/不取怎么做. \(f[x][0/1]\) 表示取/不取 \(x\) 后,\ ...
- [NOIP2018]保卫王国 题解
NOIP2018提高组D2T3 ddp虽然好想,但是码量有点大(其实是我不会),因此本文用倍增优化树形DP来解决本题. 题意分析 给一棵树染色,每个节点染色需要一定的花费,要求相邻两个节点至少要有一个 ...
- luogu5024 [NOIp2018]保卫王国 (动态dp)
可以直接套动态dp,但因为它询问之间相互独立,所以可以直接倍增记x转移到fa[x]的矩阵 #include<bits/stdc++.h> #define CLR(a,x) memset(a ...
- NOIP2018保卫王国
题目大意:给一颗有点权的树,每次规定两个点选还是不选,求这棵树的最小权点覆盖. 题解 ZZ码农题. 要用动态dp做,这题就是板子,然鹅并不会,留坑代填. 因为没有修改,所以可以静态倍增. 我们先做一遍 ...
- [NOIP2018]保卫王国
嘟嘟嘟 由于一些知道的人所知道的,不知道的人所不知道的原因,我来发NOIP2018day2T3的题解了. (好像我只是个搬运工--) 这题真可以叫做NOIplus了,跟其他几道比较水的题果然不一样,无 ...
- P5024 保卫王国[倍增+dp]
窝当然不会ddp啦,要写这题当然是考虑优化裸dp啦,但是这题非常麻烦,于是变成了黑题. 首先,这个是没有上司的舞会模型,求图的带权最大独立集. 不考虑国王的限制条件,有 \[ dp[x][0]+=dp ...
随机推荐
- POJ 3259 Wormholes ( SPFA判断负环 && 思维 )
题意 : 给出 N 个点,以及 M 条双向路,每一条路的权值代表你在这条路上到达终点需要那么时间,接下来给出 W 个虫洞,虫洞给出的形式为 A B C 代表能将你从 A 送到 B 点,并且回到 C 个 ...
- 写一下SPFA和迪杰斯特拉的模版。。。第一次写博客,有错请提出哦!
SPFA的模版 #include<bits/stdc++.h> using namespace std; queue <int> q; typedef pair <int ...
- ubuntu 18.04设置系统自带系统截图快捷键
0.前言 ubuntu 18.04自带一个截图工具gnome-screenshot,有三种模式,全屏截图.当前活动窗口截图.选取活动区域截图 1.设置快捷键 Setting->Devices-& ...
- Xcode编辑器之快捷键的使用
一,快捷键图标 图标 键盘 ⌘ Command ⌃ Control ⌥ Option ⇧ Shift 二, 常用快捷键 文件快捷键 快捷键 键盘 描述 ⌘N command + N 新文件 ⇧⌘N ...
- 【转】iis解决应用程序池**提供服务的进程意外终止进程ID是**。进程退出代码是'0x80'
转自:http://blog.sina.com.cn/s/blog_56a68d5501013xdd.html 我们公司旗下的红黑互联会遇到这种问题 事件类型: 警告事件来源: W3SVC事件种类: ...
- UE4-PS4开发渲染线程优化方法及记录
先说方法: Launch 到 PS4 Devkit上,在PS4上输入Stat unit 看瓶颈在哪里.我们发现Frame 和Draw数值几乎一样,其余两项相对较小,这表明瓶颈在渲染线程上. 关于渲染线 ...
- 使用CFStringTransform将汉字转换为拼音
之前做通讯录相关的一些App时,有一个比较常用的算法是将汉字转换成拼音.当时采用的做法是:将各个拼音段的首个汉字(按Unicode排序)做成两个数组,一个数组存拼音,另一个数组存拼音对应首个汉字的Un ...
- XX-Net 使用教程(Across the Great Wall)
注意: 由于封锁严重,软件自带IP已经被封杀殆尽.因此需要数分钟到数小时的初始化IP扫描,方能正常运行. 虽然系统内置了公共appid, 还是建议部署自己的appid,公共appid限制看视频.需要注 ...
- html简单标签代码
html简单标签代码demo <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "ht ...
- 组建MySQL集群的几种方案
组建MySQL集群的几种方案LVS+Keepalived+MySQL(有脑裂问题?但似乎很多人推荐这个)DRBD+Heartbeat+MySQL(有一台机器空余?Heartbeat切换时间较长?有脑裂 ...