bzoj1998[Hnoi2010] Fsk物品调度

题目链接:bzoj1998
题目大意:
有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搞的我一脸懵比啊。
先讲我会的置换(有什么用没学置换的都会)
假设pos求好了,就可以分成很多个互不相交的循环。对于一个长度大于1为cnt的循环,若含空格,那么使其达成目标的步数就是cnt-1;若不含,就要把空格换过来,达成目标后再换回去,即需要cnt+1步。

然后就是pos怎么构造了。大重点,不会这个就等于不会这题QAQ
观察posi=(ci+yi+d*xi) mod n这个式子,可以发现,若yi不变的话,xi的改变就相当于不断+d+d,在mod n下显然会成环。那么就相当于把n化成很多个环(一个yi就一个环)。
因为题目要在yi最小的条件下要xi最少,所以可以从0开始**枚举**yi。看看yi这个环里还有没有位置空着,有的话就找个最小的x来让i填。没有的话yi++,即跳到下个环里,重复以上步骤。
并查集找的就是环里还能填的。若最终找出来的也被找过了(即不是空的)就说明这个环被填完了。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
#define maxn 100010

bool vis[maxn],pd[maxn];
int fa[maxn],pos[maxn];LL c[maxn];
int ffind(int x){return (x==fa[x])?x:fa[x]=ffind(fa[x]);}
void merge(int x,int y)
{
    int fx=ffind(x),fy=ffind(y);
    fa[fx]=fy;
}
int main()
{
    //freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout);
    int T,n,s,d,i,ans,cnt;LL q,p,mod;
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d%lld%lld%lld%d",&n,&s,&q,&p,&mod,&d);
        d%=n;c[0]=0;ans=0;
        for (i=0;i<n;i++)
        {
            if (i) c[i]=(c[i-1]*q+p)%mod;
            fa[i]=i,vis[i]=0;pd[i]=0;
        }
        pos[0]=s;pd[s]=1;fa[s]=(s+d)%n;
        for (i=1;i<n;i++)
        {
            int y=0,t=ffind((c[i]+y)%n);
            while (pd[t])
            {
                y++;
                t=ffind((c[i]+y)%n);
            }
            pos[i]=t;pd[t]=true;
            merge(t,(t+d)%n);
        }
        for (i=0;i<n;i++) if (!vis[i])
        {
            cnt=0;int x=i;
            bool zero=false;
            while (!vis[x])
            {
                cnt++;
                if (x==0) zero=true;
                vis[x]=true;
                x=pos[x];
            }
            if (cnt<=1) continue;
            if (zero) ans+=cnt-1;
            else ans+=cnt+1;
        }
        // for (i=1;i<n;i++) printf("%d ",pos[i]);
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值