4.3 省选模拟赛 采蘑菇 点分治

给出一棵树 每个点都有一个颜色ci 问 从i号点出发到任意一个点的路径上本质不同的颜色之和。

\(n\leq 300000\)

光线性扫描时不行的 显然有\(n^2\)的暴力。

考虑树是一条链的时候怎么做? 可以发现先求出1的答案然后维护换根的过程 记录每个点颜色的pre 前驱 nex后继很容易通过分类讨论得到答案。

考虑树的时候怎么做?还是维护换根的过程 当两个点颜色相同的时候 答案显然一样,当不同的时候 可以分析得出 要查自己子树内没有自己父亲颜色的祖先的节点个数。还要查 自己子树之外没有自己的颜色所庇护的节点个数。

第一个很好查 预处理一下祖先庇护了哪写节点 经过祖先的话就下方这些节点 通过线段树维护dfs序很容易区间求和。

考虑第二个由于在不断换根的过程中 有很多节点可以庇护自己子树之外的节点 这个是存在轮换关系的 但是考虑和上一步的下方关系并不容易合并 或者说合并非常的困难所以这个做法是不成立的。

考试的时候我只是关心了 一下换根第一个步应该怎么做 却没有长远的眼光看到换到若干步之后思路的错误。

果然 换根主要解决的问题 是自己父亲那边的处理问题 这点尤其重要。

只能换个思路了 。事实上对于树上路径信息统计问题 或者说对于第一步暴力的优化 有一个非常有效的做法 点分治。

我们可以尝试利用点分治来维护刚才的暴力的过程 从而求解答案。

具体过程:考虑颜色数较少的情况 对于每一种颜色单独点分治一次。

可以发现对于非当前分治重心的点来说 对于当前颜色 dfs统计一下其到分治重心的路径上出现这种颜色了没有 如果出现了 显然这种颜色的贡献为分治大小-这个点在当前分治重心下的子树大小 考虑没有出现 显然是其他子树内点到到分治重心出现这种颜色的点的个数-自己子树中的这样的点的个数。

考虑分治重心 显然是所有子树内到分治重心出现这种颜色的点的个数。

可以发现这个做法 可以拓展到多种颜色上 在点分治的时候预处理一下第二种贡献再进行计算即可。

换根的代码(虽然是错误的 但是只是换根的时候父亲往上的那部分点没有办法在O(logn)的时间内处理罢了。

const int MAXN=300010;
int n,len,cnt,top,id;ll ans1[MAXN],ans;
int a[MAXN],root[MAXN],dfn[MAXN],sz[MAXN],c[MAXN];
int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1];
struct wy{int l,r;int sum;}t[MAXN*30];
vector<int>g[MAXN];
inline void add(int x,int y)
{
	ver[++len]=y;
	nex[len]=lin[x];
	lin[x]=len;
}
inline void dfs(int x,int fa)
{
	sz[x]=1;dfn[x]=++cnt;
	int ww=c[a[x]];
	c[a[x]]=x;g[ww].pb(x);
	if(!ww)++ans;ans1[1]+=ans;
	go(x)
	{
		if(tn==fa)continue;
		dfs(tn,x);
		sz[x]+=sz[tn];
	}
	c[a[x]]=ww;if(!ww)--ans;
}
inline void change(int &p,int l,int r,int x,int w)
{
	if(!p)p=++id;
	if(l==r){sum(p)+=w;return;}
	int mid=(l+r)>>1;
	if(x<=mid)change(l(p),l,mid,x,w);
	else change(r(p),mid+1,r,x,w);
	sum(p)=sum(l(p))+sum(r(p));
}
inline int ask(int p,int l,int r,int L,int R)
{
	if(L>R)return 0;
	if(!p)return 0;
	if(L<=l&&R>=r)return sum(p);
	int mid=(l+r)>>1;
	if(L>mid)return ask(r(p),mid+1,r,L,R);
	if(R<=mid)return ask(l(p),l,mid,L,R);
	return ask(l(p),l,mid,L,R)+ask(r(p),mid+1,r,L,R);
}
inline void dp(int x,int fa)
{
	change(root[a[x]],1,n,dfn[x],-sz[x]);
	for(ui i=0;i<g[x].size();++i)
	{
		int tn=g[x][i];
		change(root[a[tn]],1,n,dfn[tn],sz[tn]);
	}
	go(x)
	{
		if(tn==fa)continue;
		if(a[tn]==a[x])ans1[tn]=ans1[x];
		else
		{
			ans1[tn]=ans1[x]-(sz[tn]-ask(root[a[x]],1,n,dfn[tn],dfn[tn]+sz[tn]-1));
			ans1[tn]=ans1[tn]+(n-sz[tn]-ask(root[a[tn]],1,n,1,dfn[tn]-1)-ask(root[a[tn]],1,n,dfn[tn]+sz[tn],n));
			//if(tn==3)put(ask(root[a[tn]],1,n,dfn[tn]+sz[tn],n));
		}
		dp(tn,x);
	}
	change(root[a[x]],1,n,dfn[x],sz[x]);
	for(ui i=0;i<g[x].size();++i)
	{
		int tn=g[x][i];
		change(root[a[tn]],1,n,dfn[tn],-sz[tn]);
	}
}
int main()
{
	freopen("1.in","r",stdin);
	freopen("2.out","w",stdout);
	get(n);
	rep(1,n,i)get(a[i]);
	rep(1,n-1,i)
	{
		int x,y;
		get(x);get(y);
		add(x,y);add(y,x);
	}
	dfs(1,0);//putl(ans1[1]);
	for(ui i=0;i<g[0].size();++i)
	{
		change(root[a[g[0][i]]],1,n,dfn[g[0][i]],sz[g[0][i]]);
		//put(g[0][i]);
	}
	dp(1,0);rep(1,n,i)putl(ans1[i]);return 0;
}

