[HNOI2010] 物品调度 fsk
标签:链表+数论知识。
题解:
对于这道题,其实就是两个问题的拼凑,我们分开来看。
首先要求xi与yi。这个可以发现,x每增加1,则pos增加d;y每增加1,则pos增加1。然后,我们把x与y分别写在二维平面上,比如样例:
x= 0 1
y=0 {0 4}
y=1 {1 5}
y=2 {2 6}
y=3 {3 7}
发现行数=gcd(n,d),列数=n/gcd(n,d)。
那么题目要求y尽量小,然后x尽量小。我们先求y,那么初始pos就是ci,把ci放入这张表,寻找最近的可以不重复的行,也就是y。找到之后我们再把ci+y代入,去找在第y行的哪一个数是最近的,这样就能找到x与y了。
具体的:我们使用rx[i],代表x=i时,右边那个可用的是哪一个,rx_cnt[i]代表距离。同样ry[i],与ry_cnt[i]分别表示y=i的下面的哪一个与距离。每次修改(也就是标记Val已经被使用)仅仅需要对于(Val+d)%n即可,然后rx[Val]=(Val+d)%n,rx_cnt[Val]=1。将Val%=gcd,即可完成对于y的修改。
修改那么简单,那么查询:类似并查集一样的进行路径压缩,即如果rx[Val]被使用,那么rx[Val]=find(rx[Val]),对应修改rx_cnt[Val]即可,然后返回rx_cnt[Val]为x的值,y同样如此。
解决了这个问题,下面求解最小步数就很简单了。我们在纸上将i与pos[i]连上一条边,发现会构成很多个环,如果这个环中有0,那么直接把0按照环的反方向依次移动,即可把整个环都归位,如果环中没有0,那么先把0移进去,归位之后再移出来,步数+2即可,这样是最优的方案。证明可以使用置换群什么的。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define LL long long 6 using namespace std; 7 const int MAXN=110000; 8 bool vis[MAXN]; 9 int T,n,s,q,p,m,d,ans; 10 int c[MAXN],pos[MAXN],cnt[MAXN],rx[MAXN],ry[MAXN],rx_cnt[MAXN],ry_cnt[MAXN]; 11 inline int gi(){int res; scanf("%d",&res); return res;} 12 int gcd(int A,int B) 13 { 14 if(A%B==0)return B; 15 return gcd(B,A%B); 16 } 17 void insert(int x) 18 { 19 int tmp; 20 if((tmp=x+d)>=n)tmp-=n; 21 rx[x]=tmp; 22 rx_cnt[x]=1; 23 x%=m; 24 if(--cnt[x])return; 25 if((tmp=x+1)>=m)tmp-=m; 26 ry[x]=tmp; 27 ry_cnt[x]=1; 28 } 29 int query(int *r,int *r_cnt,int x) 30 { 31 if(r[x]==x)return 0; 32 r_cnt[x]=r_cnt[x] + query(r,r_cnt,r[x]); 33 r[x]=r[r[x]]; 34 return r_cnt[x]; 35 } 36 int main() 37 { 38 T=gi(); 39 while(T--) 40 { 41 scanf("%d%d%d%d%d%d",&n,&s,&q,&p,&m,&d); pos[0]=s; 42 for(int i=1;i<n;i++) c[i]=((LL)c[i-1]*q+p)%m; 43 d%=n;//insert()中没有使用取模,而是使用减法代替,所以这里先模一下。 44 m=gcd(n,d); 45 for(int i=0;i<m;i++) 46 { 47 cnt[i]=n/m; 48 ry[i]=i; 49 ry_cnt[i]=0; 50 } 51 for(int i=0;i<n;i++) rx[i]=i,rx_cnt[i]=0; 52 insert(pos[0]); 53 for(int i=1;i<n;i++) 54 { 55 int y=query(ry,ry_cnt,c[i]%m); 56 int x=query(rx,rx_cnt,(c[i]+y)%n); 57 pos[i]=((LL)d*x+y+c[i])%n; 58 insert(pos[i]); 59 } 60 ans=0; memset(vis,0,sizeof vis); 61 for(int i=0;i<n;i++) 62 if(!vis[i]) 63 { 64 int now=i,Start=i,flag=0,cnt=0; 65 do 66 { 67 vis[now]=1; cnt++; 68 if(now==0)flag=1; 69 now=pos[now]; 70 }while(now!=Start); 71 if(cnt>1) 72 { 73 ans+=cnt-1; 74 if(!flag)ans+=2; 75 } 76 } 77 printf("%d\n",ans); 78 } 79 return 0; 80 }