联赛模拟测试10 C. 射手座之日
题目描述

分析
方法一(线段树)
线段树维护的是以当前节点为左端点的区间的贡献
而区间的右端点则会从 \(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. 射手座之日的更多相关文章
- [考试反思]0729NOIP模拟测试10
安度因:哇哦. 安度因:谢谢你. 第三个rank1不知为什么就来了.迷之二连?也不知道哪里来的rp 连续两次考试数学都占了比较大的比重,所以我非常幸运的得以发挥我的优势(也许是优势吧,反正数学里基本没 ...
- [CSP-S模拟测试]:射手座之日(dsu on tree)
题目传送门(内部题103) 输入格式 第一行一个数$n$,表示结点的个数. 第二行$n–1$个数,第$i$个数是$p[i+1]$.$p[i]$表示结点$i$的父亲是$p[i]$.数据保证$p[i]&l ...
- 联赛模拟测试20 C. Weed
题目描述 \(duyege\) 的电脑上面已经长草了,经过辨认上面有金坷垃的痕迹. 为了查出真相,\(duyege\) 准备修好电脑之后再进行一次金坷垃的模拟实验. 电脑上面有若干层金坷垃,每次只能在 ...
- NOIP模拟测试10「大佬·辣鸡·模板」
大佬 显然假期望 我奇思妙想出了一个式子$f[i]=f[i-1]+\sum\limits_{j=1}^{j<=m} C_{k \times j}^{k}\times w[j]$ 然后一想不对得容 ...
- 2019.7.29 NOIP模拟测试10 反思总结【T2补全】
这次意外考得不错…但是并没有太多厉害的地方,因为我只是打满了暴力[还没去推T3] 第一题折腾了一个小时,看了看时间先去写第二题了.第二题尝试了半天还是只写了三十分的暴力,然后看到第三题是期望,本能排斥 ...
- noip模拟测试10
T1 这道题在考场上想到了二维前缀和,就是自己算前缀和的方式有点麻烦,导致花的时间较长,但还是成功搞了出来. 因为暴力计算的话需要不停枚举左上角和右下角的 i ,j, 时间复杂度为 n^4 ,我当时就 ...
- 7.29 NOIP模拟测试10 辣鸡(ljh)+模板(ac)+大佬(kat)
T1 辣鸡(ljh) 就是一道分类讨论的暴搜,外加一丢丢的减枝,然而我挂了,为啥呢,分类讨论变量名打错,大于小于号打反,能对才怪,写了sort为了调试就注释了,后来忘了解开,小减枝也没打.但是这道题做 ...
- [NOIP模拟测试10]辣鸡(ljh) 题解
首先计算块内贡献,很显然是$(x_2-x_1)*(y_2-y_1)*2$. 然后考虑矩形之间的贡献,sort一遍分类讨论$n^2$暴力即可. 注意考虑边界情况是否能多两个,以及角对角的情况. 另外,排 ...
- csp-s模拟测试10.1(b)X 国的军队,排列组合, 回文题解
题面:https://www.cnblogs.com/Juve/articles/11615883.html X 国的军队: 好像有O(T*N)的直接贪心做法 其实多带一个log的二分也可以过 先对所 ...
随机推荐
- 2020,最新APP重构:网络请求框架
在现在的app,网络请求是一个很重要的部分,app中很多部分都有或多或少的网络请求,所以在一个项目重构时,我会选择网络请求框架作为我重构的起点.在这篇文章中我所提出的架构,并不是所谓的 最好 的网络请 ...
- Redis秒杀实战-微信抢红包-秒杀库存,附案例源码(Jmeter压测)
导读 前二天我写了一篇,Redis高级项目实战(点我直达),SpringBoot整合Redis附源码(点我直达),今天我们来做一下Redis秒杀系统的设计.当然啦,Redis基础知识还不过关的,先去加 ...
- Idea创建maven加载过慢解决方法
添加标签:archetypeCataloginternal问题解决!
- Labview学习之路(十三)数组函数
本文章介绍一下函数选版中的数组函数,一眼看懂没什么特殊地方的就不说了 (一)数组大小 返回I32类型的数或者数组. 什么是I32类型?就是32位有符号整型, 有符号什么意思?就是在32位数据中最高做符 ...
- Selenium多浏览器处理
当我们在执行自动化测试过程中,往往会针对不同的浏览器做兼容性测试,那么我们在代码中,可以针对执行命令传过来的参数,选择对应的浏览器来执行测试用例 代码如下: 在终端中执行命令如上图红框中所示: bro ...
- C# 自定义无边框窗体阴影效果
工作中我们会经常遇到自定义一些窗口的样式,设置无边框然后自定义关闭.最大化等其他菜单,但是这样就失去了Winform自带的边框阴影效果,下面这个方法能让无边框增加阴影效果.代码如下: using Sy ...
- Fitness - 05.19
倒计时226天 运动45分钟,共计9组,4.7公里.拉伸10分钟. 每组跑步3分钟(6.5KM/h),走路2分钟(5.5KM/h). 上周的跑步计划中断了,本周重复第三阶段的跑步计划. 一共掉了10斤 ...
- 【平台开发】— 4.mysql建库建表
本想着把前端脚手架run起来了,然后就可以借着登录来捋一下前后端交互的过程.但是后端导入JPA的时候就发现了,还没有数据库. 既然是本着学习的目的,那咱也不想只在后端写死返回的数据,要做就做全套. 一 ...
- ASP导出数据到excel遇到的一些问题
一直用动易平台的ASP做新闻发布网站,直到现在才接触导出数据到Excel的问题,目的在于公司要统计各部门的投稿量,要做这么个东西,实现起来是挺简单的,但是第一次做,还是费了一些功夫的,特此记录一下 主 ...
- C#知识点:操作XML
XML是什么就不用说了文本标记语言. 主要纪录如何对XML文件进行增删改查. Xml的操作类都存在System.xml命名空间下面. 应用型的直接上代码 using System; using Sys ...