BZOJ 4011 开店

Description

风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到人生哲学。最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱。这样的想法当然非常好啦,但是她们也发现她们面临着一个问题,那就是店开在哪里,面向什么样的人群。很神奇的是,幻想乡的地图是一个树形结构,幻想乡一共有\(n\)个地方,编号为\(1\)到 n,被\(n-1\)条带权的边连接起来。每个地方都住着一个妖怪,其中第\(i\)个地方的妖怪年龄是\(x_{i}\)。妖怪都是些比较喜欢安静的家伙,所以它们并不希望和很多妖怪相邻。所以这个树所有顶点的度数都小于或等于\(3\)。妖怪和人一样,兴趣点随着年龄的变化自然就会变化,比如我们的\(18\)岁少女幽香和八云紫就比较喜欢可爱的东西。幽香通过研究发现,基本上妖怪的兴趣只跟年龄有关,所以幽香打算选择一个地方\(u\)\(u\)为编号),然后在\(u\)开一家面向年龄在\(L\)\(R\)之间(即年龄大于等于\(L\)、小于等于\(R\))的妖怪的店。也有可能\(u\)这个地方离这些妖怪比较远,于是幽香就想要知道所有年龄在\(L\)\(R\)之间的妖怪,到点\(u\)的距离的和是多少(妖怪到\(u\)的距离是该妖怪所在地方到\(u\)的路径上的边的权之和),幽香把这个称为这个开店方案的方便值。幽香她们还没有决定要把店开在哪里,八云紫倒是准备了很多方案,于是幽香想要知道,对于每个方案,方便值是多少呢。

Input

第一行三个用空格分开的数\(n,Q\)\(A\),表示树的大小、开店的方案个数和妖怪的年龄上限。第二行\(n\)个用空格分开的数\(x_{1},x_2,\cdots,x_{n},x_{i}\)表示第\(i\)个地点妖怪的年龄,满足\(0 \le x_{i} < A\)。(年龄是可以为\(0\)的,例如刚出生的妖怪的年龄为\(0\)。) 接下来\(n-1\)行,每行三个用空格分开的数\(a,b,c\),表示树上的顶点\(a\)\(b\)之间有一条权为\(c\)(\(1 \le c \le 1000\))的边,\(a\)\(b\)是顶点编号。 接下来\(Q\)行,每行三个用空格分开的数\(u,a,b\)。对于这\(Q\)行的每一行,用\(a,b,A\)计算出\(L\)\(R\),表示询问“在地方\(u\)开店,面向妖怪的年龄区间为\(\lbrack L,R \rbrack\)的方案的方便值是多少”。对于其中第\(1\)行,\(L\)\(R\)的计算方法为:\(L=min(a \; mod \; A,b \; mod \; A), R=max(a \; mod \; A,b \; mod \; A)\)。对于第\(2\)到第\(Q\)行,假设前一行得到的方便值为\(ans\),那么当前行的\(L\)\(R\)计算方法为: \(L=min((a+ans) \; mod \; A,(b+ans) \; mod \; A), R=max((a+ans) \; mod \; A,(b+ans) \; mod \; A)\)

Output

对于每个方案,输出一行表示方便值。

Sample Input

10 10 10
0 0 7 2 1 4 7 7 7 9
1 2 270
2 3 217
1 4 326
2 5 361
4 6 116
3 7 38
1 8 800
6 9 210
7 10 278
8 9 8
2 8 0
9 3 1
8 0 8
4 2 7
9 7 3
4 7 0
2 2 7
3 2 1
2 3 4

Sample Output

1603
957
7161
9466
3232
5223
1879
1669
1282
0

HINT

满足 \(n \le 150000,Q \le 200000\)。对于所有数据,满足\(A \le 10^{9}\)

