結果

問題 No.2327 Inversion Sum
ユーザー HIcoderHIcoder
提出日時 2023-07-20 16:05:21
言語 C++17
(gcc 12.3.0 + boost 1.83.0)
結果
AC  
実行時間 65 ms / 2,000 ms
コード長 6,907 bytes
コンパイル時間 1,338 ms
コンパイル使用メモリ 119,372 KB
実行使用メモリ 8,520 KB
最終ジャッジ日時 2023-10-20 16:44:54
合計ジャッジ時間 3,780 ms
ジャッジサーバーID
(参考情報)
judge14 / judge12
このコードへのチャレンジ
(要ログイン)

テストケース

テストケース表示
入力 結果 実行時間
実行使用メモリ
testcase_00 AC 11 ms
7,328 KB
testcase_01 AC 56 ms
8,448 KB
testcase_02 AC 41 ms
7,912 KB
testcase_03 AC 10 ms
7,264 KB
testcase_04 AC 65 ms
8,520 KB
testcase_05 AC 13 ms
7,264 KB
testcase_06 AC 43 ms
8,124 KB
testcase_07 AC 28 ms
7,528 KB
testcase_08 AC 13 ms
7,000 KB
testcase_09 AC 56 ms
8,424 KB
testcase_10 AC 20 ms
7,264 KB
testcase_11 AC 7 ms
7,000 KB
testcase_12 AC 7 ms
7,000 KB
testcase_13 AC 6 ms
6,736 KB
testcase_14 AC 38 ms
7,696 KB
testcase_15 AC 64 ms
8,476 KB
testcase_16 AC 23 ms
7,532 KB
testcase_17 AC 7 ms
7,000 KB
testcase_18 AC 12 ms
7,000 KB
testcase_19 AC 18 ms
7,528 KB
testcase_20 AC 6 ms
6,736 KB
testcase_21 AC 6 ms
6,736 KB
testcase_22 AC 6 ms
6,736 KB
testcase_23 AC 6 ms
6,736 KB
testcase_24 AC 7 ms
6,736 KB
testcase_25 AC 6 ms
6,736 KB
testcase_26 AC 7 ms
6,736 KB
testcase_27 AC 6 ms
6,736 KB
testcase_28 AC 7 ms
6,736 KB
testcase_29 AC 6 ms
6,736 KB
testcase_30 AC 7 ms
6,736 KB
testcase_31 AC 6 ms
6,736 KB
testcase_32 AC 6 ms
6,736 KB
権限があれば一括ダウンロードができます

ソースコード

diff #

//AC
#include<iostream>
#include<set>
#include<algorithm>
#include<vector>
#include<string>
#include<set>
#include<map>
#include<numeric>
#include<queue>
#include<tuple>
#include<cmath>
using namespace std;
typedef long long ll;
const ll INF=1LL<<60;
typedef pair<int,int> P;
typedef pair<int,P> PP;
const ll MOD=998244353;



//xのy乗
ll mod_pow(ll x,ll y,ll mod){
    ll power=x;
    ll ret = 1;
    while(y>0){
        if(y&1){
           ret = ret*power%mod;
        }
        power = power*power%mod;
        y = y>>1;
    }
    return ret;

}



struct combination{
    typedef long long ll;
    std::vector<ll> fact,ifact;
    combination(ll n): fact(n+1),ifact(n+1){
        fact[0]=1;//0!=1通り
        for(ll i=1;i<=n;i++) fact[i] = fact[i-1]*i%MOD;
        //ifact[n] = fact[n].inv();
        
        // n!の逆元がifact[n]
        ifact[n] = mod_pow(fact[n],MOD-2,MOD);// n!を(mod-2)乗した後でmodであまりをとった
        for(ll i=n;i>=1;i--) ifact[i-1] = ifact[i]*i%MOD;
    }

    ll operator()(ll n,ll k){
        if(k<0 || k>n) return 0;
        // n!/( k!*(n-k)! )
        return (fact[n]*ifact[k])%MOD * ifact[n-k]%MOD;
    }

};


//転倒数を数えるために用いる
template<class Type> struct binary_indexed_tree{
    int N;
    vector<Type> bit;
    
  

    binary_indexed_tree(int n):N(n+1){
        bit = vector<Type>(n+1,0);
        N=n;
    }



    void add(int x,Type a){
        x++;//1から始めるための補正
        //for(int i=x; i<=N; i+=(i&-i)) bit[i] = addition(bit[i],a);
        while(x<=N){
            //bit[x] = addition(bit[x],a);
            bit[x] =bit[x]+a;
            x += x&-x;
        }
    }

    
    Type sum(int x){
        x++;//1から始まることに注意
        Type ret=0;
        //for(int i=x; i>0; i-=(i&-i)) ret = addition(ret,bit[i]);
        while(x>0){
            ret = ret+bit[x];
            x -= x&-x;
        }
        
        return ret;
    }

    //[l,r]の範囲
    // rはN-1以下
    Type get(int l,int r){
        if(r>N) return 0;//配列の外へのアクセス
        if(l>r) return 0;//本来は l<=r となるのでおかしい
        if(l==0) return sum(r);//[0,r]//ここでoutなわけか
        else return (sum(r) - sum(l-1));
    }
};

