zoukankan      html  css  js  c++  java
  • 联赛模拟测试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
    ",ans);
    	return 0;
    }
    

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

    这一道题有 (20\%) 的部分排列是按照 (dfn) 序生成的
    而这样的序列对于任意一个节点来说,它的子节点的编号都是连续的
    这样的话贡献在这棵子树内的区间就为 ((siz+1) imes 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
    ",ans);
    	return 0;
    }
    

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

  • 相关阅读:
    C#进阶系列——WebApi 接口返回值不困惑:返回值类型详解
    C#进阶系列——WebApi 接口测试工具:WebApiTestClient
    Web API在OWIN下实现OAuth
    C#进阶系列——WebApi 跨域问题解决方案:CORS
    C#进阶系列——WebApi 身份认证解决方案:Basic基础认证
    C#进阶系列——WebApi 异常处理解决方案
    python标准库介绍——13 types 模块详解
    python标准库介绍——12 time 模块详解
    python标准库介绍——11 atexit 模块详解
    python标准库介绍——10 sys 模块详解
  • 原文地址:https://www.cnblogs.com/liuchanglc/p/13777348.html
Copyright © 2011-2022 走看看