[USACO21JAN] Dance Mooves S
题目大意
给定一个长度为 \(N\) 的序列 \(A\),初始化 \(A_i=i\)。
随后 \(\rm Farmer~John\) 会给出 \(K\) 对指令,形式为 \(x_i,y_i\),表示将 \(A[x_i]\) 与 \(A[y_i]\) 进行交换。
尽管给出了 \(K\) 对指令,但是奶牛们需要无穷止地跳下去,第 \(i\) 分钟需要执行第 \((i+K)\mod K\) 个操作(第 \(aK\) 秒执行第 \(K\) 个操作)。
求每头奶牛将会在哪些位置出现。
题目分析
这是我们一场模拟赛的第二题,赛时只想到了暴力循环点的做法。并且因为数组开大了直接挂完 (我以为用了 \(\rm bitset\) 再应该可以卡过去的) 。
\(\rm 50\) 分做法
通过手动枚举可以发现当奶牛们变了 \(N\times K\) 次之后,会在此回到初始时的序列。于是我们枚举每头牛在这 \(N\times K\) 次舞列中哪些位置出现过即可。
在枚举的过程中,可以开个桶来记录。但是不要像我一样把桶开炸了。。。
代码就不放了。
满分做法
假设小牛 \(A\) 在 \(i\) 位置,小牛 \(B\) 在 \(j\) 位置。
出现过操作 \(\rm i,f\) 和 \(\rm j,i\),那么 \(K\) 分钟之后,\(B\) 也会到达 \(i\) 位置,它们走过的位置一定相同,这启发我们把环剥离出来或者使用并查集维护。
用 \(\rm vector\) 来记录每头奶牛出现过的点,再用 \(\rm set\) 统计每头奶牛出现的数量。
这里是并查集做法。
代码
const int ma=100005;
int a[ma],fa[ma];
int n,k;
vector<int>vec[ma];
set<int>ans[ma];
//==========并查集========
inline void init()
{
for(register int i=1;i<=n;i++)
{
fa[i]=a[i]=i;
}
}
inline int get(int x)
{
if(fa[x]==x)
{
return x;
}
return fa[x]=get(fa[x]);
}
inline void merge(int x,int y)
{
int f1=get(x),f2=get(y);
if(f1!=f2)
{
fa[f1]=f2;
}
}
//========================
int main(void)
{
n=read(),k=read();
init();
for(register int i=1;i<=k;i++)
{
int x=read(),y=read();
swap(a[x],a[y]);
vec[a[x]].push_back(y);
vec[a[y]].push_back(x);
}
for(register int i=1;i<=n;i++)
{
vec[a[i]].push_back(i);
}
for(register int i=1;i<=n;i++)
{
merge(i,a[i]);
}
for(register int i=1;i<=n;i++)
{
for(register int j=0;j<vec[a[i]].size();j++)
{
ans[get(a[i])].insert(vec[a[i]][j]);
}
}
for(register int i=1;i<=n;i++)
{
printf("%d\n",ans[get(i)].size());
}
return 0;
}

浙公网安备 33010602011771号