codeforces(E. Long Permutation)康托展开

在这里插入图片描述
在这里插入图片描述

题意:给你一个n和q,1~n个数和q次查询
n表示1到n的全排列,一开始是1,2,3····n-1,n
查询有两种
1 x
表示将当前的排列往后递推x次,比如现在是[1,2,3,4]->[1,2,4,3](这是递推了1次)按照字典序
2 x y
输出排列中第x个数到第y个数的和

思路:
我们晓得全排列的复杂度是n!
比如5的全排列有5!=120种
但题目给的x是1e5的,并且q是2e5,所以题目给的排列最坏情况下会递推x*q次,也就是2e10次
而14个数的全排列为14!高达87178291200次!
所以最多会改变排列的最后14个数
所以利用逆康托展开计算出第x个排列为多少,并同时更新前缀和即可

关于康托展开(自行学习)

代码:

#include <bits/stdc++.h>
#define pb push_back
#define ll long long
#define IOS std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
template<class T> inline void read(T &x){
    x=0; register char c=getchar(); register bool f=0;
    while(!isdigit(c))f^=c=='-',c=getchar();
    while(isdigit(c))x=x*10+c-'0',c=getchar(); if(f)x=-x;
}
using namespace std;

ll n,q,tot,op,x,y;
ll pre[200005],vis[30];
ll c[2000005];
ll book[2000];
ll Z(int x)
{
    ll s=1;
    for(int i=1;i<=x;i++){s*=(ll)i;}
    return s;
}
int main()
{
    scanf("%lld %lld",&n,&q);
    ll id=1;
    if(n>14)
    {
        for(int i=n-13;i<=n;i++){vis[++tot]=(ll)i;}//映射
        for(int i=1;i<=n;i++){pre[i]=pre[i-1]+(ll)i;}//前缀和
        while(q--)
        {
            scanf("%lld",&op);//操作数
            if(op==1)
            {
                scanf("%lld %lld",&x,&y);
                printf("%lld\n",pre[y]-pre[x-1]);
                continue;
            }
            else
            {
                scanf("%lld",&x);
                id+=x;
                ll tmp=id-1;//tmp表示前面有几个比此排列小的
                for(int i=1;i<=20;i++){book[i]=0;}
                for(int i=1;i<=13;i++)//计算每一位
                {
                    ll base=tmp/Z(14-i);//下取整
                    for(int j=1;j<=14;j++)
                    {
                        if(book[j]==0&&base==0)
                        {c[i]=j;break;}
                        if(book[j]==0)
                        {base--;}
                    }
                    book[c[i]]=1;
                    ll cnt=0;
                    for(int j=1;j<c[i];j++)
                    {
                        if(book[j]==0){cnt++;}
                    }
                    tmp-=cnt*Z(14-i);
                }
                for(int i=1;i<=14;i++)//计算最后一位
                {
                    if(book[i]==0){c[14]=i;break;}
                }
                for(int i=n-13;i<=n;i++)//更新前缀和
                {
                    pre[i]=pre[i-1]+vis[c[i-n+14]];
                }
            }
        }
    }
    else
    {
        for(int i=1;i<=n;i++){pre[i]=pre[i-1]+(ll)i;}
        while(q--)
        {
            scanf("%lld",&op);
            if(op==1)
            {
                scanf("%lld %lld",&x,&y);
                printf("%lld\n",pre[y]-pre[x-1]);
            }
            else
            {
                scanf("%lld",&x);
                id+=x;
                ll tmp=id-1;
                for(int i=1;i<=20;i++){book[i]=0;}
                for(int i=1;i<=n-1;i++)
                {
                    ll base=tmp/Z(n-i);
                    for(int j=1;j<=n;j++)
                    {
                        if(base==0&&book[j]==0)
                        {c[i]=(ll)j;break;}
                        if(book[j]==0){base--;}
                    }
                    book[c[i]]=1;
                    ll cnt=0;
                    for(int j=1;j<c[i];j++)
                    {
                        if(book[j]==0){cnt++;}
                    }
                    tmp-=cnt*Z(n-i);
                }
                for(int i=1;i<=n;i++)
                {
                    if(book[i]==0){c[n]=i;break;}
                }
                for(int i=1;i<=n;i++)
                {
                    pre[i]=pre[i-1]+c[i];
                }
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值