康托展开求全排列

康托展开:已知一个排列,求这个排列在全排列中是第几个

ll kt() {	// 计算 p序列在全排列排第几 
	ll ret = 0;
	for(int i=1; i<=n; i++) {
		ll tmp = 0;
		for(int j=i+1; j<=n; j++)
			if(p[j] < p[i]) tmp++;
		ret += tmp * fac[n-i];  //f[]为阶乘
	}
	return ret;  
}

康托展开逆运算:已知在全排列中排第几,求这个排列

void kt(ll x, int n) {	// 求第 x的排列,返回为 p 
	memset(used, 0, sizeof(used));
	for(rint i=1; i<=n; i++) {
		int t = x/fac[n-i]+1, k = 0;
		for(rint j=1; j<=n; j++)
			if(!used[j] && ++k==t) {
				k = j;
				break;
			}
		used[p[i]=k] = 1;
		x %= fac[n-i];
	}
}

例题链接:点我啊╭(╯^╰)╮

题目大意:

    长度为 n n n 的排列 a a a ,有两种操作
     1 1 1 l l l r r r ,求 ∑ i = l r a i \sum_{i=l}^{r}a_i i=lrai
     2 2 2 x x x, 对 a a a 执行 x x x 次下一个排列

解题思路:

    发现 q × x ≤ 2 e 10 q \times x ≤ 2e10 q×x2e10,因此只会对末尾 15 15 15 个数字进行修改
    用逆康拓维护暴力修改末尾 15 15 15 个数字,然后前缀和维护即可

#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;
const int maxn = 1e6 + 5;
int n, q, a[maxn];
int p[20], used[20];
ll fac[20], pre[maxn];

ll kt() {	// 计算 p序列在全排列排第几 
	ll ret = 0;
	for(int i=1; i<=n; i++) {
		ll tmp = 0;
		for(int j=i+1; j<=n; j++)
			if(p[j] < p[i]) tmp++;
		ret += tmp * fac[n-i];  //f[]为阶乘
	}
	return ret;  
}
void kt(ll x, int n) {	// 求第 x的排列,返回为 p 
	memset(used, 0, sizeof(used));
	for(rint i=1; i<=n; i++) {
		int t = x/fac[n-i]+1, k = 0;
		for(rint j=1; j<=n; j++)
			if(!used[j] && ++k==t) {
				k = j;
				break;
			}
		used[p[i]=k] = 1;
		x %= fac[n-i];
	}
}
int main() {
	fac[0] = 1;
	for(int i=1; i<=15; i++) fac[i] = fac[i-1] * i;
	scanf("%d%d", &n, &q);
	for(int i=1; i<=n; i++) a[i] = i, pre[i] = pre[i-1] + a[i];
	ll cnt = 0; kt(cnt, 15);
	while(q--) {
		int op, l, r, x;
		scanf("%d", &op);
		if(op == 1) {
			scanf("%d%d", &l, &r);
			printf("%lld\n", pre[r] - pre[l-1]);
		} else {
			scanf("%d", &x);
			cnt += x, kt(cnt, 15);
			if(n > 15) for(int i=n-14; i<=n; i++)
					a[i] = p[i-n+15] + n - 15;
			else for(int i=1; i<=n; i++) a[i] = p[i+15-n] - 15 + n;
			for(int i=max(1, n-14); i<=n; i++) pre[i] = pre[i-1] + a[i];
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值