結果

問題 No.2327 Inversion Sum
ユーザー Naoto FujiwaraNaoto Fujiwara
提出日時 2023-05-29 18:13:45
言語 C++14
(gcc 12.3.0 + boost 1.83.0)
結果
AC  
実行時間 61 ms / 2,000 ms
コード長 8,508 bytes
コンパイル時間 1,969 ms
コンパイル使用メモリ 98,096 KB
実行使用メモリ 8,332 KB
最終ジャッジ日時 2023-08-27 23:48:27
合計ジャッジ時間 4,238 ms
ジャッジサーバーID
(参考情報)
judge13 / judge12
このコードへのチャレンジ
(要ログイン)

テストケース

テストケース表示
入力 結果 実行時間
実行使用メモリ
testcase_00 AC 10 ms
7,068 KB
testcase_01 AC 54 ms
8,128 KB
testcase_02 AC 39 ms
7,628 KB
testcase_03 AC 10 ms
7,260 KB
testcase_04 AC 61 ms
8,332 KB
testcase_05 AC 12 ms
6,788 KB
testcase_06 AC 41 ms
7,804 KB
testcase_07 AC 27 ms
7,256 KB
testcase_08 AC 13 ms
6,784 KB
testcase_09 AC 54 ms
8,132 KB
testcase_10 AC 19 ms
7,004 KB
testcase_11 AC 6 ms
6,764 KB
testcase_12 AC 6 ms
6,844 KB
testcase_13 AC 6 ms
6,428 KB
testcase_14 AC 37 ms
7,368 KB
testcase_15 AC 61 ms
8,288 KB
testcase_16 AC 23 ms
7,316 KB
testcase_17 AC 8 ms
6,816 KB
testcase_18 AC 12 ms
6,728 KB
testcase_19 AC 17 ms
7,348 KB
testcase_20 AC 6 ms
6,544 KB
testcase_21 AC 7 ms
6,484 KB
testcase_22 AC 6 ms
6,556 KB
testcase_23 AC 6 ms
6,728 KB
testcase_24 AC 6 ms
6,688 KB
testcase_25 AC 6 ms
6,432 KB
testcase_26 AC 7 ms
6,416 KB
testcase_27 AC 6 ms
6,556 KB
testcase_28 AC 6 ms
6,496 KB
testcase_29 AC 6 ms
6,492 KB
testcase_30 AC 6 ms
6,440 KB
testcase_31 AC 6 ms
6,504 KB
testcase_32 AC 6 ms
6,500 KB
権限があれば一括ダウンロードができます
コンパイルメッセージ
main.cpp: 関数 ‘int main()’ 内:
main.cpp:257:18: 警告: structured bindings only available with ‘-std=c++17’ or ‘-std=gnu++17’ [-Wc++17-extensions]
  257 |         for(auto [p,k]:PM){//数字のみを取り出す
      |                  ^

ソースコード

diff #

#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;
    }

};



// segment_tree<ll,op,e> a(n) みたいに呼び出す
// Typeにはソートの基準が必要

/*
Type op(Type x, Type y){
    //(x,y)を比較する任意の演算子.今回はmaxとした
    return max(x,y);
}

Type e(){
    //initialize value
    return 0;
}
*/

int op(int x, int y){
    //(x,y)を比較する任意の演算子
    return x+y;
}

int e(){
    //initialize value
    return 0;
}




template<class Type,
    Type (*op)(Type,Type),
    Type (*e)()
    > class segment_tree{
    
    public:
        
        std::vector<Type> dat;
        int n=1;
        segment_tree(int n_){    
            while(n < n_){
                n*=2;
            }
            dat = std::vector<Type>(2*n-1,e());// 0,1,2,...,2*n-1,2*n-2
        }

        ~segment_tree(){
            std::vector<Type>().swap(dat);
        }

        void set(int k,Type a){
            update(k,a);
        }


        void update(int k,Type a){
            k+=n-1;
            dat[k] = a;
            while(k>0){
                k = (k-1)/2;
                dat[k]=op(dat[2*k+1],dat[2*k+2]);
            }
        }

        //[a,b)
        Type query(int a,int b,int k,int l,int r){
            if(r<=a || b<=l) return e();
            if(a<=l && r<=b) return dat[k];
            else{

                Type vl = query(a,b,2*k+1,l,(l+r)/2);
                Type vr = query(a,b,2*k+2,(l+r)/2,r);
                return op(vl,vr);
            }
        }

        //[a,b)の範囲でのmax
        Type query(int a,int b){
            return query(a,b,0,0,n);
        }

        Type operator[](int index){
            
            return get(index);
        }

        Type get(int index){
            index += n-1;
            return dat[index];
        }
};





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;
        }

    }

    {

        vector<int> R(N,1);//L[i]=順列の0~i番目までで,空きがある場所
        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;//k+1~N+1までで空きマスの数
            if(k+1>=N){
                Ri=0;
            }else{
                Ri=R[k+1];
            }

            //p未満でusednumに使われている数
            int num = upper_bound(usednum.begin(),usednum.end(),p-1)- usednum.begin();

            //[0,p-1]
            //Yの選び方= p-num;
            ll cnt=max(p-num,0);
            cnt*=Ri;
            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