题目描述
题解
首先考虑如何构造出pos
因为要求在x最小的情况下y最小
那么当y固定的时候x会有一坨取值,也会有一些不同的数
考虑先枚举y=0,然后如果x没有合适的取值再将y+1
因为要求最终的答案不重复,那么如果w已经在答案中出现过了,那么w+d是有可能出现的(将x+1),所以用并查集将w并到w+d
这样的话,首先枚举y,如果ci+y所在的集合中的元素都被选过了,那么只能将y+1
求出pos之后,考虑如何求答案
这就是一个置换,存在若干个循环节。循环节长度为1的不用管,考虑长度大于1的。如果某一个大小为k循环节中有0(有空位),那么可以直接交换k-1次将这个循环节归位;如果某一个大小为k的循环节中没有0,那么先将0强拉进来,然后用k-1次归位,再将0放回去,这样是k+1次
找出来所有的循环节就可以了
注意要是直接计数的话要考虑0自己单独一个循环节的情况
可是感觉这样的时间复杂度不是很科学,并查集的过程最坏 O(n2) ?但是貌似很难卡…
代码
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define LL long long
#define N 100005
int T,n,q,p,m,d,s,now,cnt,tot,ans;
int c[N],pos[N],f[N];
bool flag[N];
void clear()
{
n=q=p=m=d=s=now=cnt=tot=ans=0;
memset(c,0,sizeof(c));
memset(pos,0,sizeof(pos));
memset(f,0,sizeof(f));
memset(flag,0,sizeof(flag));
}
int find(int x)
{
if (x==f[x]) return x;
f[x]=find(f[x]);
return f[x];
}
void merge(int x,int y)
{
int fx=find(x),fy=find(y);
if (fx!=fy) f[fx]=fy;
}
int main()
{
scanf("%d",&T);
while (T--)
{
clear();
scanf("%d%d%d%d%d%d",&n,&s,&q,&p,&m,&d);
c[0]=0;
for (int i=1;i<n;++i) c[i]=((LL)c[i-1]*q%m+p)%m;
for (int i=0;i<n;++i) f[i]=i;
pos[0]=s;flag[s]=1;merge(s,(s+d)%n);
for (int i=1;i<n;++i)
{
now=0;
int x=find((c[i]+now)%n);
while (flag[x])
{
++now;
x=find((c[i]+now)%n);
}
pos[i]=x;
flag[x]=1;
merge(x,(x+d)%n);
}
memset(flag,0,sizeof(flag));
tot=0;ans=0;
for (int i=0;i<n;++i)
if (!flag[i])
{
cnt=0;
int x=i;
while (!flag[x])
{
++cnt;flag[x]=1;
x=pos[x];
}
if (cnt>1) ans+=cnt,++tot;
}
if (!pos[0]) ans+=tot;
else ans=(ans-1)+tot-1;
printf("%d\n",ans);
}
}