题目描述

分析

方法一(线段树)

线段树维护的是以当前节点为左端点的区间的贡献

而区间的右端点则会从 \(1\) 到 \(n\) 逐渐右移

当我们把右端点从 \(i-1\) 的位置扩展到 \(i\) 的位置时

如果原先区间的最近公共祖先到根节点的路径中经过 \(a[i-1]\) 和 \(a[i]\) 的最近公共祖先

那么我们直接把这些区间的价值累加到 \(a[i-1]\) 和 \(a[i]\) 的最近公共祖先上

同时将其子树清空,还要把 \(a[i]\) 位置上的贡献加一

每次移完右端点后都要统计一下答案

代码

#include<cstdio>
#include<cstring>
#include<iostream>
inline int read(){
int x=0,fh=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
const int maxn=1e6+5;
int n,head[maxn],tot=1,a[maxn],x[maxn];
struct asd{
int to,next;
}b[maxn];
void ad(int aa,int bb){
b[tot].to=bb;
b[tot].next=head[aa];
head[aa]=tot++;
}
int siz[maxn],son[maxn],f[maxn],dep[maxn];
void dfs1(int now,int fa){
f[now]=fa;
siz[now]=1;
dep[now]=dep[fa]+1;
for(int i=head[now];i!=-1;i=b[i].next){
int u=b[i].to;
if(u==fa) continue;
dfs1(u,now);
siz[now]+=siz[u];
if(son[now] || siz[son[now]]<siz[u]){
son[now]=u;
}
}
}
int dfn[maxn],tp[maxn],dfnc,rk[maxn];
void dfs2(int now,int top){
tp[now]=top;
dfn[now]=++dfnc;
rk[dfnc]=now;
if(son[now]) dfs2(son[now],top);
for(int i=head[now];i!=-1;i=b[i].next){
int u=b[i].to;
if(u==f[now] || u==son[now]) continue;
dfs2(u,u);
}
}
int get_LCA(int u,int v){
while(tp[u]!=tp[v]){
if(dep[tp[u]]<dep[tp[v]]) std::swap(u,v);
u=f[tp[u]];
}
if(dep[u]<dep[v]) std::swap(u,v);
return v;
}
struct trr{
int l,r,tag;
long long cnt,sum;
}tr[maxn];
void push_up(int da){
tr[da].cnt=tr[da<<1].cnt+tr[da<<1|1].cnt;
tr[da].sum=tr[da<<1].sum+tr[da<<1|1].sum;
}
void push_down(int da){
if(tr[da].tag==-1){
tr[da<<1].tag=tr[da<<1|1].tag=-1;
tr[da<<1].cnt=tr[da<<1|1].cnt=0;
tr[da<<1].sum=tr[da<<1|1].sum=0;
tr[da].tag=0;
}
}
void build(int da,int l,int r){
tr[da].l=l,tr[da].r=r;
if(tr[da].l==tr[da].r){
return;
}
int mids=(tr[da].l+tr[da].r)>>1;
build(da<<1,l,mids);
build(da<<1|1,mids+1,r);
}
void xg(int da,int wz,long long val){
if(tr[da].l==tr[da].r){
tr[da].cnt+=val;
tr[da].sum=tr[da].cnt*x[rk[wz]];
return;
}
push_down(da);
int mids=(tr[da].l+tr[da].r)>>1;
if(wz<=mids) xg(da<<1,wz,val);
else xg(da<<1|1,wz,val);
push_up(da);
}
long long cx(int da,int l,int r){
if(tr[da].l>=l && tr[da].r<=r){
long long x=tr[da].cnt;
tr[da].cnt=0;
tr[da].sum=0;
tr[da].tag=-1;
return x;
}
push_down(da);
int mids=(tr[da].l+tr[da].r)>>1;
long long ans=0;
if(l<=mids) ans+=cx(da<<1,l,r);
if(r>mids) ans+=cx(da<<1|1,l,r);
push_up(da);
return ans;
}
int main(){
freopen("sagittarius.in","r",stdin);
freopen("sagittarius.out","w",stdout);
memset(head,-1,sizeof(head));
n=read();
int aa;
for(int i=2;i<=n;i++){
aa=read();
ad(aa,i);
ad(i,aa);
}
dfs1(1,0);
dfs2(1,1);
for(int i=1;i<=n;i++){
a[i]=read();
}
for(int i=1;i<=n;i++){
x[i]=read();
}
build(1,1,n);
long long ans=0;
for(int i=1;i<=n;i++){
if(i>1){
int now=get_LCA(a[i],a[i-1]);
long long cs=cx(1,dfn[now],dfn[now]+siz[now]-1);
xg(1,dfn[now],cs);
}
xg(1,dfn[a[i]],1);
ans+=tr[1].sum;
}
printf("%lld\n",ans);
return 0;
}

方法二(树上启发式合并)

这一道题有 \(20\%\) 的部分排列是按照 \(dfn\) 序生成的

而这样的序列对于任意一个节点来说,它的子节点的编号都是连续的

这样的话贡献在这棵子树内的区间就为 \((siz+1) \times siz/2\)

其中 \(siz\) 为子树大小

这就启示我们可以维护子树内连续区间段的长度

这样的话子树的值就可以直接累加到父亲节点上

因此我们可以使用树上启发式合并

连续区间段的维护则要用到一个性质

我们设 \(rk[a[i]]=i\),那么如果有一个连续区间短

那么很显然,它们的 \(rank\) 值是连续的

代码

#include<cstdio>
#include<cstring>
#define rg register
inline int read(){
rg int x=0,fh=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
const int maxn=1e6+5;
int h[maxn],tot=1;
struct asd{
int to,nxt;
}b[maxn];
int a[maxn],x[maxn],n;
void ad(int aa,int bb){
b[tot].to=bb;
b[tot].nxt=h[aa];
h[aa]=tot++;
}
int son[maxn],siz[maxn],dep[maxn],f[maxn];
void dfs1(int now,int fa){
siz[now]=1;
f[now]=fa;
dep[now]=dep[fa]+1;
for(rg int i=h[now];i!=-1;i=b[i].nxt){
rg int u=b[i].to;
if(u==fa) continue;
dfs1(u,now);
siz[now]+=siz[u];
if(son[now]==0 || siz[u]>siz[son[now]]){
son[now]=u;
}
}
}
long long nans[maxn],ans,mans,tmp;
int orz,len[maxn],rk[maxn];
long long js(int now){
int len1=len[rk[now]-1],len2=len[rk[now]+1];
int len3=len1+len2+1;
len[rk[now]-len1]=len3;
len[rk[now]+len2]=len3;
return 1LL*(len3+1)*len3/2-1LL*(len1+1)*len1/2-1LL*(len2+1)*len2/2;
}
void xg(int now,int op){
if(op==-1) len[rk[now]]=0;
else {
mans+=js(now);
}
for(rg int i=h[now];i!=-1;i=b[i].nxt){
rg int u=b[i].to;
if(u==orz || u==f[now]) continue;
xg(u,op);
}
}
void dfs2(int now,int op){
for(rg int i=h[now];i!=-1;i=b[i].nxt){
rg int u=b[i].to;
if(u==f[now] || u==son[now]) continue;
dfs2(u,0);
}
if(son[now]){
dfs2(son[now],1);
orz=son[now];
}
xg(now,1);
orz=0;
tmp=mans;
nans[now]=mans;
for(rg int i=h[now];i!=-1;i=b[i].nxt){
rg int u=b[i].to;
if(u==f[now]) continue;
tmp-=nans[u];
}
ans+=1LL*tmp*x[now];
if(op==0){
xg(now,-1);
mans=0;
}
}
int main(){
freopen("sagittarius.in","r",stdin);
freopen("sagittarius.out","w",stdout);
memset(h,-1,sizeof(h));
n=read();
rg int aa;
for(rg int i=2;i<=n;i++){
aa=read();
ad(aa,i);
ad(i,aa);
}
for(rg int i=1;i<=n;i++){
a[i]=read();
rk[a[i]]=i;
}
for(rg int i=1;i<=n;i++){
x[i]=read();
}
dfs1(1,0);
dfs2(1,0);
printf("%lld\n",ans);
return 0;
}

两种方法相比,树上启发式合并的代码更短,常数也更小

联赛模拟测试10 C. 射手座之日的更多相关文章

  1. [考试反思]0729NOIP模拟测试10

    安度因:哇哦. 安度因:谢谢你. 第三个rank1不知为什么就来了.迷之二连?也不知道哪里来的rp 连续两次考试数学都占了比较大的比重,所以我非常幸运的得以发挥我的优势(也许是优势吧,反正数学里基本没 ...

  2. [CSP-S模拟测试]:射手座之日(dsu on tree)

    题目传送门(内部题103) 输入格式 第一行一个数$n$,表示结点的个数. 第二行$n–1$个数,第$i$个数是$p[i+1]$.$p[i]$表示结点$i$的父亲是$p[i]$.数据保证$p[i]&l ...

  3. 联赛模拟测试20 C. Weed

    题目描述 \(duyege\) 的电脑上面已经长草了,经过辨认上面有金坷垃的痕迹. 为了查出真相,\(duyege\) 准备修好电脑之后再进行一次金坷垃的模拟实验. 电脑上面有若干层金坷垃,每次只能在 ...

  4. NOIP模拟测试10「大佬·辣鸡·模板」

    大佬 显然假期望 我奇思妙想出了一个式子$f[i]=f[i-1]+\sum\limits_{j=1}^{j<=m} C_{k \times j}^{k}\times w[j]$ 然后一想不对得容 ...

  5. 2019.7.29 NOIP模拟测试10 反思总结【T2补全】

    这次意外考得不错…但是并没有太多厉害的地方,因为我只是打满了暴力[还没去推T3] 第一题折腾了一个小时,看了看时间先去写第二题了.第二题尝试了半天还是只写了三十分的暴力,然后看到第三题是期望,本能排斥 ...

  6. noip模拟测试10

    T1 这道题在考场上想到了二维前缀和,就是自己算前缀和的方式有点麻烦,导致花的时间较长,但还是成功搞了出来. 因为暴力计算的话需要不停枚举左上角和右下角的 i ,j, 时间复杂度为 n^4 ,我当时就 ...

  7. 7.29 NOIP模拟测试10 辣鸡(ljh)+模板(ac)+大佬(kat)

    T1 辣鸡(ljh) 就是一道分类讨论的暴搜,外加一丢丢的减枝,然而我挂了,为啥呢,分类讨论变量名打错,大于小于号打反,能对才怪,写了sort为了调试就注释了,后来忘了解开,小减枝也没打.但是这道题做 ...

  8. [NOIP模拟测试10]辣鸡(ljh) 题解

    首先计算块内贡献,很显然是$(x_2-x_1)*(y_2-y_1)*2$. 然后考虑矩形之间的贡献,sort一遍分类讨论$n^2$暴力即可. 注意考虑边界情况是否能多两个,以及角对角的情况. 另外,排 ...

  9. csp-s模拟测试10.1(b)X 国的军队,排列组合, 回文题解

    题面:https://www.cnblogs.com/Juve/articles/11615883.html X 国的军队: 好像有O(T*N)的直接贪心做法 其实多带一个log的二分也可以过 先对所 ...

随机推荐

  1. Java程序员博客系统推荐!我调研了100来个 Java 开源博客系统,发现这 5 个最好用!

    大家好!我是 Guide 哥,Java 后端开发.一个会一点前端,喜欢烹饪的自由少年. 最近想倒腾一下博客,看了很多现成的比较成熟的开源博客系统,自己也简单从下面几个维度总结对比了一下: star数量 ...

  2. boot磁盘空间大于80警报

    WARNING=80SPACE_USED=`df |grep '^/dev/sda' |tr -s ' ' %|cut -d% -f5|sort -n|tail -n1`[ "$SPACE_ ...

  3. Selenium 如何复用浏览器【解决扫码登录等问题】

    Selenium中复用已经打开的浏览器进行自动化测试,可以辅助我们解决某些登录需要扫二维码之后,才能进行的操作 目前只支持谷歌Chrome浏览器,那需要做哪些准备操作呢?往下看 1.windows和M ...

  4. 最小生成树MST

    定义 在一给定的无向联通带权图\(G = (V, E, W)\)中,\((u, v)\) 代表连接顶点 \(u\) 与顶点 \(v\) 的边,而 \(w(u, v)\) 代表此边的权重,若存在 \(T ...

  5. [Failed]Tomcat cluster方案共享session配置出错,sigh....

    后继发展:https://www.cnblogs.com/xiandedanteng/p/12134300.html 参考网文一:多个Tomcat之间实现Session共享 参考网文二:Tomcat官 ...

  6. MySQL通过实体经纬度字段插入数据库point类型的经纬度字段

    说明:数据库:表中没有经度跟纬度字段,只有location字段(point类型) POINT(经度 纬度)实体类:只有经度 lng 字段.纬度 lat 字段 没有location字段 <!--添 ...

  7. Linux:apache目录结构和配置文件详解

    bin目录下的常见命令 conf目录 htdocs目录 logs目录 httpd.conf文件解析. 如果后期自己新创建了新的站点目录,就要重新增加对应的目录权限配置 extra/目录下配置文件解析 ...

  8. C++ 读入优化&输出优化

    读入优化:读入优化只是针对整数,由于getchar()读字符非常的快,所以采用getchar()来进行读入,下设输入的数为x 负数处理:用一个标志变量f,开始时为1,当读入了'-'时,f变为-1,最后 ...

  9. python模块之----subprocess

    例子 >>> subprocess.getstatusoutput('pwd')(0, '/home/ronny')>>> subprocess.getoutput ...

  10. $\TeX$ Gyre 字体安装过程与问题解决

    目录 安装过程 1. 下载字体包 2. 安装字体 3. 测试范例文件 本文地址 https://www.cnblogs.com/oberon-zjt0806/p/13672426.html 本文只是一 ...