NOIp2018 D2T3 defense——树上倍增
题目:https://www.luogu.org/problemnew/show/P5024
考场上只会写n,m<=2000的暴力,还想了想A1和A2的情况,不过好像只得了A1的分。然后仔细一看,原来是把dp2[ ][ ]写成dp[ ][ ]了。改一下,就能得到A1和A2的分。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e5+;
const ll INF=1e10+;
int n,m,p[N],hd[N],xnt,to[N<<],nxt[N<<];
int q0,q1,f0,f1;
ll dp[N][],dp2[N][];
char ch[];
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='') ret=(ret<<)+(ret<<)+ch-'',ch=getchar();
return fx?ret:-ret;
}
void add(int x,int y)
{
to[++xnt]=y; nxt[xnt]=hd[x]; hd[x]=xnt;
}
ll Mn(ll a,ll b){return a<b?a:b;}
ll Mx(ll a,ll b){return a>b?a:b;}
void dfs(int cr,int fa)
{
dp[cr][]=; dp[cr][]=p[cr];
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa)
{
dfs(v,cr);
dp[cr][]+=dp[v][];
dp[cr][]+=Mn(dp[v][],dp[v][]);
}
if(cr==q0)dp[cr][!f0]=INF;
if(cr==q1)dp[cr][!f1]=INF;
}
bool chk()
{
bool flag=;
for(int i=hd[q0];i;i=nxt[i])
if(to[i]==q1){flag=;break;}
if(flag&&!f0&&!f1)
{
puts("-1");return true;
}
return false;
}
void solve1()
{
for(int i=;i<=m;i++)
{
q0=rdn();f0=rdn();q1=rdn();f1=rdn();
if(chk())continue;
dfs(,);
printf("%lld\n",Mn(dp[][],dp[][]));
}
}
void solve2()
{
dp[][]=p[]; dp[][]=INF;
for(int i=;i<=n;i++)
{
dp[i][]=Mn(dp[i-][],dp[i-][])+p[i];
dp[i][]=dp[i-][];
}
dp2[n][]=p[n]; dp2[n][]=;
for(int i=n-;i;i--)
{
dp2[i][]=Mn(dp2[i+][],dp2[i+][])+p[i];
dp2[i][]=dp2[i+][];
}
for(int i=;i<=m;i++)
{
q0=rdn();f0=rdn();q1=rdn();f1=rdn();
if(chk())continue;
printf("%lld\n",dp[q1][f1]+dp2[q1][f1]-(f1?p[q1]:));
}
}
void solve3()
{
dp[][]=p[]; dp[][]=;
for(int i=;i<=n;i++)
{
dp[i][]=Mn(dp[i-][],dp[i-][])+p[i];
dp[i][]=dp[i-][];
}
dp2[n][]=p[n]; dp2[n][]=;
for(int i=n-;i;i--)
{
dp[i][]=Mn(dp[i+][],dp[i+][])+p[i];
dp[i][]=dp[i+][];
}
for(int i=;i<=m;i++)
{
q0=rdn();f0=rdn();q1=rdn();f1=rdn();
if(chk())continue;
if(q0>q1)swap(q0,q1),swap(f0,f1);
printf("%lld\n",dp[q0][f0]+dp2[q1][f1]);
}
}
int main()
{
freopen("defense.in","r",stdin);
freopen("defense.out","w",stdout);
n=rdn();m=rdn();scanf("%s",ch+);
for(int i=;i<=n;i++)p[i]=rdn();
for(int i=,u,v;i<n;i++)
{
u=rdn(); v=rdn(); add(u,v); add(v,u);
}
if(n<=)solve1();
else if(ch[]=='A'&&ch[]=='')solve2();
else if(ch[]=='A'&&ch[]=='')solve3();
else solve1();
return ;
}
然后得知A的分好像就是一个线段树。想一想,就记录一下该区间两端的是0还是1就行了。
正解的一种是倍增。
先做出正常的dp[ ][ 0/1 ]数组。考虑倍增,f[ cr ][ i ][0/1][0/1]表示自己到第 i 个祖先的路上的贡献(不含自己及自己子树,含祖先,含路上的点以及它们的分叉,不含祖先上面的部分);只要把dp数组累加起来就行了;累加的时候注意把自己这一条减去,就是如果用父亲的dp[ ][1]的话,就减去自己的min(dp[ ][0],dp[ ][1]),不然就减去自己的dp[ ][1],然后把父亲的这个减去之后的东西放到f[ ][0][ ][ ]里;i 的其他值正常倍增合并就行了。
考虑统计,两个端点就用它们的dp值就行;路上的就倍增地走,用 f 值就行;lca处需要一个以该点为根的值,减去走来的那两条,然后累加到答案里。因为在lca处的那两条一定是原树的两个子树,所以可以换一遍根记录每个点作为根的值,用的时候减去那两条的dp值即可。
思路似乎也很简单?倍增真是好物。考虑两个点的问题时应该想一想倍增、lca之类的。然后考虑倍增数组维护什么,想一想统计答案的时候分为几种不同的部分,应该就差不多了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e5+,M=; const ll INF=1e10+;
int n,m,p[N],pr[N][M],hd[N],xnt,to[N<<],nxt[N<<],lm,dep[N];
ll dp[N][],info[N][],f[N][M][][];
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='') ret=(ret<<)+(ret<<)+ch-'',ch=getchar();
return fx?ret:-ret;
}
ll Mn(ll a,ll b){return a<b?a:b;}
void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;}
void dfs1(int cr,int fa)
{
dp[cr][]=p[cr]; dep[cr]=dep[fa]+;
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa)
{
dfs1(v,cr);
dp[cr][]+=Mn(dp[v][],dp[v][]);
dp[cr][]+=dp[v][];
}
}
void dfs2(int cr,int fa,ll w0,ll w1)
{
info[cr][]=dp[cr][]+w1;
info[cr][]=dp[cr][]+Mn(w0,w1);
for(int i=hd[cr],v;i;i=nxt[i])
if((v=to[i])!=fa)
{
dfs2(v,cr,info[cr][]-dp[v][],info[cr][]-Mn(dp[v][],dp[v][]));
}
}
void dfsx(int cr,int fa)
{
for(int i=,d;i<=lm&&pr[pr[cr][i-]][i-];i++)
{
d=pr[cr][i-];
pr[cr][i]=pr[d][i-];
for(int j=;j<=;j++)
for(int k=;k<=;k++)
{
f[cr][i][j][k]=Mn(f[cr][i-][j][]+f[d][i-][][k],f[cr][i-][j][]+f[d][i-][][k]);//not dec p[d]
}
}
for(int i=hd[cr],v;i;i=nxt[i])//f:without son
if((v=to[i])!=fa)
{
pr[v][]=cr;
for(int j=;j<=;j++)
{
f[v][][j][]=dp[cr][]-dp[v][];//not pls p[v]
f[v][][j][]=dp[cr][]-Mn(dp[v][],dp[v][]);//not pls p[v]
}
f[v][][][]=INF;
dfsx(v,cr);
}
}
ll cz(int x,int f0,int y,int f1)
{
ll d00=,d01=,d10=,d11=;
if(dep[x]<dep[y])swap(x,y),swap(f0,f1);
int x0=x,y0=y;
ll y00=,y01=;
if(f0)y00=INF; else y01=INF;
for(int i=lm;i>=;i--)
if(dep[pr[x][i]]>dep[y])//> not >=
{
d00=Mn(y00+f[x][i][][],y01+f[x][i][][]);
d01=Mn(y00+f[x][i][][],y01+f[x][i][][]);
y00=d00; y01=d01;
x=pr[x][i];
}
ll ret=;
if(pr[x][]==y)
{
ret=info[y][f1];
if(f1) ret-=Mn(dp[x][],dp[x][]),ret+=Mn(y00,y01);//y** not d** for init
else ret-=dp[x][],ret+=y01;
ret+=dp[x0][f0];
return ret;
}
if(dep[x]!=dep[y])
{
d00=Mn(y00+f[x][][][],y01+f[x][][][]);
d01=Mn(y00+f[x][][][],y01+f[x][][][]);
y00=d00; y01=d01;
x=pr[x][];
} ll y10=,y11=;
if(f1)y10=INF; else y11=INF;
for(int i=lm;i>=;i--)
if(pr[x][i]!=pr[y][i])
{
d00=Mn(y00+f[x][i][][],y01+f[x][i][][]);
d01=Mn(y00+f[x][i][][],y01+f[x][i][][]);
y00=d00; y01=d01; x=pr[x][i];
d10=Mn(y10+f[y][i][][],y11+f[y][i][][]);
d11=Mn(y10+f[y][i][][],y11+f[y][i][][]);
y10=d10; y11=d11; y=pr[y][i];
}
int d=pr[x][];
ret=info[d][]-Mn(dp[x][],dp[x][])-Mn(dp[y][],dp[y][])+Mn(y00,y01)+Mn(y10,y11);//y** not d** for init
ll rt2=info[d][]-dp[x][]-dp[y][]+y01+y11;
ret=Mn(ret,rt2)+dp[x0][f0]+dp[y0][f1];
return ret;
}
int main()
{
freopen("defense.in","r",stdin);
freopen("defense.out","w",stdout);
char ch[];
n=rdn(); m=rdn(); scanf("%s",ch);
for(;(<<lm)<n;lm++);
for(int i=;i<=n;i++)p[i]=rdn();
for(int i=,u,v;i<n;i++)
{
u=rdn(); v=rdn(); add(u,v); add(v,u);
}
dfs1(,);dfs2(,,,);dfsx(,);
for(int i=,a,b,x,y;i<=m;i++)
{
a=rdn();x=rdn();b=rdn();y=rdn();
ll ans=cz(a,x,b,y);
printf("%lld\n",ans>=INF?-:ans);
}
return ;
}
NOIp2018 D2T3 defense——树上倍增的更多相关文章
- Codevs 2370 小机房的树 LCA 树上倍增
题目描述 Description 小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上.有一天,他们想爬到一个节点上去搞基,但是作为两只虫子, ...
- NOIP2013 货车运输 (最大生成树+树上倍增LCA)
死磕一道题,中间发现倍增还是掌握的不熟 ,而且深刻理解:SB错误毁一生,憋了近2个小时才调对,不过还好一遍AC省了更多的事,不然我一定会疯掉的... 3287 货车运输 2013年NOIP全国联赛提高 ...
- HDU 4822 Tri-war(LCA树上倍增)(2013 Asia Regional Changchun)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4822 Problem Description Three countries, Red, Yellow ...
- [NOIP2013/Codevs3287]货车运输-最小[大]生成树-树上倍增
Problem 树上倍增 题目大意 给出一个图,给出若干个点对u,v,求u,v的一条路径,该路径上最小的边权值最大. Solution 看到这个题第一反应是图论.. 然而,任意路径最小的边权值最大,如 ...
- 树上倍增求LCA及例题
先瞎扯几句 树上倍增的经典应用是求两个节点的LCA 当然它的作用不仅限于求LCA,还可以维护节点的很多信息 求LCA的方法除了倍增之外,还有树链剖分.离线tarjan ,这两种日后再讲(众人:其实是你 ...
- [树上倍增+二分答案][NOIP2012]运输计划
题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 公元 2044 年,人类进入了宇宙纪元 L 国有 nn 个星球,还有 n-1n−1 条双向航道,每条航道建立在两个星球之间,这 n-1n− ...
- 两种lca的求法:树上倍增,tarjan
第一种:树上倍增 f[x,k]表示x的2^k辈祖先,即x向根结点走2^k步达到的结点. 初始条件:f[x][0]=fa[x] 递推式:f[x][k]=f[ f[x][k-1] ][k-1] 一次bfs ...
- 【NOIP2013/Codevs3287】货车运输-最小生成树(大)-树上倍增
https://www.luogu.org/problemnew/show/P1967 由题可知,我们走的路的边应尽可能大,所以通过kruscal建最大生成树的图,再树上倍增,注意可能有多棵树; #i ...
- LCA树上倍增
LCA就是最近公共祖先,比如 节点10和11的LCA就是8,9和3的LCA就是3. 我们这里讲一下用树上倍增来求LCA. 大家都可以写出暴力解法,两个节点依次一步一步往上爬,直到爬到了相同的一个节点. ...
随机推荐
- Linux系统下使用pwgen生成密码的使用教程
pwgen生成的密码易于记忆且相当安全.从技术上来说,容易记忆的密码不会比随机生成的密码更加安全.但是,在大多数情况下,pwgen生成的密码已经足够安全,除了网银密码等需要高安全等级的情况外.使用易于 ...
- Spring核心技术AOP实现原理
关于Spring的AOP也是Spring的非常重要的一项技术.大致上可以这样说,面向切面编程,它的出现说明可以在不修改代码的情况下实现对功能的增强.而增强就是给一个方法增加一些功能.AOP主要思想就是 ...
- LeetCode——Max Consecutive Ones
LeetCode--Max Consecutive Ones Question Given a binary array, find the maximum number of consecutive ...
- C# word 类库基本属性介绍
using System;using System.Collections.Generic;using System.Text;using Microsoft.Office.Interop.Word; ...
- linux 分析进程占用CPU过高
重点是查看进程的线程中,哪个线程占用cpu过高,然后用gdb附加到进程,调试线程,看是否有死循环或者死锁等问题,步骤如下: 1 先用ps + grep找出该死的进程pid,比如 1706 2 top ...
- var与this定义变量的区别以及疑惑
我们知道: var可以定义一个局部变量,当然如果var定义在最外层的话,就是全局的局部变量,也就算是全局变量了. 而this关键字定义的变量准确的说应该算是成员变量.即定义的是调用对象的成员变量. 另 ...
- 浅谈MySQL中优化sql语句查询常用的30种方法 - 转载
浅谈MySQL中优化sql语句查询常用的30种方法 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中使 ...
- crontab 参数详解
crontab 参数 用户所建立的crontab文件中,每一行都代表一项任务,每行的每个字段代表一项设置,它的格式共分为六个字段,前五段是时间设定段,第六段是要执行的命令段,格式如下: minute ...
- scala学习手记36 - 容器基础
scala的容器包括Set.List和Map.三种容器的特征和Java中一样.scala为每种容器都提供了可变和不可变两种版本,分别位于scala.collection.mutable或scala.c ...
- vue组件化开发-vuex状态管理库
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.Vuex 也集成到 Vue 的官方调试工具 ...