LOJ #2116. 「HNOI2015」开店(树链剖分+标记永久化主席树 || 动态点分治)

130 篇文章 1 订阅
124 篇文章 0 订阅

题目

解法一:
∑ i ∈ [ 1 , n ]   a n d   x [ i ] ∈ [ l , r ] d i s ( u , i ) = ∑ i d e p [ u ] + d e p [ i ] − 2 ∗ d e p [ l c a ( u , i ) ] \sum_{i\in [1,n] \ and \ x[i]\in[l,r]} dis(u,i) = \sum_{i}dep[u]+dep[i]-2*dep[lca(u,i)] i[1,n] and x[i][l,r]dis(u,i)=idep[u]+dep[i]2dep[lca(u,i)]
从这个式子我们可以将答案表达为: [ L , R ] [L,R] [L,R]中妖怪数量*u的深度+这些妖怪深度之和- 2 ∗ 2* 2他们与u的lca的深度之和。
前面两个比较简单,重要的是第3个。
这也是一种套路了。
两个点的lca的深度等于他们到根的路径的并的长度。
对于这个题我们可以更具体一点:
我们统计每个点被两个点同时经过的次数。
那么每个点的次数 * 这个点到它父亲的距离 的和 = lca的深度。
对于多个与u匹配的点 x 1 , x 2... x1,x2... x1,x2...,我们可以用树剖+线段树把被 x i x_i xi经过的次数累加统计。
回答询问时再拿 u u u往上跑统计答案。
但是我们需要的是 [ L , R ] [L,R] [L,R]之间的答案。
那么就用可持久化线段树就行了
等等这是个区间修改区间查询,主席树空间会炸?!。
可以标记永久化。
通过这个题总算是知道了标记永久化的正确姿势。
以前打标记永久化都是区间修改单点查询,以为就是经过标记时处理一下就行了。
实际上是经过标记时处理。线段被整个覆盖时需要把下面更深的所有线段的贡献也加上,但这个贡献都是覆盖一个整的线段的,可以在打标记时处理出来。
具体看代码。
AC Code:

#include<bits/stdc++.h>
#define maxn 300005
#define maxpt maxn * 50
#define LL long long
using namespace std;

char cb[1<<15],*cs=cb,*ct=cb;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<15,stdin),cs==ct)?0:*cs++)
void read(int &res){
	char ch;
	for(;!isdigit(ch=getc()););
	for(res=ch-'0';isdigit(ch=getc());res=res*10+ch-'0');
}
int n,q,A,x[maxn];	
int info[maxn],Prev[maxn<<1],to[maxn<<1],cst[maxn<<1],cnt_e=0;
void Node(int u,int v,int w){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=w; }
LL val[maxn],sum[maxn],s2[maxn];
int siz[maxn],son[maxn],tp[maxn],id[maxn],fa[maxn],dep[maxn],tot;
void dfs1(int now,int ff){
	siz[now]=1,son[now]=-1;
	fa[now] = ff;
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff){
			dep[to[i]] = dep[now] + cst[i];
			dfs1(to[i],now);
			if(son[now] == -1 || siz[son[now]] < siz[to[i]]) 
				son[now] = to[i];
			siz[now] += siz[to[i]];
		}
}
void dfs2(int now,int ff){
	id[now] = ++tot;
	if(son[now]!=-1) tp[son[now]]=tp[now],dfs2(son[now],now);
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff && to[i]!=son[now]){
			tp[to[i]] = to[i];
			dfs2(to[i],now);
		}
}
void dfs3(int now,int ff){
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff){
			val[id[to[i]]] = cst[i];
			dfs3(to[i],now);
		}
}

bool cmp(const int &u,const int &v){ return x[u]<x[v]; }
int c[maxn],sb[maxn];

int rt[maxn],lc[maxpt],rc[maxpt],tag[maxpt];
LL Sum[maxpt],Val[maxpt];

void upd(int now){
	Sum[now] = Sum[lc[now]] + Sum[rc[now]] + Val[now];
}

