HAOI 2016 地图 题解

题目传送门

题目大意: 给一张仙人掌图,多组询问,每次给出一个点,询问封掉点 1 1 到这个点的所有路径后,从这个点出发能吃到的拉面的油腻度中吃了奇数次或偶数次的油腻度有多少种。

题解

纪念一下看错题后乱JB敲出来的代码(放心,这东西和题解没有半毛钱关系)。

#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
#define maxn 200010

int n,m,q,nn,a[maxn];
struct V_E{
	struct edge{int y,type,id,next;};
	edge e[maxn<<1];
	int first[maxn],len;
	void buildroad(int x,int y,int type=0,int id=0)
	{
		e[++len]=(edge){y,type,id,first[x]};
		first[x]=len;
	}
}T1,T2,T3;
int tr[maxn<<3],odd[maxn],even[maxn];
int lowbit(int x){return x&(-x);}
void tr_add(int *tree,int x,int y){for(;x<=1000000;x+=lowbit(x))tree[x]+=y;}
int tr_sum(int *tree,int x){int p=0;for(;x>=1;x-=lowbit(x))p+=tree[x];return p;}
int tr_ask(int *tree,int x){return tr_sum(tree,x)-tr_sum(tree,x-1);}
void add(int x)
{
	int p=tr_ask(tr,x); tr_add(tr,x,1);
	if(p==0)tr_add(odd,x,1);
	else if(p%2==0)tr_add(even,x,-1),tr_add(odd,x,1);
	else tr_add(odd,x,-1),tr_add(even,x,1);
}
void del(int x)
{
	int p=tr_ask(tr,x); tr_add(tr,x,1);
	if(p==1)tr_add(odd,x,-1);
	else if(p%2==0)tr_add(even,x,-1),tr_add(odd,x,1);
	else tr_add(odd,x,-1),tr_add(even,x,1);
}
int dfn[maxn],low[maxn],id=0,fa[maxn],belong[maxn];
void solve(int x,int y)
{
	int now=y; nn++;
	while(now!=fa[x])belong[now]=nn,T2.buildroad(now,nn),T2.buildroad(nn,now),now=fa[now];
}
void tarjan(int x)
{
	dfn[x]=low[x]=++id;
	for(int i=T1.first[x];i;i=T1.e[i].next)
	{
		int y=T1.e[i].y; if(y==fa[x])continue;
		if(!dfn[y])
		{
			fa[y]=x; tarjan(y);
			if(low[y]<low[x])low[x]=low[y];
		}
		else if(dfn[y]<low[x])low[x]=dfn[y];
		if(low[y]<=dfn[x])continue;
		T2.buildroad(x,y);T2.buildroad(y,x);
	}
	for(int i=T1.first[x];i;i=T1.e[i].next)
	{
		int y=T1.e[i].y; if(fa[y]==x||dfn[y]<dfn[x])continue;
		solve(x,y);
	}
}
int ans[maxn];
bool vis[maxn];
void work(int x,int type)
{
	if(vis[x])return;
	if(belong[x])
	{
		x=belong[x]; vis[x]=true;
		for(int i=T2.first[x];i;i=T2.e[i].next)
		vis[T2.e[i].y]=true,del(a[T2.e[i].y]);
		if(type==1)
		{
			for(int i=T2.first[x];i;i=T2.e[i].next)
			{
				for(int i=T3.first[T2.e[i].y];i;i=T3.e[i].next)
				if(T3.e[i].type==0)ans[T3.e[i].id]=tr_sum(even,T3.e[i].y);
				else ans[T3.e[i].y]=tr_sum(odd,T3.e[i].y);
			}
		}
	}
	else
	{
		del(a[x]); vis[x]=true;
		if(type==1)
		{
			for(int i=T3.first[x];i;i=T3.e[i].next)
			if(T3.e[i].type==0)ans[T3.e[i].id]=tr_sum(even,T3.e[i].y);
			else ans[T3.e[i].y]=tr_sum(odd,T3.e[i].y);
		}
	}
}
void go(int x,int father)
{
	work(x,1);
	for(int i=T2.first[x];i;i=T2.e[i].next)
	if(T2.e[i].y!=father)go(T2.e[i].y,x);
	work(x,-1);
}
struct par{
	int x,y;
	par(int xx,int yy):x(xx),y(yy){}
	bool operator <(const par &b)const{return x==b.x?(y<b.y):(x<b.x);}
};
map<par,bool> Map;

