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])。
注意到B的部分分,可以想到每次修改只会对修改点到根的路径上的点的dp值产生影响。
考虑如何优化修改路径这一过程,先看只修改一个点的情况。
由于每次修改并非累积,事实上应该考虑一种预处理来快速得到答案,并且发现其实最终我们只需要f[root][]。容易想到倍增。设f[x][k][0/1][0/1]表示x号点为0/1时其2k级祖先为0/1时这条链上的答案,即其2k级祖先的子树-x的子树的答案。这个东西本身就是可减的,即知道了在x号点子树内的y点选/不选的情况下x子树的答案、y号点选/不选的情况下y子树的答案,将其相减就是x子树去掉y子树的答案。
倍增数组并不难求,显然我们已经有f[x][0][][],在2k-1级祖先那里合并得到2k级的答案,考虑2k-1级祖先选还是不选取个min即可,大约就是floyd/矩乘。回答询问同样也是类似的很正常的倍增。那么只改一个点就能做了。
再考虑改两个点,其实基本类似。两个点倍增求出到他们的lca下方一个点的答案,以此更新lca答案,再从lca倍增跳到根即可。对其中一点是另一点祖先的情况最好特判。听起来不是很复杂但写起来得考虑清楚。
另一种做法是ddp,暂时觉得不太学的动,好像也很久没学新姿势了。感觉倍增做法看上去还是比较noip的,虽然考场上被神仙t2和完全没碰过但知道能做这个题的ddp冲昏头脑肯定想不出来。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define inf 100000000000ll
#define N 100010
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<''||c>'')) c=getchar();return c;}
int gcd(int n,int m){return m==?n:gcd(m,n%m);}
int read()
{
int x=,f=;char c=getchar();
while (c<''||c>'') {if (c=='-') f=-;c=getchar();}
while (c>=''&&c<='') x=(x<<)+(x<<)+(c^),c=getchar();
return x*f;
}
int n,m,a[N],p[N],fa[N][],deep[N],t;
ll f[N][],g[N][][][];
struct data{int to,nxt,len;
}edge[N<<];
struct data2{ll x,y;int id;};
void addedge(int x,int y){t++;edge[t].to=y,edge[t].nxt=p[x],p[x]=t;}
void dfs(int k)
{
f[k][]=,f[k][]=a[k];
for (int i=p[k];i;i=edge[i].nxt)
if (edge[i].to!=fa[k][])
{
deep[edge[i].to]=deep[k]+;
fa[edge[i].to][]=k;
dfs(edge[i].to);
f[k][]+=f[edge[i].to][];
f[k][]+=min(f[edge[i].to][],f[edge[i].to][]);
}
for (int i=p[k];i;i=edge[i].nxt)
if (edge[i].to!=fa[k][])
{
g[edge[i].to][][][]=inf;
g[edge[i].to][][][]=f[k][]-min(f[edge[i].to][],f[edge[i].to][]);
g[edge[i].to][][][]=f[k][]-f[edge[i].to][];
g[edge[i].to][][][]=f[k][]-min(f[edge[i].to][],f[edge[i].to][]);
}
}
void pre()
{
for (int j=;j<;j++)
{
for (int i=;i<=n;i++)
fa[i][j]=fa[fa[i][j-]][j-];
for (int i=;i<=n;i++)
for (int x=;x<;x++)
for (int y=;y<;y++)
g[i][j][x][y]=min(g[i][j-][x][]+g[fa[i][j-]][j-][][y],g[i][j-][x][]+g[fa[i][j-]][j-][][y]);
}
}
int lca(int x,int y)
{
if (deep[x]<deep[y]) swap(x,y);
for (int j=;~j;j--) if (deep[fa[x][j]]>=deep[y]) x=fa[x][j];
if (x==y) return x;
for (int j=;~j;j--) if (fa[x][j]!=fa[y][j]) x=fa[x][j],y=fa[y][j];
return fa[x][];
}
data2 query(int x,int root,int isup,ll tx,ll ty)
{
data2 u;u.x=tx,u.y=ty;
for (int j=;~j;j--)
if (deep[fa[x][j]]>deep[root])
{
tx=u.x,ty=u.y;
u.x=min(tx+g[x][j][][],ty+g[x][j][][]);
u.y=min(tx+g[x][j][][],ty+g[x][j][][]);
x=fa[x][j];
}
u.id=x;
if (isup)
{
tx=u.x,ty=u.y;
u.x=min(tx+g[x][][][],ty+g[x][][][]);
u.y=min(tx+g[x][][][],ty+g[x][][][]);
}
return u;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("bzoj5466.in","r",stdin);
freopen("bzoj5466.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
n=read(),m=read();read();
for (int i=;i<=n;i++) a[i]=read();
for (int i=;i<n;i++)
{
int x=read(),y=read();
addedge(x,y),addedge(y,x);
}
fa[][]=;dfs();
pre();
while (m--)
{
int x=read(),opx=read(),y=read(),opy=read();
if (deep[x]<deep[y]) swap(x,y),swap(opx,opy);
if (opx==&&opy==&&fa[x][]==y) {printf("-1\n");continue;}
int k=lca(x,y);
if (k==y)
{
data2 u=query(x,k,,opx==?f[x][]:inf,opx==?f[x][]:inf);
ll tx=u.x,ty=u.y;
u.x=f[k][]+ty-f[u.id][];
u.y=f[k][]+min(tx,ty)-min(f[u.id][],f[u.id][]);
if (opy==) u.y=inf;else u.x=inf;
if (k!=) u=query(k,,,u.x,u.y);
printf(LL,min(u.x,u.y));
}
else
{
data2 u=query(x,k,,opx==?f[x][]:inf,opx==?f[x][]:inf);
data2 v=query(y,k,,opy==?f[y][]:inf,opy==?f[y][]:inf);
data2 w;
w.x=f[k][]+u.y-f[u.id][]+v.y-f[v.id][];
w.y=f[k][]+min(u.x,u.y)-min(f[u.id][],f[u.id][])+min(v.x,v.y)-min(f[v.id][],f[v.id][]);
w=query(k,,,w.x,w.y);
printf(LL,min(w.x,w.y));
}
}
return ;
}
BZOJ5466 NOIP2018保卫王国(倍增+树形dp)的更多相关文章
- [BZOJ5466][NOIP2018]保卫王国 倍增
题面 首先可以写一个暴力dp的式子,非常经典的树形dp \(dp[i][0]\)表示\(i\)这个点没有驻军,\(dp[i][1]\)就是有驻军,\(j\)是\(i\)的孩子.那么显然: \[ \be ...
- [NOIP2018]保卫王国(树形dp+倍增)
我的倍增解法吊打动态 \(dp\) 全局平衡二叉树没学过 先讲 \(NOIP\) 范围内的倍增解法. 我们先考虑只有一个点取/不取怎么做. \(f[x][0/1]\) 表示取/不取 \(x\) 后,\ ...
- NOIP2018 保卫王国(动态DP)
题意 求最小权值点覆盖. mmm次询问,每次给出两个点,分别要求每个点必须选或必须不选,输出每次的最小权值覆盖或者无解输出−1-1−1 题解 强制选或者不选可以看做修改权值为±∞\pm\infin±∞ ...
- 2019.02.16 bzoj5466: [Noip2018]保卫王国(链分治+ddp)
传送门 题意简述: mmm次询问,每次规定两个点必须选或者不选,求树上的带权最小覆盖. 思路: 考虑链分治+ddpddpddp 仍然是熟悉的套路,先考虑没有修改的状态和转移: 令fi,0/1f_{i, ...
- 竞赛题解 - NOIP2018 保卫王国
\(\mathcal{NOIP2018}\) 保卫王国 - 竞赛题解 按某一个炒鸡dalao名曰 taotao 的话说: \(\ \ \ \ \ \ \ \ \ "一道sb倍增题" ...
- 【NOIP 2018】保卫王国(动态dp / 倍增)
题目链接 这个$dark$题,嗯,不想说了. 法一:动态$dp$ 虽然早有听闻动态$dp$,但到最近才学,如果你了解动态$dp$,那就能很轻松做出这道题了.故利用这题在这里科普一下动态$dp$的具体内 ...
- [NOIP2018]保卫王国 题解
NOIP2018提高组D2T3 ddp虽然好想,但是码量有点大(其实是我不会),因此本文用倍增优化树形DP来解决本题. 题意分析 给一棵树染色,每个节点染色需要一定的花费,要求相邻两个节点至少要有一个 ...
- NOIP2018保卫王国
题目大意:给一颗有点权的树,每次规定两个点选还是不选,求这棵树的最小权点覆盖. 题解 ZZ码农题. 要用动态dp做,这题就是板子,然鹅并不会,留坑代填. 因为没有修改,所以可以静态倍增. 我们先做一遍 ...
- luogu5024 [NOIp2018]保卫王国 (动态dp)
可以直接套动态dp,但因为它询问之间相互独立,所以可以直接倍增记x转移到fa[x]的矩阵 #include<bits/stdc++.h> #define CLR(a,x) memset(a ...
随机推荐
- 问题:MongoDB C# driver异常:Truncation resulted in data loss
问题描述: 原因分析: MongoDB C#驱动在读取数据记录遇到数值类型字段时,如果没有设置允许截断,将抛出TruncationException. 解决方法: [BsonRepresentatio ...
- 开胃小菜——impress.js代码详解
README 友情提醒,下面有大量代码,由于网页上代码显示都是同一个颜色,所以推荐大家复制到自己的代码编辑器中看. 今天闲来无事,研究了一番impress.js的源码.由于之前研究过jQuery,看i ...
- java 二叉树的创建 遍历
本来说复习一下BFS和DFS,辗转就来到了二叉树...本文包括二叉树的创建和遍历 概念 数据:1 2 3 4 5 6 7生成一颗二叉树 上面的数是数据,不是位置,要区别一下数据和位置 红色的代表位置, ...
- lesson 19 A very dear cat
lesson 19 A very dear cat dear adj. 亲爱的:尊敬的:昂贵的 表示几乎不,很少的词语 rarely hardly seldom scarcely flat = apa ...
- 小组ITalk网站开发中使用到的一些技巧
----->Display属性和Visibility属性:一个清除内容和框体,另一个只清除内容而保留窗体: $('#abc').css({ 'font-size' : '12px', '-web ...
- Memcached命令-存储命令-查找命令-清理命令
Memcached set 存储命令 Memcached set 命令用于将 value(数据值) 存储在指定的 key(键) 中. 如果set的key已经存在,该命令可以更新该key所对应的原来的数 ...
- JDK源码分析:Object.java
一. 序言 Object.java是一切类的基类,所以了解该类有一定的必要 二 .属性及方法分析 方法列表: private static native void registerNatives(); ...
- [Clr via C#读书笔记]Cp6类型和成员基础
Cp6类型和成员基础 成员 常量:字段(静态字段和实例字段):实例构造器:类型构造器(用于静态字段的构造):方法(静态方法和实例方法):操作符重载(本质是一个方法):转换操作符:属性(本质还是方法): ...
- 线性代数之——正交矩阵和 Gram-Schmidt 正交化
这部分我们有两个目标.一是了解正交性是怎么让 \(\hat x\) .\(p\) .\(P\) 的计算变得简单的,这种情况下,\(A^TA\) 将会是一个对角矩阵.二是学会怎么从原始向量中构建出正交向 ...
- Java并发简介
年轻的时候学会了“使用”Servlet后,感觉自己什么都会做了,之后就不停的写所谓的业务逻辑,框架(这里说的不是structs,spring等,就是说servlet)给人们屏蔽了很多复杂性(更别说构建 ...