【ZROI264. 花园】基环内向树

orz orz orz 降智光环严重 orz orz orz 小A和一些朋友来到了一个热带花园游玩。花园中有 n 个喷泉(标号 0 ~ n-1 ),以及 m条双向道路。 每条道路有个互不相同的美丽程度,当一个人在节点 x 时,他会选择一条与 x 相连的最美丽的道路走过去,如果他在前一时刻是从那条道路走过来的,则他会选择美丽程度次大的道路走(如果不存在次大的,则还是走最大的那条)。保证每个点都至少有一条边与它相连。 一开始每个点上都有一个人,他们将按照上述规则行走,每单位时间所有人都走过一条道路。 在P号点上有一个豪华餐厅,你需要计算在 K_i 时间后恰好在P号点上的人数。 有q 组 K_i 需要你计算,每组的P 都相同,询问互相独立。

输入格式

第一行三个数 n, m, P。 接下来M行,每行两个数 x , y 表示一条道路。道路按照美丽程度从大到小给出。 接下来一行一个数 q。 接下来一行,q 个正整数 K_i。

输出格式

一行Q个数,表示每次询问的答案。

样例1

样例输入1

5 5 2
1 0
1 2
3 2
1 3
4 2
2
3 1

样例输出1

1 2

样例2

样例输入2

6 6 0
1 2
0 1
0 3
3 4
4 5
1 5
1
3

样例输出2

2

样例3

见下载文件

样例4

见下载文件

样例5

见下载文件

样例6

见下载文件

限制与约定

对于 30%的数据,n<=1000 m<=10000 Ki100 对于 60%的数据,q=1 对于 100% 的数据,2<=n<=150000 1<=m<=150000  1<=q<=2000  1<=Ki<=10^9   时间限制:1s 空间限制:512MB

题解

