BZOJ 1998: [Hnoi2010]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最小的。 这样你得到了所有盒子的最终位置,现在你每次可以把某个盒子移动到空位上,移动后原盒子所在的位置成为空位。请问把所有的盒子移动到目的位置所需的最少步数。


 

研究了好长时间那个并查集是怎么用的,照着黄学长的代码一直看,最后得出一个结论:这也太乱搞了...

首先知道$pos$后就太容易做了置换群套路题

对于$len>1$的循环按有没有$0$分类

怎么算$pos$?

容易发现$y$最多$n$种取值,$x$每$+1$就是在环上移动$d$个位置

然后用并查集黑科技维护这个东西.....$fa$指向下一个可用位置,这个$y$上没可用位置就到$y+1$上去

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e5+5;
typedef long long ll;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}
int n,s,q,p,m,d;
ll c[N];
int pos[N],fa[N],cir[N];
bool vis[N],full[N];
int find(int x){
    if(!full[cir[x]]) return x==fa[x]?x:fa[x]=find(fa[x]);
    else return find(fa[x]=(x+1)%n);
}
void solve(){
    for(int i=1;i<n;i++) c[i]=(c[i-1]*q+p)%m;
    for(int i=0;i<n;i++) c[i]%=n,fa[i]=i,cir[i]=-1,vis[i]=0,full[i]=0;
    for(int i=0;i<n;i++) 
        for(int j=i;cir[j]==-1;j=(j+d)%n) cir[j]=i;
    fa[s]=(s+d)%n;
    pos[0]=s;
    if(d==0) full[cir[s]]=1;
    for(int i=1;i<n;i++){
        int x=find(c[i]),y=find((x+d)%n);
        pos[i]=x;
        if(x==y) full[cir[x]]=1;
        else fa[x]=y;
    }
    int ans=0;
    for(int i=0;i<n;i++) if(!vis[i]){//printf("\nhi %d ",i);
        int u=pos[i],len=1;
        while(u!=i){//printf("%d ",u);
            vis[u]=1;
            len++;
            u=pos[u];
        }
        //printf("len %d\n ",len);
        if(len>1){
            if(i==0) ans+=len-1;
            else ans+=len+1;
        }
    }
    printf("%d\n",ans);
}
int main(){
    freopen("in","r",stdin);
    int T=read();
    while(T--){
        n=read();s=read();q=read();p=read();m=read();d=read()%n;
        solve();
    }
}

 

转载于:https://www.cnblogs.com/candy99/p/6485309.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值