2020牛客暑期多校訓練營(第六場)J Josephus Transform (置換)
https://ac.nowcoder.com/acm/contest/5671/J
題意:
初始有一個1-n的排列,對這個排列進行m次操作,每次操作對排列進行x次k-約瑟夫置換,問m次操作後的序列是什麼。
k-約瑟夫置換:n個數圍成一個圈,從第1個開始,數到第k個,將這個數字去掉,操作n次直至圈為空。數字去掉的順序就是對該排列進行1次k-約瑟夫置換後的序列。
一個置換可以定義為一個函式的複合
記f={a1,a2,a3,……an }表示數1-n的一個置換,即 i-->ai,ai<=n
對於某一個操作來說,它的x次k-約瑟夫置換對每一個數進行的置換都是相同的。
比如:7個數進行5次4約瑟夫置換
1 2 3 4 5 6 7
一 4 1 6 5 7 3 2
二 5 4 3 7 2 6 1
三 7 5 6 2 1 3 4
四 2 7 3 1 4 6 5
五 1 2 6 4 5 3 7
其f={4,1,6,5,7,3,2}
置換(函式複合)乘法滿足乘法結合律
所以只需要得到第一次的置換結果,對於x次同樣的置換,用快速冪的方式完成即可
數學渣渣表示記住結論走人
第一次的置換結果就是模擬約瑟夫問題
假設上一個去掉的數字是在剩餘的數中的第x個,去掉之後剩餘m個數字,
那麼這一次選出的數字就是這m個數字中的第(x-1+k)%m 個,若結果為0就是第m個
上式等價於(x-1+k-1)%m+1
如何得到剩餘m個數字中的第y個數字?
利用線段樹或者樹狀陣列二分
初始每個位置都是1,表示這個數字還沒有去掉
當去掉一個數字時,它的位置減去1
每次二分一個位置,查詢字首和,直到字首和為y
然後記第i種操作的置換為Pi,那麼所有的置換可以表示為 (P1^x1)(P2^x2)(P3^x3)……(Pm^xm)
即暴力的求法是 P1*P1……*P1*P2*P2*P2……*Pm*Pm……*Pm (x1個P1,x2個P2……xm個Pm
快速冪的方式是利用結合律,先將所有的P1算完,再算所有的P2,……最後將m個結果相乘得到最終結果
相乘的時候注意順序
#include<cstdio>
#include<cstring>
using namespace std;
#define N 100001
#define lowbit(x) x&-x
int a[N],tmp[N],to[N],ans[N];
int n,c[N];
void add(int x,int y)
{
while(x<=n)
{
c[x]+=y;
x+=lowbit(x);
}
}
int query(int x)
{
int s=0;
while(x)
{
s+=c[x];
x-=lowbit(x);
}
return s;
}
void find_one(int k)
{
int last=1,now,l,r,mid,pos;
for(int i=1;i<=n;++i) c[i]=0;
for(int j=1;j<=n;++j) add(j,1);
for(int j=1;j<=n;++j)
{
pos=(last-1+k-1)%(n-j+1)+1;
l=1;
r=n;
while(l<=r)
{
mid=l+r>>1;
if(query(mid)>=pos) now=mid,r=mid-1;
else l=mid+1;
}
a[j]=now;
last=query(now);
add(now,-1);
}
}
void mul(int x)
{
for(int i=1;i<=n;++i) to[i]=i;
while(x)
{
if(x&1)
{
for(int i=1;i<=n;++i) tmp[i]=to[a[i]];
for(int i=1;i<=n;++i) to[i]=tmp[i];
}
x>>=1;
for(int i=1;i<=n;++i) tmp[i]=a[a[i]];
for(int i=1;i<=n;++i) a[i]=tmp[i];
}
for(int i=1;i<=n;++i) tmp[i]=ans[to[i]];
for(int i=1;i<=n;++i) ans[i]=tmp[i];
}
int main()
{
int m,k,x;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) ans[i]=i;
for(int i=1;i<=m;++i)
{
scanf("%d%d",&k,&x);
find_one(k);
mul(x);
}
for(int i=1;i<=n;++i) printf("%d ",ans[i]);
}