JZOJ5956. 【NOIP2018模拟11.7A组】easy LCA

12 篇文章 0 订阅
5 篇文章 0 订阅

题意:

数据范围:

Analysis:

辣鸡出题人,卡你tm的常。
发现询问的区间很多,往分治上想想。
考虑从中间往两边做一个前缀LCA,后缀LCA。然后一个跨越中点的序列LCA就能前后缀拼接。
发现两边的前缀后缀LCA,一定会形成两条链,依次考虑右边每个点的贡献,发现我们只需要求出两条链相交的LCA,然后扫一遍得出答案,我们需要 O ( 1 ) O(1) O(1)求LCA,用欧拉序预处理 r m q rmq rmq即可。那么复杂度就做到了 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)。于是你被卡常了。
有一种常数较小的做法,任意排列的LCA,一定是相邻两个数LCA中深度最小的,这个随意证证就好。
那么对于排列 p p p求一次相邻点的LCA,那么长度变为 n − 1 n-1 n1。那么一个区间贡献就是里面深度最小的,这个可以用单调栈求出两边第一个小于或小于等于(这是细节,避免算重)它的。
然后算答案即可。
附上两种做法代码:
分治:

# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
const int N = 6e5 + 5;
typedef long long ll;
int st[N],to[N << 1],nx[N << 1];
int d[N],f[N << 1][25],id[N << 1];
int p[N],w[N],b[N],lg[N << 1];
int n,tot;
ll ans;
inline int read()
{
	int x = 0; char ch = getchar();
	for (; ch < '0' || ch > '9' ; ch = getchar());
	for (; ch >= '0' && ch <= '9' ; ch = getchar()) x = x * 10 + ch - '0';
	return x;
}
inline void add(int u,int v)
{
	to[++tot] = v,nx[tot] = st[u],st[u] = tot;
	to[++tot] = u,nx[tot] = st[v],st[v] = tot;
}
int com(int x,int y) { return d[x] > d[y] ? y : x; }
inline void dfs(int x,int fr)
{
	d[x] = d[fr] + 1,id[x] = ++tot,f[tot][0] = x;
	for (int i = st[x] ; i ; i = nx[i])
	if (to[i] != fr) dfs(to[i],x),f[++tot][0] = x;
}
inline int find(int x,int y)
{
	int l = id[x],r = id[y];
	if (l > r) swap(l,r);
	int k = lg[r - l + 1];
	return com(f[l][k],f[r - (1 << k) + 1][k]);
}
inline void solve(int l,int r)
{
	if (l == r) { ans += d[p[l]]; return; }
	int mid = (l + r) >> 1,h = r - mid,h1 = mid - l + 1,top = 0; w[1] = p[mid + 1],b[1] = p[mid];
	for (int i = mid + 2 ; i <= r ; ++i) w[i - mid] = find(w[i - mid - 1],p[i]);
	for (int i = mid - 1 ; i >= l ; --i) b[mid + 1 - i] = find(b[mid - i],p[i]);
	int lca = find(p[mid],p[mid + 1]),num = 0; ll ret = 0,all = 0;
	for (int i = 1 ; i <= h1 ; ++i)
	{
		if (d[b[i]] >= d[lca]) ++num; else ret += d[b[i]];
		all += d[b[i]];
	}
	int pos = h + 1,z = 1;
	for (int i = 1 ; i <= h ; ++i)
	if (d[w[i]] >= d[lca]) ans += (ll)num * d[lca] + ret;
	else { pos = i,num = 0; break; }
	for (int i = pos ; i <= h ; ++i)
	{
		while (z <= h1 && d[b[z]] >= d[w[i]]) all -= d[b[z]],++z,++num;
		ans += (ll)num * d[w[i]] + all;
	}
	solve(l,mid);
	solve(mid + 1,r);
}
int main()
{
	freopen("easy.in","r",stdin);
	freopen("easy.out","w",stdout);
	scanf("%d",&n);
	for (int i = 1 ; i < n ; ++i) add(read(),read());
	tot = 0,dfs(1,0);
	for (int i = 2 ; i <= tot ; ++i) lg[i] = lg[i >> 1] + 1;
	for (int j = 1 ; j <= lg[tot] ; ++j)
		for (int i = 1 ; i + (1 << j) - 1 <= tot ; ++i) f[i][j] = com(f[i][j - 1],f[i + (1 << j - 1)][j - 1]);
	for (int i = 1 ; i <= n ; ++i) p[i] = read();
	solve(1,n);
	printf("%lld\n",ans);
	return 0;
}

单调栈:

# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
const int N = 6e5 + 5;
typedef long long ll;
int st[N],to[N << 1],nx[N << 1],L[N],R[N];
int top[N],sta[N],p[N],son[N],siz[N],d[N],fa[N];
int n,tot;
ll ans;
inline int read()
{
	int x = 0; char ch = getchar();
	for (; ch < '0' || ch > '9' ; ch = getchar());
	for (; ch >= '0' && ch <= '9' ; ch = getchar()) x = x * 10 + ch - '0';
	return x;
}
inline void add(int u,int v)
{
	to[++tot] = v,nx[tot] = st[u],st[u] = tot;
	to[++tot] = u,nx[tot] = st[v],st[v] = tot;
}
inline void dfs(int x)
{
	d[x] = d[fa[x]] + 1,siz[x] = 1;
	for (int i = st[x] ; i ; i = nx[i])
	if (to[i] != fa[x])
	{
		fa[to[i]] = x,dfs(to[i]),siz[x] += siz[to[i]];
		if (siz[to[i]] > siz[son[x]]) son[x] = to[i];
	}
}
inline void dfs1(int x,int t)
{
	top[x] = t;
	if (son[x]) dfs1(son[x],t);
	for (int i = st[x] ; i ; i = nx[i])
	if (to[i] != fa[x] && to[i] != son[x]) dfs1(to[i],to[i]);
}
inline int find(int x,int y)
{
	while (top[x] != top[y])
	{
		if (d[top[x]] < d[top[y]]) swap(x,y);
		x = fa[top[x]];
	} return d[x] > d[y] ? y : x;
}
int main()
{
	freopen("easy.in","r",stdin);
	freopen("easy.out","w",stdout);
	scanf("%d",&n);
	for (int i = 1 ; i < n ; ++i) add(read(),read());
	dfs(1);
	dfs1(1,1);
	for (int i = 1 ; i <= n ; ++i) p[i] = read(),ans += d[p[i]];
	for (int i = n ; i > 1 ; --i) p[i] = find(p[i],p[i - 1]);
	for (int i = 1 ; i < n ; ++i) p[i] = p[i + 1];
	sta[tot = 0] = 0,--n;
	for (int i = 1 ; i <= n ; ++i)
	{
		while (tot && d[p[i]] < d[p[sta[tot]]]) --tot;
		L[i] = sta[tot] + 1,sta[++tot] = i;
	} sta[tot = 0] = n + 1;
	for (int i = n ; i ; --i)
	{
		while (tot && d[p[i]] <= d[p[sta[tot]]]) --tot;
		R[i] = sta[tot] - 1,sta[++tot] = i;
	}
	for (int i = 1 ; i <= n ; ++i) ans += (ll)(R[i] - i + 1) * (i - L[i] + 1) * d[p[i]];
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值