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;
    }
    
  • 相关阅读:
    分页SQL 和Oracle 存储过程
    什么是SilverLight
    opendpi 源码分析(一)
    Multiway arrays
    循环链表
    轮询算法 这是一个印度人写的,学习下。 来自 codeproject
    Friday the Thirteenth
    通过命令行指定监听的IP和端口
    pthread_key_t
    贝叶斯网络 未学习前数据结构
  • 原文地址:https://www.cnblogs.com/olinr/p/10274348.html
Copyright © 2011-2022 走看看