zoukankan      html  css  js  c++  java
  • P3241 [HNOI2015]开店 动态点分治

    (color{#0066ff}{ 题目描述 })

    风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到人生哲学。最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱。

    这样的想法当然非常好啦,但是她们也发现她们面临着一个问题,那就是店开在哪里,面向什么样的人群。很神奇的是,幻想乡的地图是一个树形结构,幻想乡一共有 (n)个地方,编号为 (1)(n)(n-1) 条带权的边连接起来。每个地方都住着一个妖怪,其中第(i) 个地方的妖怪年龄是 (x_i)

    妖怪都是些比较喜欢安静的家伙,所以它们并不希望和很多妖怪相邻。所以这个树所有顶点的度数都小于或等于 (3)。妖怪和人一样,兴趣点随着年龄的变化自然就会变化,比如我们的(18) 岁少女幽香和八云紫就比较喜欢可爱的东西。幽香通过研究发现,基本上妖怪的兴趣只跟年龄有关,所以幽香打算选择一个地方(u)(u) 为编号),然后在(u)开一家面向年龄在(L)(R) 之间(即年龄大于等于(L) 小于等于(R) )的妖怪的店。

    也有可能(u) 这个地方离这些妖怪比较远,于是幽香就想要知道所有年龄在(L)(R) 之间的妖怪,到点(u) 的距离的和是多少(妖怪到(u) 的距离是该妖怪所在地方到(u) 的路径上的边的权之和),幽香把这个称为这个开店方案的方便值。

    幽香她们还没有决定要把店开在哪里,八云紫倒是准备了很多方案,于是幽香想要知道,对于每个方案,方便值是多少呢。

    (color{#0066ff}{输入格式})

    第一行三个用空格分开的数(n,Q)(A) ,表示树的大小、开店的方案个数和妖怪的年龄上限。

    第二行(n) 个用空格分开的数(x_1,x_2,ldots,x_n;)xi 表示第(i) 个地点妖怪的年龄,满足(0le x_ilt A) 。(年龄是可以为(0)的,例如刚出生的妖怪的年龄为(0) 。)

    接下来(n-1) 行,每行三个用空格分开的数(a)(b)(c) ,表示树上的顶点(a)(b) 之间有一条权为(c(1le cle1000))的边,(a)(b) 是顶点编号。

    接下来(Q) 行,每行三个用空格分开的数(u,a,b)

    对于这(Q) 行的每一行,用(a,b,A) 计算出(L)(R) ,表示询问”在地方(u) 开店,面向妖怪的年龄区间为([L,R]) 的方案的方便值是多少“。

    对于其中第(1) 行,(L)(R) 的计算方法为:(L) = min((a) % (A),(b) % (A)),(R) = max((a) % (A),(b) % (A)) 。

    对于第(2) 到第(Q) 行,假设前一行得到的方便值为(ans) ,那么当前行的(L)(R) 计算方法为: $L=min((a+ans)%A,(b+ans) %A),R=max((a+ans) %A,(b+ans) %A) 。

    (color{#0066ff}{输出格式})

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

    (color{#0066ff}{输入样例})

    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
    

    (color{#0066ff}{输出样例})

    1603 
    957 
    7161 
    9466 
    3232 
    5223 
    1879 
    1669 
    1282 
    0
    

    (color{#0066ff}{数据范围与提示})

    满足(nle1.5*10^5,Qle2*10^5) 。对于所有数据,满足 (A<=10^9)

    (color{#0066ff}{ 题解 })

    动态点分治

    建立点分树

    每个点维护4个vector,一个是自己子树的age(有序加入),一个是对应的dis前缀和,我们考虑在age上二分找到L,R, 用这个下标在dis上收集ans

    还有两个数组类似,记录对父亲的贡献

    先在点分树让上把四个vector预处理出来,为了保证age有序,我们先把点按age从小到大排序,在更新

    然后在点分树上统计贡献就行,注意卡二分边界

    #include<bits/stdc++.h>
    #define LL long long
    LL in() {
    	char ch; LL x = 0, f = 1;
    	while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    	return x * f;
    }
    const int maxn = 2e5 + 10;
    struct node {
    	int to;
    	LL dis;
    	node *nxt;
    	node(int to = 0, LL dis = 0, node *nxt = NULL): to(to), dis(dis), nxt(nxt) {}
    	void *operator new (size_t) {
    		static node *S = NULL, *T = NULL;
    		return (S == T) && (T = (S = new node[1024]) + 1024), S++;
    	}
    };
    LL n, Q, A;
    using std::vector;
    vector<LL> tofadis[maxn], tofaage[maxn], dis[maxn], age[maxn];
    int siz[maxn], maxsiz[maxn], root, f[maxn][26], sum, rt, u[maxn], dep[maxn];
    LL d[maxn][26], val[maxn];
    node *head[maxn];
    bool vis[maxn];
    void add(int from, int to, LL dis) {
    	head[from] = new node(to, dis, head[from]);
    }
    void init() {
    	n = in(), Q = in(), A = in();
    	for(int i = 1; i <= n; i++) val[i] = in();
    	LL x, y, z;
    	for(int i = 1; i < n; i++) {
    		x = in(), y = in(), z = in();
    		add(x, y, z), add(y, x, z);
    	}
    }
    void getroot(int x, int fa) {
    	siz[x] = 1;
    	maxsiz[x] = 0;
    	for(node *i = head[x]; i; i = i->nxt) {
    		if(i->to == fa || vis[i->to]) continue;
    		getroot(i->to, x);
    		siz[x] += siz[i->to];
    		maxsiz[x] = std::max(maxsiz[x], siz[i->to]);
    	}
    	maxsiz[x] = std::max(maxsiz[x], sum - siz[x]);
    	if(maxsiz[x] < maxsiz[root]) root = x;
    }
    void build(int x) {
    	vis[x] = true;
    	for(node *i = head[x]; i; i = i->nxt) {
    		if(vis[i->to]) continue;
    		root = 0, sum = siz[i->to];
    		getroot(i->to, 0);
    		u[root] = x;
    		build(root);
    	}
    }	
    void build() {
    	maxsiz[0] = sum = n;
    	getroot(1, 0);
    	rt = root;
    	build(root);
    }
    void dfs(int x, int fa) {
    	dep[x] = dep[fa] + 1;
    	f[x][0] = fa;
    	for(node *i = head[x]; i; i = i->nxt) {
    		if(i->to == fa) continue;
    		dfs(i->to, x);
    		d[i->to][0] = i->dis;
    	}
    }
    void beizeng() {
    	dfs(1, 0);
    	for(int j = 1; j <= 24; j++) 
    		for(int i = 1; i <= n; i++) {
    			f[i][j] = f[f[i][j - 1]][j - 1];
    			d[i][j] = d[f[i][j - 1]][j - 1] + d[i][j - 1];
    		}
    }
    LL LCA(int x, int y) {
    	LL D = 0;
    	if(dep[x] < dep[y]) std::swap(x, y);
    	for(int i = 24; i >= 0; i--) if(dep[f[x][i]] >= dep[y]) D += d[x][i], x = f[x][i];
    	if(x == y) return D;
    	for(int i = 24; i >= 0; i--) if(f[x][i] != f[y][i]) D += d[x][i] + d[y][i], x = f[x][i], y = f[y][i];
    	return D + d[x][0] + d[y][0];
    }
    
    bool cmp(const int &a, const int &b) { return val[a] < val[b]; }
    void predoit() {
    	static int id[maxn];
    	for(int i = 1; i <= n; i++) id[i] = i;
    	std::sort(id + 1, id + n + 1, cmp);
    	for(int i = 1; i <= n; i++) {
    		int now = id[i];
    		//当前点的年龄,dis前缀和
    		age[now].push_back(val[now]);
    		dis[now].push_back(dis[now].empty()? 0 : dis[now].back());
    		for(int o = now; u[o]; o = u[o]) {
    			LL D = LCA(now, u[o]);
    			//当前点父亲的年龄,dis前缀和
    			age[u[o]].push_back(val[now]);
    			dis[u[o]].push_back(dis[u[o]].empty()? D : dis[u[o]].back() + D);
    			//当前点对父亲的贡献,同样维护
    			tofaage[o].push_back(val[now]);
    			tofadis[o].push_back(tofadis[o].empty()? D : tofadis[o].back() + D);
    		}
    	}
    }
    
    LL calc(int pos, LL L, LL R) {
    	LL posl, posr;
    	LL ans;
    	//初始为pos子树自己的贡献
    	posl = std::lower_bound(age[pos].begin(), age[pos].end(), L) - age[pos].begin() - 1;
    	posr = std::upper_bound(age[pos].begin(), age[pos].end(), R) - age[pos].begin() - 1;
    	//注意卡边界
    	if(R < age[pos].front() || L > age[pos].back()) ans = 0;
    	else if(posl == -1) ans = dis[pos][posr];
    	else ans = dis[pos][posr] - dis[pos][posl];
    	for(int o = pos; u[o]; o = u[o]) {
    		LL tot, fal, far;
    		//统计ans在age里二分,在dis里收集答案
    		posl = std::lower_bound(tofaage[o].begin(), tofaage[o].end(), L) - tofaage[o].begin() - 1;
    		posr = std::upper_bound(tofaage[o].begin(), tofaage[o].end(), R) - tofaage[o].begin() - 1;
    		
    		if(R < tofaage[o].front() || L > tofaage[o].back()) tot = 0;
    		else if(posl == -1) tot = tofadis[o][posr];
    		else tot = tofadis[o][posr] - tofadis[o][posl];
    		//这个减去的是自己对父亲的贡献,现在要统计父亲的其他子树的贡献,自己的贡献会重(父亲子树的贡献-自己子树的贡献=兄弟子树的贡献(对父亲的贡献))
    		ans -= tot;
    
    		LL D = LCA(u[o], pos);
    		fal = std::lower_bound(age[u[o]].begin(), age[u[o]].end(), L) - age[u[o]].begin() - 1;
    		far = std::upper_bound(age[u[o]].begin(), age[u[o]].end(), R) - age[u[o]].begin() - 1;
    
    		if(R < age[u[o]].front() || L > age[u[o]].back()) tot = 0;
    		else if(fal == -1) tot = dis[u[o]][far];
    		else tot = dis[u[o]][far] - dis[u[o]][fal];
    		//父亲子树到父亲的贡献,因为上面已经减去了那部分,所以不会重复
    		ans += tot;
    		//上面的是父亲其他子树到父亲的贡献,这是其中一段距离,我们的目的是他们到pos的距离,所以还差父亲到pos的距离
    		ans += ((far - fal) - (posr - posl)) * D;
    	}
    	return ans;
    }			
    void query() {
    	LL pos, a, b, L, R, ans = 0;
    	while(Q --> 0) {
    		pos = in(), a = in(), b = in();
    		L = std::min((a + ans) % A, (b + ans) % A), R = std::max((a + ans) % A, (b + ans) % A);
    		ans = calc(pos, L, R);
    		printf("%lld
    ", ans);
    	}
    }
    int main() { 
    	init();
    	build();
    	beizeng();
    	predoit();
    	query();
    	return 0;
    }
    
  • 相关阅读:
    【BZOJ 4151 The Cave】
    【POJ 3080 Blue Jeans】
    【ZBH选讲·树变环】
    【ZBH选讲·拍照】
    【ZBH选讲·模数和】
    【CF Edu 28 C. Four Segments】
    【CF Edu 28 A. Curriculum Vitae】
    【CF Edu 28 B. Math Show】
    【CF Round 439 E. The Untended Antiquity】
    【CF Round 439 C. The Intriguing Obsession】
  • 原文地址:https://www.cnblogs.com/olinr/p/10274348.html
Copyright © 2011-2022 走看看