combination comb(2*100000+10);


//転倒数のカウントに用いる
ll calc(vector<int> arg){
    ll res=0;


    //segment_tree<int,op,e> seg();
    const int MAXN=1e5;
    binary_indexed_tree<int> bit(MAXN+5);
    
    //順列に出現する順番で見ていく
    for(int v:arg){
        //vよりも大きい値で インデックスがvよりも小さい数
        res+=bit.get(v+1,MAXN+1);
        bit.add(v,1);
    }


    return res;
};

int main(){
    int N,M;
    cin>>N>>M;
    vector<pair<int,int>> PM(M);
    vector<int> P(M),K(M);

    //vector<int> L(N,1);//L[i]=順列の0~i番目までで,空きがある場所
    vector<int> R(N,1);//R[i]=順列のi~N-1番目までで,空きがある場所

    for(int i=0;i<M;i++){
        cin>>P[i]>>K[i];
        P[i]--;K[i]--;
    
    
        //数字P[i]が数列のK[i]番目にある
        PM[i]=make_pair(P[i],K[i]);
    }
   



    ll ans=0;

    {
        ll cnt=0;
        //X,Yのどちらも固定されている場合

        sort(PM.begin(),PM.end(),
        [](const pair<int,int>& lhs,const pair<int,int>& rhs){
            return lhs.second<rhs.second;
        });
        //インデックスが昇順になるようにソート

        vector<int> ord;
        for(auto [p,k]:PM){//数字のみを取り出す
            ord.push_back(p);
        }

        ll res=calc(ord);
        res%=MOD;

        //(N-M)!をかける
        cnt=res*comb.fact[N-M]%MOD;

        ans+=cnt;
        ans%=MOD;
    

    }
    {
        //Xのみ順列の指定位置.Yの指定がない

        vector<int> L(N,1);//L[i]=順列の0~i番目までで,空きがある場所
        vector<int> usednum;//すでに条件で出現した数字

        for(int i=0;i<M;i++){
            usednum.push_back(P[i]);//数字
            L[K[i]]=0;
        }

        sort(usednum.begin(),usednum.end());

        for(int i=1;i<N;i++){
            L[i]=L[i-1]+L[i];
        }

       
        
        for(int i=0;i<M;i++){
            int p=P[i],k=K[i];//数字pが順列中にある場所k
            
            int Li=0;//0~k-1までで空きマスの数
            if(k-1>=0){
                Li=L[k-1];
            }

            //usednumのうちの数字pより大きいものは何個あるか
            //[,usednum.end())
            int num = usednum.end() - upper_bound(usednum.begin(),usednum.end(),p);

            //p+1~N-1のうち, num個が使われている. 

            //(Yの選び方) =[p+1,N-1]-num =  (N-1)-(p+1)+1 - num
            ll cnt=(max(0,N-p-1-num))%MOD;
            cnt*=Li%MOD;
            cnt%=MOD;

            if(N-M-1>=0){
                cnt*=comb.fact[N-M-1];
                cnt%=MOD;
            }else{
                cnt=0;
            }

            ans+=cnt;
            ans%=MOD;
        }

    }

    {
        //Yのみ順列が固定 Xは固定されない

        vector<int> R(N,1);//R[i]=順列のi~N-1番目までで,空きがある場所
        vector<int> usednum;

        for(int i=0;i<M;i++){
            usednum.push_back(P[i]);//すでに条件で使われた数のリスト
            R[K[i]]=0;
        }

        sort(usednum.begin(),usednum.end());


        for(int i=N-2;i>=0;i--){
            R[i]=R[i]+R[i+1];
        }



        //Yのみ順列の指定の位置.
        for(int i=0;i<M;i++){
            int p=P[i],k=K[i];//数字pが順列中にある場所k
            
            int Ri=0;//k+1~N+1までで空きマスの数
            if(k+1<N){
                Ri=R[k+1];
            }

            //p未満でusednumに使われている数
            int num = upper_bound(usednum.begin(),usednum.end(),p-1)- usednum.begin();
           
            //Yの選び方=[0,p-1] - num =  p-num;
            ll cnt=max(p-num,0);
            cnt*=Ri%MOD;
            cnt%=MOD;

            if(N-M-1>=0){
                cnt*=comb.fact[N-M-1];
                cnt%=MOD;
            }else{
                cnt=0;
            }

            ans+=cnt;
            ans%=MOD;

        }

    }



    
    {
       
        //X,Yのどちらも指定がない場合.ただし X<Y とする
        /*
        X,Yの選び方(ただしX<Y)で (n-m)C2

        X,Yを入れる場所の選び方で (n-m)C2 ただし, (Yの座標)<(Xの座標)となるようにする

        のこりの N-M-2個の数字の順列で(N-M-2)!とおり
        */
      
        ll cnt=comb(N-M,2)*comb(N-M,2)%MOD;
        
        ll fa=0;
        if(N-M-2>=0){
            fa=comb.fact[N-M-2];
        }

        cnt=cnt*fa%MOD;

        ans+=cnt;
        ans%=MOD;

    }

    cout<<ans<<endl;


}
0