void Insert(int &now,int l,int r,int ql,int qr){
	if(qr<l || r<ql) return;
	tag[++tot] = tag[now] , Sum[tot] = Sum[now] , Val[tot] = Val[now] , lc[tot] = lc[now] ,rc[tot] = rc[now];
	now = tot;
	if(ql<=l && r<=qr){ tag[now]++,Val[now]+=val[r]-val[l-1],upd(now);return;}
	int mid = (l+r) >> 1;
	Insert(lc[now],l,mid,ql,qr);
	Insert(rc[now],mid+1,r,ql,qr);
	upd(now);
}

void Insertpath(int now,int &rt){
	for(;now;now=fa[tp[now]])
		Insert(rt,1,n,id[tp[now]],id[now]);
}

LL query(int now,int l,int r,int ql,int qr){
	if(!now || l>qr || ql>r) return 0;
	if(ql<=l&&r<=qr) return Sum[now];
	int mid = (l+r) >> 1;
	LL ret = tag[now] * (val[min(r,qr)]-val[max(l,ql)-1]);
	ret += query(lc[now],l,mid,ql,qr) + query(rc[now],mid+1,r,ql,qr);
	return ret;
}

LL querypath(int now,int L,int R){
	LL ret = 0;
	for(;now;now=fa[tp[now]]){
		//printf("%lld %lld\n",query(rt[R],1,A,id[tp[now]],id[now]),query(rt[L],1,A,id[tp[now]],id[now]));
		ret += query(rt[R],1,n,id[tp[now]],id[now]) - query(rt[L],1,n,id[tp[now]],id[now]);
	}
	return ret;
}

int main(){
	read(n),read(q),read(A);
	for(int i=1;i<=n;i++) read(x[i]),sb[++sb[0]]=x[i],c[i]=i;
	sort(sb+1,sb+1+sb[0]);
	sb[0] = unique(sb+1,sb+1+sb[0])-sb-1;
	for(int i=1;i<=n;i++) x[i]=lower_bound(sb+1,sb+1+sb[0],x[i])-sb;
	for(int i=1,u,v,w;i<n;i++){
		read(u),read(v),read(w);
		Node(u,v,w),Node(v,u,w);
	}
	dfs1(1,0),tp[1]=1,dfs2(1,0),dfs3(1,0);tot=0;
	for(int i=1;i<=n;i++) val[i] += val[i-1];
	sort(c+1,c+1+n,cmp);
	for(int i=1;i<=n;i++) 
	{
		if(i>1 && x[c[i]] != x[c[i-1]]) rt[x[c[i]]] = rt[x[c[i]]-1];
		//if(x[c[i]] == 3)
		//	printf("%d\n",rt[x[c[i]]]);
		Insertpath(c[i],rt[x[c[i]]]);
		sum[x[c[i]]]++,s2[x[c[i]]]+=dep[c[i]];
	}
	for(int i=1;i<=sb[0];i++) sum[i]+=sum[i-1],s2[i]+=s2[i-1];
		//printf("s2[%d] = %lld\n",i,s2[i]);
	LL ans=0;
	for(int u,a,b,L,R;q--;){
		read(u),read(a),read(b);
		L=min((a+ans)%A,(b+ans)%A),R=max((a+ans)%A,(b+ans)%A);
		//printf("%d %d\n",L,R);
		L = lower_bound(sb+1,sb+1+sb[0],L)-sb-1, R = upper_bound(sb+1,sb+1+sb[0],R)-sb-1;
		//printf("%d %d\n",L,R);
		ans = (sum[R] - sum[L]) * dep[u] + s2[R] - s2[L];
		//printf("%lld\n",ans);
		ans -= 2 * querypath(u,L,R);
		printf("%lld\n",ans);
	}
}

解法二:
动态边分治。

这个树所有顶点的度数都小于或等于3

题目明示。
所以不需要加虚点,记所有点在每一层的所属就行。
每层用动态开店线段树求区间和。?!
直接 v e c t o r &lt; i n t , l o n g   l o n g &gt; vector&lt;int,long\ long&gt; vector<int,long long>
前面存年龄,后面存距离的前缀和, l o w e r b o u n d lowerbound lowerbound大法好。
感觉比动态点分治好写多了。。。
感觉比点分治好写多了。
感觉比分治好些多了。
感觉好写多了。
我再也不会写点分治了
具体看短得要命的代码。

