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;
}

BZOJ 4011 开店的更多相关文章

  1. BZOJ 4011: [HNOI2015]落忆枫音( dp )

    DAG上有个环, 先按DAG计数(所有节点入度的乘积), 然后再减去按拓扑序dp求出的不合法方案数(形成环的方案数). ---------------------------------------- ...

  2. BZOJ 4011 HNOI2015 落忆枫音 DAG上的dp(实际上重点在于分析)

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=4011 题意概述:给出一张N点的DAG(从1可以到达所有的点),点1的入度为0.现在加一条原 ...

  3. BZOJ 4011 HNOI2015 落忆枫音

    AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=4011 题目很长,写得也很有诗意与浪漫色彩,让我们不禁感叹出题人是一个多么英俊潇洒的人. 所 ...

  4. BZOJ 4011 落忆枫音

    几个重点: 1.从每个点任选一条入边,都可以成为一个树形图. 2.于是考虑所有答案减去有环的答案. 3.将要求的东西形式化表示出来,然后发现可以直接dp.. 好厉害啊.. #include<io ...

  5. bzoj 4011

    看了好多篇题解才看懂的题,我实在太菜了... 首先根据一个我不知道的算法,可以证明在没有加入新的边的时候,原图的所有生成树的方案数就是所有点(除1以外)的度之积 那么在新加入这条边之后,我们仍然可以这 ...

  6. BZOJ 4011 【HNOI2015】 落忆枫音

    题目链接:落忆枫音 以下内容参考PoPoQQQ大爷的博客 首先我们先来考虑一下如果没有新加入的那条边,答案怎么算. 由于这是一个\(DAG\),所以我们给每个点随便选择一条入边,最后一定会构成一个树形 ...

  7. BZOJ 4011: [HNOI2015]落忆枫音 计数 + 拓扑排序

    Description 「恒逸,你相信灵魂的存在吗?」 郭恒逸和姚枫茜漫步在枫音乡的街道上.望着漫天飞舞的红枫,枫茜突然问出 这样一个问题.  「相信吧.不然我们是什么,一团肉吗?要不是有灵魂……我们 ...

  8. Week One

    2018.11.21: 1.[BZOJ 4868][SHOI 2017] 从后往前枚举最后位置即可,如果$A<B$,用尽可能多的$A$替换$B$操作 Tip:很大的$C$可能爆$longlong ...

  9. 【BZOJ】【4011】【HNOI2015】落忆枫音

    拓扑排序+DP 题解:http://blog.csdn.net/PoPoQQQ/article/details/45194103 http://www.cnblogs.com/mmlz/p/44487 ...

随机推荐

  1. SOAP消息的传递

    上一篇说了SOAP消息的创建,那么创建好了的SOAP消息要怎么发送给服务端呢? public class SoapTest { private String wsdlUri = "http: ...

  2. Oracle 插入数据效率对比

    oracle插入数据有多种方式: 将从多个表中查出来的数据插入到临时表中 数据行数 5189597 1.传统方式:直接将数据插入到表中 insert into LLB_BASIC_USER_D_TEM ...

  3. 前后端分离--mock

    fekit mock 数据 > fekit server -m mock.config 配置mock.config 支持正则 module.exports = { /queryProductDe ...

  4. C#中的两种debug方法

    这篇文章主要介绍了C#中的两种debug方法介绍,本文讲解了代码用 #if DEBUG 包裹.利用宏定义两种方法,需要的朋友可以参考下   第一种:需要把调试方法改成debug代码用 #if DEBU ...

  5. HTML5 Canvas 2D绘图

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/4851774. ...

  6. Linux下载jdk ,Linux如何下载jdk

    Linux下载jdk Linux如何下载jdk >>>>>>>>>>>>>>>>>>> ...

  7. eAccelerator介绍

    加速器 eAccelerator是一个自由开放源码php加速器,优化和动态内容缓存,提高了php脚本的缓存性能,使得PHP脚本在编译的状态下,对服务器的开销几乎完全消除. 它还有对脚本起优化作用,以加 ...

  8. 用Markdown优雅的渲染我们的网页

    认识 Markdown Markdown 是一种用来写作的轻量级「标记语言」,它用简洁的语法代替排版,而不像一般我们用的字处理软件 Word 或 Pages 有大量的排版.字体设置.它使我们专心于码字 ...

  9. Sql操作表字段

    1.增加字段     alter table docdsp     add dspcodechar(200)2.删除字段     ALTER TABLE table_NAME DROP COLUMNc ...

  10. Android - NullPointerException

    Android中的空指针,感觉出现率,比普通的java出现的概率多得多,今天我无意中发现的NullPointerException,还是想记录下来. 我们知道, 我们的控件一般定义在layout.xm ...