kmjp's blog

競技プログラミング参加記です

yukicoder : No.2133 Take it easy!

括弧列でも良い気がするが。
https://yukicoder.me/problems/no/2133

問題

整数Nに対し、1~NのPermutation Pを考える。
加えて、Q個の条件が与えられる。
各条件は、数列Pにおける部分列P[L,R]を示す。

各部分列のprefixは、奇数の登場頻度が偶数の登場頻度以上でなければならず、かつ部分列全体で奇数と偶数が同数でなければならない。
条件を満たすPは何通りか。

解法

P中の奇数を"("、偶数を")"と置き換えると、各条件は部分列が正しい括弧列であることを意味する。
複数の条件があるが、互いに共通部分を持たないように分解しよう。そうすると、各条件における括弧列の組み合わせは、カタラン数で表現できる。

int N,Q;
int C[2];
vector<int> E[505050];
const ll mo=998244353;
const int NUM_=400001;
static ll fact[NUM_+1],factr[NUM_+1],inv[NUM_+1];

ll catalan(int n) {
	return fact[2*n]*factr[n]%mo*factr[n+1]%mo;
}

ll ret=1;
void dfs(int L,int R) {
	if((R-L)%2) {
		cout<<0<<endl;
		exit(0);
	}
	int num=1;
	L++;
	while(L<R) {
		if(E[L].size()) {
			dfs(L,E[L][0]);
			L=E[L][0];
		}
		else {
			num++;
			L++;
		}
	}
	ret=ret*catalan(num/2)%mo;
	
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	inv[1]=fact[0]=factr[0]=1;
	for (int i=2;i<=NUM_;++i) inv[i] = inv[mo % i] * (mo - mo / i) % mo;
	for (int i=1;i<=NUM_;++i) fact[i]=fact[i-1]*i%mo, factr[i]=factr[i-1]*inv[i]%mo;
	
	cin>>N>>Q;
	C[0]=N/2;
	C[1]=N-N/2;
	if(N%2) N++;
	E[0].push_back(N);
	FOR(i,Q) {
		int L,R;
		cin>>L>>R;
		E[L-1].push_back(R);
	}
	
	FOR(i,N) if(E[i].size()) {
		sort(ALL(E[i]));
		E[i].erase(unique(ALL(E[i])),E[i].end());
		if(E[i][0]==i) E[i].erase(E[i].begin());
		if(E[i].empty()) continue;
		
		FOR(j,E[i].size()-1) {
			E[E[i][j]].push_back(E[i][j+1]);
		}
		E[i].resize(1);
		for(j=i+1;j<E[i][0];j++) {
			int f=0;
			FORR(e,E[j]) {
				if(e>=E[i][0]) f=1;
			}
			if(f) {
				E[j].push_back(E[i][0]);
				E[i][0]=j;
				break;
			}
		}
	}
	
	ret=1;
	dfs(0,N);
	FOR(i,C[0]) ret=ret*(i+1)%mo;
	FOR(i,C[1]) ret=ret*(i+1)%mo;
	cout<<ret<<endl;
}

まとめ

複数クエリが直行するように分解するの、時々出てくるな。
なんかライブラリ化した方がいいのかな。