30分,直接暴力模拟O(nK+m^2+qn)。 60分, 将每个点拆分成两个点来考虑(一个表示从最大边走过来,一个表示不从最大边走过来。这样的话我们可以发现,对于每个点,其下一步到达的点是固定的。那么我们可以倍增直接搞出每个点对应时间所在位置而不是暴力跳!O(m+nlogmax:K + qnlogK) 60分code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 300005;
int Q,n,m,p;
int ST[32][maxn]; int cdl[maxn],zdl[maxn];
int id(int x,bool frzd) { return x*2+(int)frzd; }
int KK[3005],S;
int main()
{
	scanf("%d%d%d",&n,&m,&p);
//	for(int i=0;i<=id(n,1);i++) ST[0][i]=i;
	for(int i=0;i<n;i++) zdl[i]=cdl[i]=-1;
	for(int i=1;i<=m;i++)
	{
		int x,y; scanf("%d%d",&x,&y);
		if(zdl[x]==-1) zdl[x]=y;
		else if(cdl[x]==-1) cdl[x]=y;
		if(zdl[y]==-1) zdl[y]=x;
		else if(cdl[y]==-1) cdl[y]=x;
	}
	for(int i=0;i<n;i++)
	{
		if(zdl[i]!=-1) 
		{
			ST[0][id(i,0)]=id(zdl[i],i==zdl[zdl[i]]?1:0);
			if(cdl[i]!=-1)
			{
				ST[0][id(i,1)]=id(cdl[i],i==zdl[cdl[i]]?1:0);
			}
			else 
			{
				ST[0][id(i,1)]=id(zdl[i],i==zdl[zdl[i]]?1:0);
			}
		}
		else 
		{
		ST[0][id(i,1)]=id(i,1);
		ST[0][id(i,0)]=id(i,0);
		}
	}
	scanf("%d",&Q);
	for(int i=1;i<=Q;i++)
	{
		scanf("%d",&KK[i]);
		S = max(S,KK[i]);
	}
	S = floor(log2(S));
	for(int i=1;i<=S;i++)
	{
		for(int j=0;j<=id(n,1);j++)
		{
			ST[i][j]=ST[i-1][ST[i-1][j]];
		}
	}
	for(int i=1;i<=Q;i++)
	{
		int sum = 0;
		for(int j=0;j<n;j++)
		{
			int aha = id(j,0);
			for(int k=KK[i],z=0;k;k>>=1,z++)
				if(k&1) aha = ST[z][aha];
			if(aha/2==p) sum++;
		}
		printf("%d ",sum);
	}
}
100分:在完成拆点之后,我们发现对于每个点都有确定的一个转移!并且显而易见,这些点最终会陷入一个环中间反复循环!进而我们发现这是一颗基环内向树。这意味着,所有的点一旦到达环内,之后的一切状态就完全由循环的循环节决定了。 由此,我们判一下P点是在环内还是环外,是环外只需要判定距离等于时间的个数,而在环外的我们要判一下各自点到环的时间和循环节进行滚动就可以了。 当然,比较懒的实现方法是对于时间小于等于点数的直接裸判距离并滚动一下循环节,对于时间大于点数的,显然其必定陷入了循环,就直接判定一下循环到哪里了就可以了。 100分code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 300005; 
int Q,n,m,p;
int NT[maxn]; int cdl[maxn],zdl[maxn];
int ANS[maxn];
int id(int x,bool frzd) { return x*2+(int)frzd; }
int KK[3005],S;
int dis[maxn]; bool vis[maxn];
bool hhflag;
int dfs(int x)
{
	if(dis[x]>=0) return dis[x];
	if(vis[x]) { return -1;}
	vis[x]=1;
	dis[x]=dfs(NT[x]);
	if(dis[x]>=0) dis[x]++;
	return dis[x];
}
int cnt[2][maxn];
void solve(int p)
{
	for(int i=0;i<=id(n,1);i++) dis[i] = -1,vis[i]=0;
	dis[p] = 0; 
	for(int i=0;i<=id(n,1);i++)
		if(!vis[i]) 
		{
			dfs(i);
		}
	memset(cnt,0,sizeof cnt); int cir = dis[NT[p]]==-1?-1:dis[NT[p]]+1;
	for(int i=0;i<n;i++)
	{
		if(dis[id(i,0)]>=0)
		{
		cnt[0][dis[id(i,0)]]++; 
		if(!hhflag)cnt[1][dis[id(i,0)]%cir]++;
		}
	}
	if(cir!=-1)
	for(int i=cir;i<=id(n,1);i++)
		cnt[0][i]+=cnt[0][i-cir];
	for(int i=1;i<=Q;i++)
	{
		if(cir!=-1)
		{
			if(KK[i]>id(n,1))
			{
				ANS[i]+=cnt[1][KK[i]%cir];
			}
			else ANS[i]+=cnt[0][KK[i]];
		}
		else 
		{
			if(KK[i]<=id(n,1))
			ANS[i]+=cnt[0][KK[i]];
		}
	}
}
int main()
{
//	freopen("ex_garden5.in","r",stdin);
	scanf("%d%d%d",&n,&m,&p);
//	for(int i=0;i<=id(n,1);i++) ST[0][i]=i;
	for(int i=0;i<n;i++) zdl[i]=cdl[i]=-1;
	for(int i=1;i<=m;i++)
	{
		int x,y; scanf("%d%d",&x,&y);
		if(zdl[x]==-1) zdl[x]=y;
		else if(cdl[x]==-1) cdl[x]=y;
		if(zdl[y]==-1) zdl[y]=x;
		else if(cdl[y]==-1) cdl[y]=x;
	}
	for(int i=0;i<n;i++)
	{
		if(zdl[i]!=-1) 
		{
			NT[id(i,0)]=id(zdl[i],i==zdl[zdl[i]]?1:0);
			if(cdl[i]!=-1)
			{
				NT[id(i,1)]=id(cdl[i],i==zdl[cdl[i]]?1:0);
			}
			else 
			{
				NT[id(i,1)]=id(zdl[i],i==zdl[zdl[i]]?1:0);
			}
		}
		else 
		{
		NT[id(i,1)]=id(i,1);
		NT[id(i,0)]=id(i,0);
		}
	}
	scanf("%d",&Q);
	for(int i=1;i<=Q;i++)
	{
		scanf("%d",&KK[i]);
		S = max(S,KK[i]);
	}
	solve(id(p,0)); solve(id(p,1));
	for(int i=1;i<=Q;i++)
	printf("%d ",ANS[i]);
}
补充参考: newuser小站---初识基环树
posted @ 2018-08-11 00:48  Newuser233  阅读(14)  评论(0)    收藏  举报