AC Code:

#include<bits/stdc++.h>
#define maxn 150005
#define lim 20
#define LL long long
using namespace std;

int n,q,A,x[maxn];
LL cst[maxn<<1];
int info[maxn],Prev[maxn<<1],to[maxn<<1],cnt_e=1;
void Node(int u,int v,int c){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=c; }
bool vis[maxn<<1];

int Min = 0 , rt , siz[maxn];
void dfs(int now , int ff , int tsz){
	siz[now] = 1;
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff && !vis[i]){
			dfs(to[i],now,tsz);
			siz[now] += siz[to[i]];
			if(Min > max(tsz - siz[to[i]] , siz[to[i]]))
				Min = max(tsz - siz[to[i]] , siz[to[i]]),
				rt = i;
		}
}

int findrt(int now,int tsz){
	rt = -1 , Min = 0x3f3f3f3f;
	dfs(now,0,tsz);
	return rt;
}

vector<pair<int,LL> >vec[maxn<<1][2];
int sid[maxn][lim],fa[maxn][lim],Dep[maxn];
LL fadis[maxn][lim];

void ser(int now,int ff,LL dis,vector<pair<int,LL> >&vec,int side,int dep,int pfa){
	vec.push_back(make_pair(x[now],dis));
	sid[now][dep] = side , fa[now][dep] = pfa , fadis[now][dep] = dis;
	siz[now] = 1;
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff && !vis[i])
			ser(to[i],now,dis+cst[i],vec,side,dep,pfa),
			siz[now] += siz[to[i]];
}

void Solve(int now,int dep){
	//printf("%d %d\n",now,dep);
	vis[now] = vis[now^1] = 1;
	
	ser(to[now],0,0,vec[now][0],0,dep,now);
	sort(vec[now][0].begin(),vec[now][0].end());
	for(int i=1,siz=vec[now][0].size();i<siz;i++) vec[now][0][i].second += vec[now][0][i-1].second;
		
	ser(to[now^1],0,0,vec[now][1],1,dep,now);
	sort(vec[now][1].begin(),vec[now][1].end());
	for(int i=1,siz=vec[now][1].size();i<siz;i++) vec[now][1][i].second += vec[now][1][i-1].second;
		
	int rt1 = findrt(to[now],siz[to[now]]);
	if(rt1 != -1) Solve(rt1,dep+1);
	else Dep[to[now]] = dep;
		
	rt1 = findrt(to[now^1],siz[to[now^1]]);
	if(rt1 != -1) Solve(rt1,dep+1);
	else Dep[to[now^1]] = dep;
}

int main(){
	scanf("%d%d%d",&n,&q,&A);
	for(int i=1;i<=n;i++) scanf("%d",&x[i]);
	for(int i=1;i<n;i++){
		int u,v,c;
		scanf("%d%d%d",&u,&v,&c);
		Node(u,v,c),Node(v,u,c);
	}
	//printf("%d\n",findrt(1,n));
	Solve(findrt(1,n),0);
	LL ans = 0;
	for(int u,a,b,L,R;q--;){
		scanf("%d%d%d",&u,&a,&b);
		L=min((a+ans)%A,(b+ans)%A),R=max((a+ans)%A,(b+ans)%A);
		
		ans = 0;
		for(int i=Dep[u];i>=0;i--){
			int l = L , r = R , now = fa[u][i] , sd = sid[u][i];
			l = lower_bound(vec[now][sd^1].begin(),vec[now][sd^1].end(),make_pair(l,-1ll))-vec[now][sd^1].begin()-1;
			r = upper_bound(vec[now][sd^1].begin(),vec[now][sd^1].end(),make_pair(r,0x3f3f3f3f3f3f3f3fll))-vec[now][sd^1].begin()-1;
			//printf("%d %d %d\n",l,r,vec[now][sd^1].size());
			ans += (r-l) * 1ll * (fadis[u][i] + cst[now]) + ((r>=0?vec[now][sd^1][r].second:0) - (l>=0?vec[now][sd^1][l].second:0));
			//printf("%d %lld\n",i,ans);
		}
		printf("%lld\n",ans);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值