题目描述

分析

方法一(线段树)

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

而区间的右端点则会从 \(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. SwiftUI - Grid View 的实现方法,逐步剖析助你实现

    简介 在当前正式 SwiftUI 版本而言,很多控件都是缺少的.比如在 UIKit 框架里有 UICollectionView 组件,可以很方便地做 Gird 格子类型的视图.但是在 SwiftUI ...

  2. android开发中防止刚进入activity时edittext获取焦点,防止自动自动弹出软键盘

    刚进入activity的时候,如果布局组件有edittext的话,往往edittext会获取焦点,自动弹出软键盘,影响整个界面的视觉效果.解决方法如下: 可以在edittext的父布局结构中(例如Li ...

  3. docker搭建zabbix收集windows计数器性能数据

    1 docker服务的安装 1)在线安装docker服务 在线安装可以参考下面的安装步骤 a 安装相关依赖组件 yum install -y yum-utils device-mapper-persi ...

  4. 轻轻松松学CSS:background

    background是复合属性,它可以分解为8个属性,其中5个属于CSS,另外3个属于CSS3一.CSS    1.background-color:背景色    2.background-image ...

  5. python中os.path下模块总结

    import os path =os.path.abspath("abc.text") # 返回绝对路径 print("path:",path) # path: ...

  6. Go测试开发(一) 怎么写Go代码

    安装过程略过,网上搜一大把. 介绍 本文会在一个module中开发一个简单的Go package. 同时介绍go tool(也就是go命令行). 以及如何fetch,build和install Go的 ...

  7. 求学生单科流水表中单科最近/最新的考试成绩表的新增可行性方案 使用Oracle提供的分析函数rank

    在 https://www.cnblogs.com/xiandedanteng/p/12327809.html 一文中,提到了三种求学生单科最新成绩的SQL可行性方案,这里还有另一种实现,那就是利用分 ...

  8. Ubuntu 安装配置 WordPress5.4.2

    一.安装 Apache 并配置 WordPress sudo apt-get install apache2 -y //  安装 apache2 sudo apt-get install php -y ...

  9. linux系统的默认用户

    1.可以通过cat /etc/passwd |cut -f1 -d:   可以提取/etc/passwd文件的第一个字段 2.也可以通过vipw进入vi编辑器来查看该文件每一行的第一个字段 linux ...

  10. adb命令—monkey篇

    monkey 目录 monkey 1.Monkey介绍 2.Monkey是用来做什么的 3.Monkey程序介绍 下面就是一些Monkey命令了 1.Monkey介绍 顾名思义,Monkey就是猴子, ...