暴走的猴子(walk.pas/c/cpp)
【题目描述】
从前有一个森林,森林里生活着一群猴子,这里猴子有个恶趣味——暴走。现在给你这个森林里的树木描述,你能计算出这只猴子在暴走k步后会蹦达到哪里吗(友情提示:由于你上周帮助猎人写程序打死了猴子父亲,所以今天猴子特别不爽,故意暴走了很多很多步来为难你,从而导致了k非常的大,做好心里准备噢~)
【输入数据】
第一行两个数n,m表示树木数和询问次数
接下来n行,第i行一个数ai表示这只猴子当前在第i棵树的话,下一步会走到第ai棵树
接下来m行,每行两个数t,k,询问如果当前猴子在第t棵树,k步之后它会到第几棵树
【输出数据】
m行为每次询问的结果
【样例输入】
3 2
2
3
2
1 2
2 4
【样例输出】
3
2
【数据范围】
共十个测试点,每个测试点数据规模如下所示
1.n=10^2,m=n,k<=10^2
2.n=10^3,m=n,k<=10^3
3.n=10^4,m=1,k<=10^9
4.n=10^5,m=1,k<=10^9
5.n=10^5,m=1,k<=10^12
6.n=10^5,m=1,k<=10^15
7.n=10^5,m=1,k<=10^18
8.n=10^5,m=n,k<=10^12
9.n=10^5,m=n,k<=10^15
10.n=10^5,m=n,k<=10^18
【时限】
1s
这套题很好,每到题都有多种解法。
此题解法有三:
方法一:TLE70
我最先采用的方法,朴素模拟,当遇到环的时候就将剩余步数对环的大小取模。然后再进行一次模拟。此方法很简单。
#include <cstdio>
#include <cstring>
#include <string>
long nxt[100010];
typedef unsigned long long ull;
ull used[100010];
long getint()
{
long rs=0;bool sgn=1;char tmp;
do tmp=getchar();
while (!isdigit(tmp)&&tmp-'-');
if (tmp=='-'){tmp=getchar();sgn=1;}
do rs=(rs<<3)+(rs<<1)+tmp-'0';
while (isdigit(tmp=getchar()));
return sgn?rs:-rs;
}
ull getll()
{
ull rs=0;bool sgn=1;char tmp;
do tmp=getchar();
while (!isdigit(tmp)&&tmp-'-');
if (tmp=='-'){tmp=getchar();sgn=1;}
do rs=(rs<<3)+(rs<<1)+tmp-'0';
while (isdigit(tmp=getchar()));
return sgn?rs:-rs;
}
int main()
{
freopen("walk.in","r",stdin);
freopen("walk.out","w",stdout);
long n = getint();
long m = getint();
for (long a=1;a<n+1;a++)
{
long b = getint();
nxt[a] = b;
}
for (long i=1;i<m+1;i++)
{
if (m > 1)
{
memset(used,0,sizeof used);
}
long u = getint();
used[u] = 1;
ull s = getll();
for (ull j=1;j<s+1;j++)
{
u = nxt[u];
if (!used[u])
used[u] = j+1;
else
{
long ss = (s-j)%(j+1-used[u]);
for (long k=1;k<ss+1;k++)
u = nxt[u];
break;
}
}
printf("%ld\n",u);
}
return 0;
}
解法二:TLE90
强连通分量,实质与上面相同,不同之处在于,此解法类似于O(n)的预处理优化,预处理出所有的环来。所以较快。
遇到两个问题:
1、因为用了stl的min所以tarjan爆栈,不知道是为何。
2、每次移动后,没有更新所属的强连通分量。所以到了环却不知道。超时8组。
#include <cstdio>
#include <string>
#include <algorithm>
using std::min;
typedef unsigned long long ull;
long getint()
{
long rs=0;char tmp;bool sgn=1;
do tmp=getchar();
while (!isdigit(tmp)&&tmp-'-');
if (tmp=='-'){tmp=getchar();sgn=0;}
do rs=(rs<<3)+(rs<<1)+tmp-'0';
while (isdigit(tmp=getchar()));
return sgn?rs:-rs;
}
ull getll()
{
ull rs=0;char tmp;bool sgn=1;
do tmp=getchar();
while (!isdigit(tmp)&&tmp-'-');
if (tmp=='-'){tmp=getchar();sgn=0;}
do rs=(rs<<3)+(rs<<1)+tmp-'0';
while (isdigit(tmp=getchar()));
return sgn?rs:-rs;
}
long DFN[100010];
long LOW[100010];
long Stack[100010];
bool InStack[100010];
long cnt[100010];
long Belong[100010];
long nxt[100010];
long Bcnt = 0;
long top = 0;
long Time = 0;
void Tarjan(long u)
{
DFN[u] = LOW[u] = ++Time;
InStack[u] = true;
Stack[++top] = u;
long v = nxt[u];
if (!DFN[v])
{
Tarjan(v);
if (LOW[v] < LOW[u])
LOW[u] = LOW[v];
}
else if (InStack[v] && DFN[v]<LOW[u])
{
LOW[u] = DFN[v];
}
if (DFN[u] == LOW[u])
{
Bcnt ++;
long v;
do
{
v = Stack[top--];
InStack[v] = false;
Belong[v] = Bcnt;
cnt[Bcnt] ++;
}while (u != v);
}
}
int main()
{
freopen("walk.in","r",stdin);
freopen("walk.out","w",stdout);
long n = getint();
long m = getint();
for (long i=1;i<n+1;i++)
nxt[i] = getint();
for (long i=1;i<n+1;i++)
if (!DFN[i])
Tarjan(i);
for (long i=1;i<m+1;i++)
{
long u = getint();
ull s = getll();
long belongu = Belong[u];
for (ull i=1;i<s+1;i++)
{
if (cnt[belongu]>1)
{
for(ull j=0;j<(s-i+1)%cnt[belongu];j++)
u = nxt[u];
break;
}
u = nxt[u];
belongu = Belong[u];
}
printf("%ld\n",u);
}
return 0;
}
解法三:AC
这是标准解法,倍增思想。
非常巧妙。f[i][j] 表示 从j开始走2^i步可以到哪里。(利用二分降低时间复杂度到O(lgn))
然后用类似于快速幂的方法来移动,思路很简单,但是不容易想到,而且优化很大!
#include <cstdio>
#include <string>
typedef unsigned long long ull;
long f[65][100010];
long getint()
{
long rs=0;bool sgn=1;char tmp;
do tmp=getchar();
while (!isdigit(tmp)&&tmp-'-');
if (tmp=='-'){tmp=getchar();sgn=1;}
do rs=(rs<<3)+(rs<<1)+tmp-'0';
while (isdigit(tmp=getchar()));
return sgn?rs:-rs;
}
ull getll()
{
ull rs=0;bool sgn=1;char tmp;
do tmp=getchar();
while (!isdigit(tmp)&&tmp-'-');
if (tmp=='-'){tmp=getchar();sgn=1;}
do rs=(rs<<3)+(rs<<1)+tmp-'0';
while (isdigit(tmp=getchar()));
return sgn?rs:-rs;
}
int main()
{
freopen("walk.in","r",stdin);
freopen("walk.out","w",stdout);
long n = getint();
long m = getint();
for (long i=1;i<n+1;i++)
{
f[0][i] = getint();
}
for (long k=1;k<62;k++)
for (long i=1;i<n+1;i++)
f[k][i] = f[k-1][f[k-1][i]];
for (long i=1;i<m+1;i++)
{
long u = getint();
ull s = getll();
long k = 0;
while (s)
{
if (s&1) u=f[k][u];
k++;
s >>= 1;
}
printf("%ld\n",u);
}
return 0;
}