又一道动态树分治的题目。考场上妙想码了\(2h\),最后被卡常数,没开long long被卡成了暴力分。呵呵TAT。。。
\(x\)离散化后,我们构建重心树(树分治),然后每个节点对其重心子树维护\(4\)个数组——\(element,kind,tree_{0},tree_{1}\)
\(element_{i}\)表示\(i\)的重心子树都有那些\(x\)(排了序),\(kind_{i}\)表示\(i\)的重心子树中前\(i\)种存在的\(x\)的个数和。\(tree_{0}\)表示以\(i\)为根的重心子树前\(i\)种存在的\(x\)元素,在原树中到\(i\)的距离和,\(tree_{1}\)表示以表示以\(i\)为根的重心子树前\(i\)种存在的\(x\)元素,在原树中到\(father_{i}\)\(i\)在重心树中的父亲)的距离和。
算贡献需要用到容斥,利用树分治的思想,我们需要将在同一棵子树内的贡献减去。
对于某个区间\(\lbrack L,R \rbrack\),我们询问\(u\)。我们枚举重心树内\(u\)\(father\)链上的点\(now\),则\(now\)对答案的贡献为

\[dis_{now,u}\times(kind_{now,r}-kind_{now,l-1})+tree_{0,now,r}-tree_{0,now,l-1} \]

但是如果\(now\)仍然有\(father\),那么计算\(father_{now}\)的时候贡献就会计算重复。因此我们就可以在\(now\)时将重复的部分减除,这时\(tree_{1}\)就有用了。这时贡献为

\[-(dis_{father_{now},u}\times(kind_{now,r}-kind_{now,l-1})+tree_{1,now,r}-tree_{1,now,l-1}) \]

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<vector>
using namespace std;

typedef long long ll;
#define rhl (4000037)
#define inf (1<<30)
#define maxn (150010)
int N,Q,A,x[maxn],disc[maxn],tot,size[maxn],large[maxn];
int dis[maxn],ge[maxn],num,have[maxn],be1[maxn],be2[maxn],be3[2][maxn];
int side[maxn],toit[maxn*2],next[maxn*2],cost[maxn*2];
int cnt,arr[maxn],best,father[maxn],last1,last2,last3[2],sz[maxn];
ll sum[maxn],ans; bool vis[maxn],in[maxn];
int element[maxn*20],kind[maxn*20]; ll tree[2][maxn*20];
struct node { int a,b,c; }edge[rhl];

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

inline void add(int a,int b,int c) { next[++cnt] = side[a]; side[a] = cnt; toit[cnt] = b; cost[cnt] = c; }
inline void ins(int a,int b,int c) { add(a,b,c); add(b,a,c); }

inline void insert(int a,int b,int c)
{
	if (a > b) swap(a,b);
	int now = (a*233+b*23)%rhl;
	while (edge[now].a) { ++now; if (now >= rhl) now -= rhl; }
	edge[now] = (node){a,b,c};
}
inline int find(int a,int b)
{
	if (a > b) swap(a,b);
	int now = (a*233+b*23)%rhl;
	while (edge[now].a!=a||edge[now].b!=b) { ++now; if (now >= rhl) now -= rhl; }
	return edge[now].c;
}

inline void getroot(int now,int fa,int rest)
{
	size[now] = 1; large[now] = 0;
	for (int i = side[now];i;i = next[i])
	{
		if (toit[i] == fa||vis[toit[i]]) continue;
		getroot(toit[i],now,rest);
		size[now] += size[toit[i]];
		large[now] = max(large[now],size[toit[i]]);
	}
	large[now] = max(large[now],rest-size[now]);
	if (large[now] < large[best]) best = now;
}
inline int find_root(int now,int rest) { best = 0; getroot(now,0,rest); return best; }

