P5002 专心OI

P5002 专心OI - 找祖先

给定一棵有根树((n leq 10000)),(M leq 50000) 次询问, 求以 (x)(LCA) 的点对个数


错误日志: 看下面


Solution

设点 (u) 的子树大小为 (size[u])
现询问以 (u)(LCA) 的点对个数
(u) 的子节点为 (v_{1}, v_{2},...,v_{m})
我们现在统计点对中一个点在 (v_{1}) 内的答案数
一个点在 (v_{1}) 内, 另一个点要么是 (u) 要么在剩下的子节点子树内选一个
所以总数为 (size[u] - size[v_{1}])
乘法原理 (v_{1}) 子树中所有答案为 (size[v] * (size[u] - size[v]))
每个子节点点都是这样
最后加上一个 ((u, u)) 即可

然后这样 (85pnts)
看数据范围, 最不优情况 (O(NM))
然后发现 (M > N)
询问次数多于点个数?
这提示我们像记忆化那样记下答案
回头想想这样的复杂度为 (O(N)) , 这是一棵树, 边数和点数不会差太多, 复杂度有保障

Code

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define LL long long
#define REP(i, x, y) for(LL i = (x);i <= (y);i++)
using namespace std;
LL RD(){
    LL out = 0,flag = 1;char c = getchar();
    while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
    while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
    return flag * out;
    }
const LL maxn = 200019,INF = 1e9 + 19, M = 1e9 + 7;
LL head[maxn],nume = 1;
struct Node{
    LL v,dis,nxt;
    }E[maxn << 3];
void add(LL u,LL v,LL dis){
    E[++nume].nxt = head[u];
    E[nume].v = v;
    E[nume].dis = dis;
    head[u] = nume;
    }
LL num, root, na;
LL size[maxn], fa[maxn];
LL mem[maxn];
void dfs(LL u, LL F){
	size[u] = 1;
	for(LL i = head[u];i;i = E[i].nxt){
		LL v = E[i].v;
		if(v == F)continue;
		fa[v] = u;
		dfs(v, u);
		size[u] = (size[u] + size[v]) % M;
		}
	}
void init(){
	num = RD(), root = RD(), na = RD();
	REP(i, 1, num - 1){
		LL u = RD(), v = RD();
		add(u, v, 1), add(v, u, 1);
		}
	dfs(root, -1);
	}
void solve(){
	while(na--){
		LL ans = 0, u = RD(), temp = size[u];
		if(mem[u]){printf("%lld
", mem[u]);continue;}
		for(LL i = head[u];i;i = E[i].nxt){
			LL v = E[i].v;
			if(v == fa[u])continue;
			ans = (ans + ((size[v] * (temp - size[v])) % M + M) % M) % M;
			temp = ((temp - size[v]) % M + M) % M;
			}
		ans = (ans * 2 + 1) % M;
		mem[u] = ans;
		printf("%lld
", ans);
		}
	}
int main(){
	init();
	solve();
	return 0;
	}
原文地址:https://www.cnblogs.com/Tony-Double-Sky/p/9910735.html