牛客第六场补题 Josephus Transform 置换、快速幂

这篇博客探讨了Josephus Transform的概念,即在环形排列中进行k-约瑟夫变换,并给出了一种利用线段树实现的n*logn复杂度解决方案。博主还解释了如何利用置换的结合律和快速幂来解决多次变换的问题,赞叹了这种方法的巧妙之处,并分享了相关代码。
摘要由CSDN通过智能技术生成

Josephus Transform

题目链接

题目大意

一个长度为n的排列,初始为1……n。
有m次操作:
每次操作是进行y次 k-约瑟夫变换 。
这个变换就是:先把1……n写成一个环,然后从1开始找到第k个,把这个数加到b里,把这个数在环里删去,从这个位置下一个 继续找第k个,直到数字被删完。
1<=n<=1e5
1<=m<=1e5
1<=n * m<=1e6

题解

求进行一次k-约瑟夫变换得到的排列b,可以用线段树求出来,复杂度是n*logn.
进行y次变换也就是用b排列把1……n置换y次。
怎么置换呢?
可以发现置换满足结合律。
也就是说 对a进行两次b置换就相当于 先把b进行一次b置换,然后把a进行一次b置换就好了。
然后就可以用快速幂了。
我怎么就想不到,巨巨们太强了,妙啊妙啊
代码:

#include<algorithm>
#include<cstring>
#include <iostream>
#include <cstdio>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<double,double> pdd;
typedef unsigned long long ull;
typedef set<int>::iterator sit;
#define st first
#define sd second
#define mkp make_pair
#define pb push_back
void wenjian(){freopen("concatenation.in","r",stdin);freopen("concatenation.out","w",stdout);}
void tempwj(){freopen("hash.in","r",stdin);freopen("hash.out","w",stdout);}
ll gcd(ll a,ll b){return b == 0 ? a : gcd(b,a % b);}
ll qpow(ll a,ll b,ll mod){a %= mod;ll ans = 1;while(b){if(b & 1)ans = ans * a % mod;a = a * a % mod;b >>= 1;}return ans;}
struct cmp{bool operator()(const pii & a, const pii & b){return a.second < b.second;}};
int lb(int x){return  x & -x;}
//friend bool operator < (Node a,Node b)   重载
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const int maxn = 5e5+10;
struct Node
{
	int l,r,num;
}node[maxn];

void build(int l,int r,int no)
{
	node[no].l = l;
	node[no].r = r;
	node[no].num = r - l + 1;
	if(l == r)
	{
		return;
	}
	int mid = l +r >> 1;
	build(l,mid,no<<1);
	build(mid + 1,r,no<<1|1);
}

void update(int pos,int num,int no)
{
	node[no].num += num;
	if(node[no].l == node[no].r)
		return;
	int mid = node[no].l + node[no].r >> 1;
	if(pos <= mid)
		update(pos,num,no<<1);
	else
		update(pos,num,no<<1|1);
}
int query(int k,int no)
{
	if(node[no].l == node[no].r)
		return node[no].l;
	int s = node[no<<1].num;
	if(k <= s)
		return query(k,no<<1);
	else
		return query(k - s,no<<1|1);

}
int n;
int temp[maxn];
void solve(int a[],int b[])
{
	for (int i = 1; i <= n; i ++ )
	{
		temp[i] = a[b[i]];
	}
	for (int i = 1; i <= n; i ++ )
		a[i] = temp[i];
}
int b[maxn];
int a[maxn];

void qui(int num)
{
	while(num)
	{
		if(num & 1)
		{
			solve(a,b);
		}
		num >>= 1;
		solve(b,b);
	}
}

int main()
{
	int m;
	scanf("%d%d",&n,&m);
	for (int i = 1; i <= n; i ++ )
			a[i] = i;
	while(m -- )
	{
		build(1,n,1);
		int x,y;
		scanf("%d%d",&x,&y);
		int cnt = n;
		int pos = 1;
		
		for (int i = 1; i <= n; i ++ )
		{
			int k = (pos - 1 + x - 1) % cnt + 1;

			cnt -- ;
			b[i] = query(k,1);
			//printf("%d %d\n",k,b[i]);
			update(b[i],-1,1);
			pos = k;
		}
		// for (int i = 1; i <= n; i ++ )
		// 	printf("%d ", b[i]);
		// cout<<endl;
		qui(y);
		
	}
	for (int i = 1; i <= n; i ++ )
	{
		printf("%d ",a[i]);
	}
	printf("\n");
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值