序列上问题(逆元+整数快速幂)

题目链接:https://ac.nowcoder.com/acm/contest/393/D

链接:https://ac.nowcoder.com/acm/contest/393/D
来源:牛客网

题目描述

请你求出一个 1 ~ N 的排列,使得它正好有 K 个逆序对。

由于存在很多种这样的排列,所以要求出字典序最大的排列。

因为排列可能很长,所以你只用输出类似于将这个排列放到 N+1 进制下的值,即 Ni=1p[i](N+1)imod 1000000007(109+7)∑i=1Np[i]∗(N+1)imod 1000000007(109+7) ,其中 p 是你求出的排列。

输入描述:

一行两个整数 N,K 。

输出描述:

一行一个整数,表示答案。
示例1

输入

复制
4 2

输出

复制
2790

说明

p[1]=3,p[2]=1,p[3]=2,p[4]=4为字典序最大的结果。

备注:

对于所有100%100%的数据,有1N109,0KN×(N1)2

思路:当时做这题的时候超时了,走了一遍N 还以为可以过的,但是N是10^9 不出意外应该是过不了的,交一发只是试试运气而已
然后超时了就不知道怎么写了。。。
结束比赛之后,看了官网的思路,并没有看别人题解,才发现原来题目中的公式是一个等差数列乘以一个等比数列!!! 这就好办了 ,高中学过呀 等差乘等比怎么求和 有公式的 自己推一下就好了
知道了这个自己还是没有那么快做出来,还用到了整数快速幂 逆元的知识 就这两点了!! 都是自己懒,逆元现在才学,其实早就要学了,因为经常用到,自己实在太懒了,欠下的总是要还的
这不还是要学,不学根本A不了这道题的,然后又跑去把逆元学了 ,终于给过了这道题,就是代码有点小复杂 具体看代码:
#include<iostream>
#include<string>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cstdio>
using namespace std;
const int maxn=1000+50;
typedef long long LL;
const LL mod=1e9+7;
/**
有以下几种情况
当K<N时  只要把 刚好得到K个的数的M放在第一 其他的数从小到大是一个连续的序列 用数列求和就可以求出来了
所以就有1~M-1  M  M+1~N   三段
当K>N时  这时分为以下几段
首先找到一个数M  依次从N排到M得到的逆序数  使得K减去这些数  K就小于M  所以再加上另外一个单独的数Z  其他数就是一个连续的序列 求和就行了
N~M  Z 1~Z-1    Z+1`M-1  四段
有这些知识就可以求解了
*/
LL N,K;
LL Quick_pow(LL n)//我们要求的是 (N+1)^n
{
    LL D=N+1;
    LL cnt=D,ans=1;
    while(n)
    {
        if(n&1) ans=(ans*cnt)%mod;
        cnt=(cnt*cnt)%mod;
        n=n/2;
    }
    return ans;
}
LL Quick_pow1(LL n,LL m)//
{

    LL cnt=n,ans=1;
    while(m)
    {
        if(m&1) ans=(ans*cnt)%mod;
        cnt=(cnt*cnt)%mod;
        m=m/2;
    }
    return ans;
}
LL solve(LL l,LL r,LL ql,LL qr)//代表左右区间 左边界的次方  右边界的次方
{
//    cout<<l<<" "<<r<<" "<<ql<<" "<<qr<<endl;
//    cout<<"qr-ql+1 "<<qr-ql+1<<endl;
//    LL z=Quick_pow(2);
//    cout<<z<<endl;
    LL sum=0;
    sum+=l*Quick_pow(ql)%mod;
//    cout<<"*sum"<<sum<<endl;
//    cout<<"Quick_pow(ql+1)"<<Quick_pow(ql+1)<<endl;
//    cout<<"Quick_pow(qr-ql+1)-1"<<Quick_pow(qr-ql+1)<<endl;
    //sum=(sum+((r-l)>=0?1:-1)*(Quick_pow(ql+1))*(Quick_pow(qr-(ql+1)+1)-1)/N-r*Quick_pow(qr+1)%mod)%mod;
    sum=(sum+((r-l)>=0?1:-1)*((Quick_pow(ql+1))*(Quick_pow(qr-(ql+1)+1)-1)%mod)*Quick_pow1(N,mod-2)%mod-r*Quick_pow(qr+1)%mod)%mod;
    //cout<<"**sum"<<sum<<endl;
    sum=-sum*Quick_pow1(N,mod-2)%mod;
    //cout<<"sum:"<<sum<<endl;
    if(sum<0) sum+=mod;
    return sum;
}
LL judge(LL mid)
{
    LL z=K-(N-mid+1)*(mid+N-2)/2;

    if(z>=0&&z<mid-1) return 1;
    else if(z<0) return 2;//证明mid太小了
    else return 0;//证明mid太大了
}
int main()
{
//    cout<<20+75+125+1250<<endl;
    while(cin>>N>>K){


//    scanf("%lld%lld",&N,&K);
    LL ans=0;
    if(K<N)
    {
        LL M=K+1;
        LL D=N+1;
        ans=(ans+M*D%mod);
        if(M>1)
        ans=(ans+solve(1,M-1,2,M))%mod;
        if(M<N)
        ans=(ans+solve(M+1,N,M+1,N))%mod;
        cout<<ans<<endl;//出错了!下次改
    }
    else
    {
        //找到数M
        LL l=1,r=N;
        LL M;
        while(l<=r)
        {
            LL mid=(l+r)>>1;
            LL flag=judge(mid);
            if(flag==1)
            {
                M=mid;
                break;
            }
            else if(flag==2)
            {
                l=mid+1;

            }
            else r=mid-1;
        }
//        cout<<"M:"<<M<<endl;
        //找到了M
        LL Z=K-(N-M+1)*(M+N-2)/2+1;//找到 了Z
        ans=(ans+solve(N,M,1,N-M+1))%mod;//N~M 连续下降的序列
//        cout<<"*ans"<<ans<<endl;
        ans=(ans+Z*Quick_pow(N-M+2)%mod)%mod;//单独的Z
        if(Z!=1)
        {
            if(1<=Z-1)
            ans=(ans+solve(1,Z-1,N-M+3,N-M+3+(Z-2)))%mod;//1~Z-1  连续递增的数列
            if(Z+1<=M-1)
            ans=(ans+solve(Z+1,M-1,N-M+3+(Z-2)+1,N))%mod;//Z+1 ~ M-1连续递增序列

        }
        else
        {
            if(Z+1<=M-1)
            ans=(ans+solve(Z+1,M-1,N-M+3+(Z-2)+1,N))%mod;
        }

        cout<<ans<<endl;
    }
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/caijiaming/p/10567418.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值