int main()
{
	scanf("%d %d",&n,&m); nn=n;
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),add(a[i]);
	for(int i=1,x,y;i<=m;i++)
	{
		scanf("%d %d",&x,&y);
		if(!Map[par(x,y)])T1.buildroad(x,y),T1.buildroad(y,x),Map[par(x,y)]=true;
	}
	scanf("%d",&q);
	for(int i=1,type,x,y;i<=q;i++)
	{
		scanf("%d %d %d",&type,&x,&y);
		T3.buildroad(x,y,type,i);
	}
	tarjan(1); go(1,0);
	for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
}

真好看不是吗qwq

发现这张图是一张仙人掌,那么自然想到以点 1 1 为根造一棵圆方树,然后题目就转化成了询问某个点子树内出现次数为奇数次或偶数次的油腻度次数。

好像突然就简单了很多?

拉一拉 dsu on tree 的板子即可。在统计的时候开一个一百万大小的树状数组维护一下每种颜色的出现次数,再开两个树状数组分别维护每种颜色出现了偶数次还是奇数次,理论时间复杂度 O ( n l o g 2 n ) O(nlog^2n)

由于常数有点大所以跑的并没有 O ( n n ) O(n\sqrt n) 的分块快qaq,但是吸口氧比原来快很多,能跑出 1.8 s 1.8s 的好成绩~

还有什么不懂的就看代码吧,注释很详细呢。

updata 当日: 突然想到那个一百万大小的树状数组我居然在用来改点求点??所以那个可以直接用数组代替……然后吸了氧就跑到 1.5 s 1.5s 了!

代码如下:

#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
#define maxn 1000010