点分治的代码

const int MAXN=300010;
int n,len,cnt,id,top,maxx,rt,tg;
int a[MAXN],sz[MAXN],son[MAXN],vis[MAXN],c[MAXN];
int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1];
ll ans1[MAXN],s[MAXN],ans,sum;
inline void add(int x,int y)
{
	ver[++len]=y;
	nex[len]=lin[x];
	lin[x]=len;
}
inline void get_root(int x,int fa)
{
	sz[x]=1;son[x]=0;
	go(x)
	{
		if(tn==fa||vis[tn])continue;
		get_root(tn,x);
		sz[x]+=sz[tn];
		son[x]=max(son[x],sz[tn]);
	}
	son[x]=max(son[x],maxx-sz[x]);
	if(son[x]<son[rt])rt=x;
}
inline void dfs(int x,int fa,int v)
{
	int ww=c[a[x]];
	if(!c[a[x]]){c[a[x]]=x;s[a[x]]+=sz[x]*v;ans+=sz[x]*v;}
	go(x)if(tn!=fa&&!vis[tn])dfs(tn,x,v);
	if(!ww)c[a[x]]=ww;
}
inline void dp(int x,int fa)
{
	int ww=c[a[x]];
	if(!c[a[x]]){c[a[x]]=x;sum+=cnt;ans-=s[a[x]];}
	ans1[x]+=sum+cnt;ans1[x]+=ans;
	go(x)if(tn!=fa&&!vis[tn])dp(tn,x);
	if(!ww){c[a[x]]=ww;sum-=cnt;ans+=s[a[x]];}
}
inline void solve(int x)
{
	get_root(x,0);c[a[x]]=x;vis[x]=1;
	go(x)if(!vis[tn])dfs(tn,x,1);
	ans1[x]+=sz[x]+ans;cnt=sz[x];
	go(x)
	{
		if(vis[tn])continue;
		dfs(tn,x,-1);cnt-=sz[tn];
		dp(tn,x);cnt+=sz[tn];
		dfs(tn,x,1);
	}
	go(x)if(!vis[tn])dfs(tn,x,-1);
	c[a[x]]=0;
	go(x)
	{
		if(vis[tn])continue;
		rt=0;maxx=sz[tn];
		get_root(tn,0);
		solve(rt);
	}
}
int main()
{
	freopen("1.in","r",stdin);
	freopen("2.out","w",stdout);
	get(n);
	rep(1,n,i)get(a[i]);
	rep(1,n-1,i)
	{
		int x,y;
		get(x);get(y);
		add(x,y);add(y,x);
	}
	rt=0;son[0]=n+1;maxx=n;get_root(1,0);
	solve(rt);rep(1,n,i)putl(ans1[i]);
	return 0;
}

其实核心就是分两步讨论。

原文地址:https://www.cnblogs.com/chdy/p/12627816.html

时间: 2024-10-28 03:59:56

4.3 省选模拟赛 采蘑菇 点分治的相关文章

@省选模拟赛03/16 - T3@ 超级树

目录 @description@ @solution@ @accepted code@ @details@ @description@ 一棵 k-超级树(k-SuperTree) 可按如下方法得到:取一棵深度为 k 的满二叉树,对每个节点向它的所有祖先连边(如果这条边不存在的话). 例如,下面是一个 4-超级树: 请统计一棵 k-超级树 中有多少条不同的简单有向路径,对 mod 取模. input 一行两整数 k, mod. output 一行一整数表示答案. example input1: 2

