题解 洛谷 P3207 【[HNOI2010]物品调度】

考虑题目所给出的式子:\(pos_i=(c_i+dx_i+y_i) \bmod n\),当 \(y_i\) 一定时,随着 \(x_i\) 的增大,得到的值会出现循环,即形成环。不难发现对于 \(x_i,y_i\) 的不同取值,\(c_i+y_i\) 可以看作对应一个环和该环的起始位置,\(x_i\) 对应到该环最终到达的位置。

因为要先保证 \(y_i\) 最小,再保证 \(x_i\) 最小,暴力的想法就是先枚举 \(y_i\),再枚举 \(x_i\),即先枚举是哪一个环,再枚举该环上的位置,判断是否该位置已经被占用,若没被占用就让当前的 \(i\) 占用,若被占用就继续枚举。

发现可以用并查集来优化这两个枚举的过程,当一个环内的位置都被占用后,将其用并查集指向下一个环,当一个位置被占用后,将其用并查集指向下一个位置。这样的话,通过并查集就能快速的找到环和其对应的位置。

确定完 \(pos\) 后,考虑如何计算最少步数。考虑到 \(pos\) 为原序列的一个置换,将 \(i\)\(pos_i\) 连边后,发现形成的图为若干个环,因此该置换是由若干个轮换组成的。考虑每一个轮换,当一个轮换中包含空位 \(0\) 时,其对答案的贡献为轮换长度减一,因为除了空位 \(0\) 以为,每个位置都要进行相应的移动,当一个轮换中不包含空位 \(0\) 时,其对答案的贡献为轮换长度加一,因为其还需将空位 \(0\) 移动进来和移动出去。

#include<bits/stdc++.h>
#define maxn 100010
using namespace std;
typedef long long ll;
template<typename T> inline void read(T &x)
{
    x=0;char c=getchar();bool flag=false;
    while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    if(flag)x=-x;
}
int T,n,s,q,p,m,d,ans,tot;
int bel[maxn],c[maxn],pos[maxn];
bool vis[maxn];
struct node
{
    int fa[maxn];
    int find(int x)
    {
        return fa[x]==x?x:fa[x]=find(fa[x]);
    }
}A,B;
void add(int x)
{
    int a=A.find(x),b=A.find((x+d)%n);
    if(a!=b) A.fa[a]=b;
    else B.fa[B.find(bel[x])]=B.find((bel[x]+1)%tot);
}
void clear()
{
    ans=tot=0;
    memset(bel,-1,sizeof(bel));
    memset(vis,0,sizeof(vis));
}
int main()
{
    read(T);
    while(T--)
    {
        clear(),read(n),read(s),read(q),read(p),read(m),read(d);
        for(int i=1;i<n;++i) c[i]=((ll)c[i-1]*q+p)%m;
        for(int i=0;i<n;++i)
        {
            if(bel[i]!=-1) continue;
            int x=i;
            while(bel[x]==-1) bel[x]=tot,x=(x+d)%n;
            tot++;
        }
        for(int i=0;i<n;++i) A.fa[i]=i,c[i]%=n;;
        for(int i=0;i<tot;++i) B.fa[i]=i;
        add(s),pos[0]=s;
        for(int i=1;i<n;++i)
        {
            int p1=B.find(bel[c[i]]),p2=A.find((c[i]+(p1-bel[c[i]]+tot)%tot)%n);
            pos[i]=p2,add(p2);
        }
        for(int i=0;i<n;++i)
        {
            if(vis[i]||i==pos[i]) continue;
            int x=i,len=0;
            while(!vis[x]) vis[x]=true,x=pos[x],len++;
            ans+=i?len+1:len-1;
        }
        printf("%d\n",ans);
    }
    return 0;
}
posted @ 2020-08-29 16:48  lhm_liu  阅读(230)  评论(0编辑  收藏  举报