int n,m,q,nn,a[maxn];
struct par{
	int x,y;
	par(int xx,int yy):x(xx),y(yy){}
	bool operator <(const par &b)const{return x==b.x?(y<b.y):(x<b.x);}
};
map<par,bool> Map;
struct V_E{
	struct edge{int y,type,id,next;};//type和id只有T3用到,下面会讲 
	edge e[maxn<<1];
	int first[maxn],len;
	void buildroad(int x,int y,int type=0,int id=0)
	{
		e[++len]=(edge){y,type,id,first[x]};
		first[x]=len;
	}
}T1,T2,T3;
//T1是原图,T2记录圆方树,T3用来记录询问
//T3中的y存的就是询问的y,id存这是哪个询问,type存 问的是奇数还是偶数 
int times[maxn],odd[maxn],even[maxn];
//times记录出现次数,odd和even是树状数组
//如果一个油腻度出现奇数次,那么odd对应位置+1,偶数次就让even对应位置+1
//询问小于等于某个油腻度有多少种油腻度出现奇数或偶数次是求个前缀和即可 
int lowbit(int x){return x&(-x);}
void tr_add(int *tree,int x,int y){for(;x<=1000000;x+=lowbit(x))tree[x]+=y;}//树状数组单点修改 
int tr_sum(int *tree,int x){int p=0;for(;x>=1;x-=lowbit(x))p+=tree[x];return p;}//树状数组求前缀和 
int tr_ask(int *tree,int x){return tr_sum(tree,x)-tr_sum(tree,x-1);}//树状数组单点求值 
void add(int x)//油腻度x出现次数+1 
{
	if(!x)return;
	times[x]++;
	if(times[x]==1)tr_add(odd,x,1);
	else if(times[x]%2==1)tr_add(even,x,-1),tr_add(odd,x,1);
	else tr_add(odd,x,-1),tr_add(even,x,1);
}
void del(int x)//油腻度x出现次数-1 
{
	if(!x)return;
	times[x]--;
	if(times[x]==0)tr_add(odd,x,-1);
	else if(times[x]%2==1)tr_add(even,x,-1),tr_add(odd,x,1);
	else tr_add(odd,x,-1),tr_add(even,x,1);
}
int dfn[maxn],low[maxn],id=0,fa[maxn],belong[maxn];
void solve(int x,int y)//圆方树板子 
{
	int now=y; nn++;
	while(now!=fa[x])belong[now]=nn,T2.buildroad(now,nn),T2.buildroad(nn,now),now=fa[now];
}
void tarjan(int x)//圆方树板子 
{
	dfn[x]=low[x]=++id;
	for(int i=T1.first[x];i;i=T1.e[i].next)
	{
		int y=T1.e[i].y; if(y==fa[x])continue;
		if(!dfn[y])
		{
			fa[y]=x; tarjan(y);
			if(low[y]<low[x])low[x]=low[y];
		}
		else if(dfn[y]<low[x])low[x]=dfn[y];
		if(low[y]<=dfn[x])continue;
		T2.buildroad(x,y);T2.buildroad(y,x);
	}
	for(int i=T1.first[x];i;i=T1.e[i].next)
	{
		int y=T1.e[i].y; if(fa[y]==x||dfn[y]<dfn[x])continue;
		solve(x,y);
	}
}
int size[maxn],mson[maxn];
int ans[maxn];
void dfs1(int x,int fa)//找重儿子 
{
	size[x]=1;
	for(int i=T2.first[x];i;i=T2.e[i].next)
	{
		int y=T2.e[i].y; if(y==fa)continue;
		dfs1(y,x); size[x]+=size[y];
		if(size[y]>size[mson[x]])mson[x]=y;
	}
}
void go(int x,int fa,bool tf)//加上/删去 以x为根的子树的贡献 
{
	if(tf)add(a[x]); else del(a[x]);
	for(int i=T2.first[x];i;i=T2.e[i].next)
	if(T2.e[i].y!=fa)go(T2.e[i].y,x,tf);
}
void getans(int x)//求解所有对x的询问 
{
	for(int i=T3.first[x];i;i=T3.e[i].next)
	if(T3.e[i].type==0)ans[T3.e[i].id]=tr_sum(even,T3.e[i].y);
	else ans[T3.e[i].id]=tr_sum(odd,T3.e[i].y);
}
void dfs2(int x,int fa,bool tf)
{
	for(int i=T2.first[x];i;i=T2.e[i].next)//处理轻子树 
	if(T2.e[i].y!=fa&&T2.e[i].y!=mson[x])dfs2(T2.e[i].y,x,false);
	
	if(mson[x]!=0)dfs2(mson[x],x,true); add(a[x]);//处理重子树和自己 
	for(int i=T2.first[x];i;i=T2.e[i].next)//加上轻子树贡献 
	if(T2.e[i].y!=fa&&T2.e[i].y!=mson[x])go(T2.e[i].y,x,true);
	getans(x);//求解自己 
	
	if(!tf)go(x,fa,false);//如果自己是父亲的轻儿子,那么删去自己的贡献 
}

int main()
{
	scanf("%d %d",&n,&m); nn=n;
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1,x,y;i<=m;i++)
	{
		scanf("%d %d",&x,&y);
		if(!Map[par(x,y)])T1.buildroad(x,y),T1.buildroad(y,x),Map[par(x,y)]=true;
	}
	scanf("%d",&q);
	for(int i=1,type,x,y;i<=q;i++)
	{
		scanf("%d %d %d",&type,&x,&y);
		T3.buildroad(x,y,type,i);
	}
	tarjan(1);//圆方树板子 
	dfs1(1,0); dfs2(1,0,true);//dsu on tree 
	for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
}
发布了234 篇原创文章 · 获赞 100 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/104037656
今日推荐