結果
| 問題 |
No.2327 Inversion Sum
|
| コンテスト | |
| ユーザー |
|
| 提出日時 | 2023-05-29 18:13:45 |
| 言語 | C++14 (gcc 13.3.0 + boost 1.87.0) |
| 結果 |
AC
|
| 実行時間 | 68 ms / 2,000 ms |
| コード長 | 8,508 bytes |
| コンパイル時間 | 1,627 ms |
| コンパイル使用メモリ | 100,180 KB |
| 実行使用メモリ | 8,576 KB |
| 最終ジャッジ日時 | 2024-12-28 10:05:49 |
| 合計ジャッジ時間 | 3,610 ms |
|
ジャッジサーバーID (参考情報) |
judge1 / judge5 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| sample | AC * 3 |
| other | AC * 30 |
コンパイルメッセージ
main.cpp: In function 'int main()':
main.cpp:257:18: warning: structured bindings only available with '-std=c++17' or '-std=gnu++17' [-Wc++17-extensions]
257 | for(auto [p,k]:PM){//数字のみを取り出す
| ^
ソースコード
#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;
}