結果
| 問題 | No.2327 Inversion Sum |
| コンテスト | |
| ユーザー |
|
| 提出日時 | 2023-07-20 16:05:21 |
| 言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
| 結果 |
AC
|
| 実行時間 | 71 ms / 2,000 ms |
| コード長 | 6,907 bytes |
| コンパイル時間 | 1,754 ms |
| コンパイル使用メモリ | 114,272 KB |
| 最終ジャッジ日時 | 2025-02-15 15:53:57 |
|
ジャッジサーバーID (参考情報) |
judge4 / judge2 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| sample | AC * 3 |
| other | AC * 30 |
ソースコード
//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;
}