inline void dfs(int st,int now,int fa)
{
	if (st) insert(st,now,dis[now]);
	if (!in[x[now]]) in[x[now]] = true,arr[++num] = x[now];
	ge[x[now]]++; sum[x[now]] += (ll)dis[now];
	size[now] = 1;
	for (int i = side[now];i;i = next[i])
	{
		if (toit[i] == fa||vis[toit[i]]) continue;
		dis[toit[i]] = dis[now]+cost[i];
		dfs(st,toit[i],now);
		size[now] += size[toit[i]];
	}
}
inline void calc(int st,int now,int len,bool sign,int root)
{
	dis[now] = len; dfs(st,now,0);
	sort(arr+1,arr+num+1);
	if (st)
	{
		sz[root] = num;
		be1[root] = last1+1;
		for (int i = 1;i <= num;++i) element[++last1]=arr[i];
		be2[root] = ++last2+1;
	}
	be3[sign][root] = ++last3[sign]+1;
	for (int i = 1;i <= num;++i)
	{
		if (st) ++last2,kind[last2] = kind[last2-1]+ge[arr[i]];
		++last3[sign]; tree[sign][last3[sign]] = tree[sign][last3[sign]-1]+sum[arr[i]];
		in[arr[i]] = false; ge[arr[i]] = sum[arr[i]] = 0;
	}
	num = 0;
}

inline void cut(int now)
{
	calc(now,now,0,0,now);
	vis[now] = true;
	for (int i = side[now];i;i = next[i])
	{
		if (vis[toit[i]]) continue;
		int root = find_root(toit[i],size[toit[i]]);
		father[root] = now;
		calc(0,toit[i],cost[i],1,root);
		cut(root);
	}
}

inline int lb(int lll,int rrr,int key)
{
	int l = lll,r = rrr;
	while (l <= r)
	{
		int mid = (l + r) >> 1;
		if (element[mid] < key) l = mid + 1;
		else r = mid - 1;
	}
	return l-lll+1;
}

inline int ub(int lll,int rrr,int key)
{
	int l = lll,r = rrr;
	while (l <= r)
	{
		int mid = (l + r) >> 1;
		if (element[mid] <= key) l = mid + 1;
		else r = mid - 1;
	}
	return l-lll+1;
}

inline ll query(int u,int L,int R)
{
	ll ret = 0;
	for (int now = u;now;now = father[now])
	{
		int l = lb(be1[now],be1[now]+sz[now]-1,L),r = ub(be1[now],be1[now]+sz[now]-1,R)-1;
		
		int all = kind[be2[now]+r-1]-kind[be2[now]+l-2];
		ll inc = tree[0][be3[0][now]+r-1]-tree[0][be3[0][now]+l-2];
		ret += (ll)all*find(now,u)+inc;
		if (father[now])
		{
			inc = tree[1][be3[1][now]+r-1]-tree[1][be3[1][now]+l-2];
			ret -= (ll)all*find(father[now],u)+inc;
		}
	}
	return ret;
}

int main()
{
	freopen("4012.in","r",stdin);
	freopen("4012.out","w",stdout);
	N = read(),Q = read(),A = read();
	for (int i = 1;i <= N;++i) disc[++tot] = x[i] = read();
	sort(disc+1,disc+tot+1); tot = unique(disc+1,disc+tot+1)-disc-1;
	for (int i = 1;i <= N;++i) x[i] = lower_bound(disc+1,disc+tot+1,x[i])-disc;
	for (int i = 1,a,b,c;i < N;++i) a = read(),b = read(),c = read(),ins(a,b,c);
	large[0] = inf;
	cut(find_root(1,N));
	while (Q--)
	{
		int u = read(),L,R; ll a = read(),b = read();
		L = min((ans + a)%A,(ans + b)%A),R = max((ans + a)%A,(ans + b)%A);
		L = lower_bound(disc+1,disc+tot+1,L)-disc,R = upper_bound(disc+1,disc+tot+1,R)-disc-1;
		ans = query(u,L,R); printf("%lld\n",ans);
	}
	fclose(stdin); fclose(stdout);
	return 0;
}
高考结束,重新回归。
posted @ 2015-04-20 21:01  lmxyy  阅读(155)  评论(0编辑  收藏  举报