题目描述

分析

方法一(线段树)

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

而区间的右端点则会从 \(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. PyTorch迁移学习-私人数据集上的蚂蚁蜜蜂分类

    迁移学习的两个主要场景 微调CNN:使用预训练的网络来初始化自己的网络,而不是随机初始化,然后训练即可 将CNN看成固定的特征提取器:固定前面的层,重写最后的全连接层,只有这个新的层会被训练 下面修改 ...

  2. HMM隐马尔可夫模型来龙去脉(二)

    目录 前言 预备知识 一.估计问题 1.问题推导 2.前向算法/后向算法 二.序列问题 1.问题推导 2.维特比算法 三.参数估计问题 1.问题推导 2.期望最大化算法(前向后向算法) 总结 前言 H ...

  3. js中的各种常用方法(持续更新中。。。)

    我看到常用的就写上去,如果你们有,可以在评论上发表,我再把它补充到我的随笔中 some() var ages = [3, 10, 18, 20]; function checkAdult(age) { ...

  4. python爬虫实战---爬取大众点评评论

    python爬虫实战—爬取大众点评评论(加密字体) 1.首先打开一个店铺找到评论 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经 ...

  5. 删除MBR分区如何使用光盘恢复

    1.备份MBR分区表 dd  if=/dev/sda  of=/data/mbr.bak  bs=1  count=64  skip=446 分区表前512字节分为三部分,第一部分446字节与启动相关 ...

  6. TonWeb6.1Linux安装文档

    东方通部署文档   东方通安装部署文档 准备文件:开始安装:警告:在安装之前先安装JDK,JDK要求JDK5以上Linux安装:运行安装包sh Install_TW6.1.5.15_Standard_ ...

  7. 外包公司派遣到网易,上班地点网易大厦,转正后工资8k-10k,13薪,包三餐,值得去吗?

    作为一个人,我们必须时时刻刻清醒地看待自己,做到不卑不亢才能坚强地活下去. 请肆无忌惮地点赞吧,微信搜索[沉默王二]关注这个在九朝古都洛阳苟且偷生的程序员.本文 GitHub github.com/i ...

  8. shell小技巧(3)复制文件到其他主机

    复制一个文件nfsclient.sh到主机172.18.117.162的/tmp目录下 scp -P22 /tmp/nfsclient.sh  root@172.18.117.162:/tmp -P2 ...

  9. 读取文本文件中的中文打印到Eclipse控制台为何显示问号

    原因:未将文本文件存为utf-8编码格式而是ascii编码格式.

  10. SpringMVC执行流程源码分析

    SpringMVC执行流程源码分析 我们先来看张图片,帮助我们理解整个流程 然后我们开始来解析 首先SpringMVC基于Servlet来运行 那么我们首先来看HttpServletBean这个类 他 ...