【BZOJ 1998】 1998: [Hnoi2010]Fsk物品调度(双向链表+并查集+置换)

1998: [Hnoi2010]Fsk物品调度

Description

现在找工作不容易,Lostmonkey费了好大劲才得到fsk公司基层流水线操作员的职位。流水线上有n个位置,从0到n-1依次编号,一开始0号位置空,其它的位置i上有编号为i的盒子。Lostmonkey要按照以下规则重新排列这些盒子。 规则由5个数描述,q,p,m,d,s,s表示空位的最终位置。首先生成一个序列c,c0=0,ci+1=(ci*q+p) mod m。接下来从第一个盒子开始依次生成每个盒子的最终位置posi,posi=(ci+d*xi+yi) mod n,xi,yi是为了让第i个盒子不与之前的盒子位置相同的由你设定的非负整数,且posi还不能为s。如果有多个xi,yi满足要求,你需要选择yi最小的,当yi相同时选择xi最小的。 这样你得到了所有盒子的最终位置,现在你每次可以把某个盒子移动到空位上,移动后原盒子所在的位置成为空位。请问把所有的盒子移动到目的位置所需的最少步数。

Input

第一行包含一个整数t,表示数据组数。接下来t行,每行6个数,n,s,q,p,m,d意义如上所述。 对于30%的数据n<=100,对于100%的数据t<=20,n<=100000,s<n。其余所有数字均为不超过100000的正整数。 <="" div="">

Output

对于每组数据输出一个数占一行,表示最少移动步数。

Sample Input

1
8 3 5 2 7 4

Sample Output

6

HINT

说明:第1个到第7个盒子的最终位置依次是:2 5 6 4 1 0 7
计算过程可能超过整型范围。

Source

 

 

 

【分析】

  啊,我好笨。

  先看那两个公式。

  ci+1=(ci*q+p) mod m

  posi=(ci+d*xi+yi) mod n

  观察题目就知道主要是求pos数组,后面的就是置换的很基本的东西,弄成循环就好了。

  pos数组怎么求呢,当然暴力是会超时的。

  观察一下他的形式,发现如果yi也固定,那么走的是一个环。

  如果这个环里面所有元素都被取走了,那么就跳到下一个环,直到环里面有东西为止。

  就是模拟一个这样的过程,然后当然虽说是跳直到找到有环,但当然还是不能这样做的,所以我用了并查集和双向链表搞这个东西。

  环的数量是gcd(n,d)【表示我一开始还搞错这个很久

  求出pos数组之后,把置换分成互不相交的循环

  若循环长度>1,且空格在里面,则ans+=L-1

  否则ans+=L+1

  【最后那部分还是很简单的】

 

  1 #include<cstdio>
  2 #include<cstdlib>
  3 #include<cstring>
  4 #include<iostream>
  5 #include<algorithm>
  6 using namespace std;
  7 #define Maxn 1000010
  8 #define INF 0xfffffff
  9 #define LL long long
 10 
 11 int n,s,m,d;
 12 int nt[Maxn],lt[Maxn],fa[Maxn];//
 13 int ntt[Maxn],ltt[Maxn],fax[Maxn];//环集
 14 int pos[Maxn],id[Maxn];
 15 LL c[Maxn],q,p;
 16 
 17 int ffa(int x)
 18 {
 19     if(fa[x]!=x) fa[x]=ffa(fa[x]);
 20     return fa[x];
 21 }
 22 
 23 int ffax(int x)
 24 {
 25     if(fax[x]!=x) fax[x]=ffax(fax[x]);
 26     return fax[x];
 27 }
 28 
 29 int gcd(int a,int b)
 30 {
 31     if(b==0) return a;
 32     return gcd(b,a%b);
 33 }
 34 
 35 bool vis[Maxn];
 36 
 37 int main()
 38 {
 39     int T;
 40     scanf("%d",&T);
 41     while(T--)
 42     {
 43         int L;
 44         scanf("%d%d%lld%lld%d%d",&n,&s,&q,&p,&m,&d);
 45         d%=n;
 46         for(int i=0;i<n;i++) nt[i]=(i+d)%n;
 47         for(int i=0;i<n;i++) lt[i]=(i-d+n)%n;
 48         c[0]=0;
 49         for(int i=1;i<n;i++) c[i]=(c[i-1]*q+p)%m;
 50         for(int i=0;i<n;i++) fa[i]=i;
 51         for(int i=0;i<n;i++) c[i]%=n;
 52         if(d!=0) L=gcd(n,d);
 53         else L=n;
 54         for(int i=0;i<L;i++) fax[i]=i;
 55         for(int i=0;i<L;i++)
 56         {
 57             id[i]=i;
 58             int x=nt[i];
 59             while(x!=i)
 60             {
 61                 id[x]=i;
 62                 x=nt[x];
 63             }
 64         }
 65         
 66         memset(vis,1,sizeof(vis));
 67         for(int i=0;i<n;i++)
 68         {
 69             int nw=ffax(id[c[i]]);
 70             
 71             pos[i]=ffa((c[i]+(nw-id[c[i]]+L)%L)%n);
 72             
 73             if(i==0) pos[i]=s,nw=s%L;
 74             if(lt[pos[i]]==pos[i])
 75             {
 76                 fax[nw]=(nw+1)%L;
 77             }
 78             else
 79             {
 80                 nt[lt[pos[i]]]=nt[pos[i]];
 81                 lt[nt[pos[i]]]=lt[pos[i]];
 82                 fa[pos[i]]=nt[pos[i]];
 83             }
 84         }
 85         LL ans=0;
 86         memset(vis,0,sizeof(vis));
 87         for(int i=0;i<n;i++) if(vis[i]==0)
 88         {
 89             int x=i,cnt=0;
 90             bool p=0;
 91             while(vis[x]==0)
 92             {
 93                 cnt++;
 94                 if(x==s) p=1;
 95                 vis[x]=1;
 96                 x=pos[x];
 97             }
 98             if(cnt>1)
 99             {
100                 if(p) ans+=cnt-1;
101                 else ans+=cnt+1;
102             }
103         }
104         printf("%d\n",ans);
105     }
106     return 0;
107 }
View Code

 

2017-01-13 09:30:25

转载于:https://www.cnblogs.com/Konjakmoyu/p/6281512.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值