题解:CF 1200 F Graph Traveler

Codeforces AC 提交记录

题意

有一张图,可重边可自环。行走规则:初始时有一个值 c c c 和所在的点 x x x,到达一个点就将 c c c 加上点权 k x k_x kx,每次可以走从点 x x x 连出去的第 c % m x c\%m_x c%mx 条边,到达下一个点。有 q q q 个询问,给你 x x x c c c,问在行走过程中有几个点会经过无限次(如果从一个点能走到一个环,问这个环包含的点数;否则是 0 0 0)。

思路

q q q y y y 都太大了,发现 m i m_i mi 很小。 y y y m i m_i mi 取模决定走到哪个点,可以先将 y y y l c m ( 1 , 2 , 3 , . . . , 10 ) lcm(1,2,3,...,10) lcm(1,2,3,...,10) 取模。因为 m i m_i mi 1 1 1 10 10 10 范围内,无论对哪个 m i m_i mi l c m ( 1 , 2 , 3 , . . . , 10 ) lcm(1,2,3,...,10) lcm(1,2,3,...,10) 都是它的倍数,所以不影响 y y y 对每个 m i m_i mi 取模的结果。
当前的值 y y y 对于边是有影响的,遂将 ( x , y ) (x,y) (x,y) 作为一个状态。 l c m ( 1 , 2 , 3 , . . . , 10 ) = 2520 lcm(1,2,3,...,10)=2520 lcm(1,2,3,...,10)=2520,那么状态数最多只有 1000 × 2520 1000\times2520 1000×2520 种,只要用记忆化搜索处理出每种状态的答案,就可以 O ( 1 ) O(1) O(1) 回答问题。

实现

由于 x , y x,y x,y 确定,每种状态有固定的下一个状态: t x = e x , y % m x , t y = y + k t x tx=e_{x,y\%m_x},ty=y+k_{tx} tx=ex,y%mx,ty=y+ktx。我们用 c n t x , y cnt_{x,y} cntx,y 记录答案, f l a g x , y flag_{x,y} flagx,y 记录这个状态在第几次 d f s dfs dfs(在 m a i n main main 函数里被调用第几次)的时候算到了。如果即将到达的状态的 f l a g flag flag 0 0 0,就 d f s ( t x , t y ) dfs(tx,ty) dfs(tx,ty);如果 f l a g flag flag 等于当前的次数,就证明这个环是新找到的,计算环里有几个点并赋值给 c n t x , y cnt_{x,y} cntx,y;如果这个状态在之前的 d f s dfs dfs 中就已经计算过了,证明这个环已经被找到过了,直接 c n t x , y = c n t t x , t y cnt_{x,y}=cnt_{tx,ty} cntx,y=cnttx,ty。最后查询答案的时候要把 x x x 的点权加进去,答案是 c n t x , y + k x cnt_{x,y+k_x} cntx,y+kx
过程中注意 y y y 的取模,由于 y y y 可能是负数, y = ( y % 2520 + 2520 ) % 2520 y=(y\%2520+2520)\%2520 y=(y%2520+2520)%2520。计算环里点的个数时,要用 v i s vis vis 标记一个点是否数过了,因为 s t a c k stack stack 记录的只有 x x x,而状态里是 ( x , y ) (x,y) (x,y),可能重复。
在下图中,原本的路径是黑色的,更新答案的路径是蓝色的:

代码

#include<bits/stdc++.h>
using namespace std;
#define mod 2520
int n,k[1005],m[1005],e[1005][15],q,a,b;
int cnt[1005][2520],st[2520000],pos[1005][2520],flag[1005][2520];
bool vis[1005];
void mo(int& x){ x=(x%mod+mod)%mod; }
void dfs(int x,int y,int id){
	flag[x][y]=id;
	st[pos[x][y]]=x;
	int tx=e[x][y%m[x]];int ty=y+k[tx];mo(ty);
	if(!flag[tx][ty]){
		pos[tx][ty]=pos[x][y]+1,dfs(tx,ty,id);
		cnt[x][y]=cnt[tx][ty];
	}
	if(flag[tx][ty]==id){
		for(int i=pos[tx][ty];i<=pos[x][y];i++)
			vis[st[i]]=0;
		for(int i=pos[tx][ty];i<=pos[x][y];i++)
			if(!vis[st[i]]) vis[st[i]]=1,cnt[x][y]++;
	}
	else cnt[x][y]=cnt[tx][ty];
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&k[i]),mo(k[i]);
	for(int i=1;i<=n;i++){
		scanf("%d",&m[i]);
		for(int j=0;j<m[i];j++) scanf("%d",&e[i][j]);
	}
	int l=0;
	for(int i=1;i<=n;i++)
		for(int j=0;j<mod;j++)
			if(flag[i][j]==0) dfs(i,j,++l);
	scanf("%d",&q);
	for(int i=1;i<=q;i++){
		scanf("%d%d",&a,&b);
		b+=k[a];mo(b);
		printf("%d\n",cnt[a][b]);
	}
	return 0;
}

感谢阅读 Thanks♪(・ω・)ノ。

  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值