4.3 省选模拟赛 石子游戏 树上博弈

注意观察题目 每个点都只能将石子给自己的两个儿子 且石子个数>=1. 显然 这是一个阶梯NIM. 只有和最后一层的奇偶性相同的层才会有贡献 证明也很显然. 那么这其实就是近乎NIM游戏了 胜负自然取决于所有有贡献的石子堆的异或和. 但是 上午我傻了的一点 没有分清SG函数和NIM游戏的联系. 在NIM游戏中SG函数其实就是每个有贡献的石子堆的石子数. 再来看这道题 由于异或和一定 暴力枚举移动哪一堆石子 判断是否可行即可. 这个操作其实是 NIM游戏的证明问题了.解决的方案是 观察一下移动后造成

2018.3.10 省选模拟赛

从这里开始 概况 Problem A 三元组 Problem B 攻略 Problem C 迂回 概况 这是省选T1合集?还是欢乐AK赛? 全班一半以上的人三道题都会做qwq. Doggu还剩一小时时以为自己AK了,然后玩了一小时.虽然最终被卡了20分的常数. ZJC 1个半小时AK?Excuse me? 我这条大咸鱼到最后10分钟才敲完了T1,然后发现线段树要T掉. 发自内心鄙视垃圾出题人卡常数,本来的欢乐AK变成280. 教练给我们考4个小时的试,题面上也这么写的,看题解,woc,考试时间3

2018.2.12 省选模拟赛

题目大意 (题目很简洁了,不需要大意) 其实显而易见地可以发现,当被卡一次后后面的路程都是固定了的. 可以用类似动态规划的思想来进行预处理.现在的问题就是怎么知道在某个位置刚等完红灯然后出发会在哪个路口再次被卡. 尝试画一画图: 其中横轴表示位置,纵轴表示时间,长方体表示红灯时段.有用的部分长度只有$r + g$,所以在模意义下弄一下就可以减少很多重复和无用状态: 但是这样仍然不好处理上面提到的问题,考虑让线段横着走,第一个撞着的长方形就是答案.为了实现这个目标,就每个长方形向下移动一段(移动的

2018/3/9 省选模拟赛 0分

第二题模拟扫一遍就可以过,不能更划算了.q=1的30分写的比100分还麻烦,有趣哦. 破暴力40分也没什么可写了,日常辣鸡吃枣药丸. 原文地址:https://www.cnblogs.com/137shoebills/p/8533870.html

2018/3/29 省选模拟赛 80

我真是太菜了... T1 10分纯暴力没写,20分容斥也没写(人就是懒死的).还有30分矩乘不会 正解 <IOI2018 中国国家集训队第一阶段作业题解部分 - 杭州第二中学 吴瑾昭.pdf>最后一题 T2  以为自己能拿到50分,但是其实那个暴力算法只能过10分的点,n=2000的20分数据用n^3带剪枝过不了,所以拿了30分. 正解 <计数与期望问题选讲 CLJ.pdf>最后一题 我记得我以前好像看到过这个文档但是没读过..今天读一下 T3 依然是分情况50分,30分树归20分

xjoi省选模拟赛_14

T1 发现 对于当前 投出 奇数次向上的概率为 p 那么 若加入一个 pi=0.5 的骰子 发现  p奇 +1=p奇 * 0.5+p偶 * 0.5 = p偶+1  也就是说 只要方案中存在一个 p=0.5 的骰子 这个方案必然合法  : 1 #include <bits/stdc++.h> 2 #define N 100 3 #define eps 1e-7 4 using namespace std; 5 typedef long double ldb; 6 int t,n; 7 ldb A

2018/3/27 省选模拟赛 140分

T1 树归 100 T2 写的快速幂卷积 40,超时了,正解是矩阵乘法之类的. 正解 1 暴力(m<=5):将x的所有约数提出来矩阵乘法 2 3 定义乘法同构: 4 A=p[1]^a[1] * p[2]^a[2] * ... * p[n]^a[n] 5 B=q[1]^b[1] * q[2]^b[2] * ... * q[n]^b[n] 6 其中p[i]与q[i]皆为质数 7 将数组a与b降序排序后如果是完全相同的,那么称A与B是乘法同构的 8 如 2*2*2*2*3*3*5 与 7*11*11*

省选模拟赛20180416

上午 T1 提答送了40分拿了20. T2 费用流,不会 暴力骗分40 T3 dp变成四个方向的转移优化掉一个m^2 不会 暴力分都没拿全只有50 下午 T1 dp+扫描线,不会扫描线,60分暴力 T2  40分二分,100分要找规律不会找,二分写炸了只有20分. 原文地址:https://www.cnblogs.com/137shoebills/p/8858110.html