kmjp's blog

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

東京工業大学プログラミングコンテスト2015 : O - 数列色ぬり

最初のアプローチは思いついたけど、最後までは自力でたどり着けず。
http://ttpc2015.contest.atcoder.jp/tasks/ttpc2015_o

問題

1~Nのpermutationである数列Aが与えられる。
数列の一部の要素を赤または青で塗ったとする。

  • i<jであるi,jに対しA[i]、A[j]が赤で塗られている場合、A[i]<A[j]を満たす
  • i<jであるi,jに対しA[i]、A[j]が青で塗られている場合、A[i]>A[j]を満たす

上記を満たすような色の塗り方をしたとき、色を塗れる要素の最大数を求めよ。

解法

赤い要素は単調増加列、青い要素は単調減少列を成す。
赤い要素でLIS、青い要素でLDSを構築すると、運がいいと|LIS|+|LDS|の分の要素を塗ることができる。
ただし、LIS,LDSをどうとっても、両方で必要な要素が生じる場合、その要素は赤か青どちらかにしか塗れないので、解は|LIS|+|LDS|-1となる。
よってこの問題は、「LIS,LDSをどうとっても、両方で必要な要素が生じる」かどうかを求める問題と言える。

…ここまでは自力でたどり着いたが、ここからは自力でうまく解けなかったのでEditorialを参照。
Writer解でない別解法の方がしっくりきたので以下そちらを回答。

まず|LISの組み合わせ数|×|LDSの組み合わせ数|をDPで求める。
そして、各A[i]に対し、|A[i]を含むLISの組み合わせ数|×|A[i]を含むLSSの組み合わせ数|を求めその総和を取る。
(LIS/LDSの組み合わせ数はBITを使ったDPで求められる)

両者の値が一致する場合、LIS/LDSをどうとってもどこか1個の値は共有しなければならないので解は|LIS|+|LDS|-1。
そうでなければ値を共有せずLIS/LDSを作れるので解は|LIS|+|LDS|。

なお、|LISの組み合わせ数|×|LDSの組み合わせ数|はとても大きいので、多倍長整数を使って整数するか、適当な数の剰余を取って比較することになる。
LISやLDSの組み合わせ数が任意の数列を作るのはさほど難しくないため、1種類の剰余で処理するのは危険である(CFなら狙って落とされる)。
そのため複数の剰余で計算しておいた方が良い。

#include <limits>
template<class CC> vector<vector<int>> LIS_ext(vector<CC>& v) {
	int i,N=v.size();
	if(v.empty()) return vector<vector<int>>();
	vector<CC> dp(N,(numeric_limits<CC>::max)()),id(N),dp2(N,(numeric_limits<CC>::min)());
	FOR(i,N) dp[id[i]=lower_bound(dp.begin(),dp.end(),v[i]) - dp.begin()] = v[i];
	int nl = *max_element(id.begin(),id.end());
	vector<vector<int>> ret(nl+1);
	for(i=N-1;i>=0;i--) ret[id[i]].push_back(i), dp2[id[i]]=max(dp2[id[i]],v[i]);
	return ret;
}

ll mo;

template<class V, int ME> class BIT_mod {
public:
	V bit[1<<ME];
	void clear(){ZERO(bit);};
	V operator()(int e) {V s=0;e++;while(e) s+=bit[e-1],e-=e&-e; return s%mo;}
	V add(int e,V v) { e++; while(e<=1<<ME) { bit[e-1]+=v; bit[e-1] -= (bit[e-1]>=mo)?mo:0; e+=e&-e;}}
};
BIT_mod<int,20> bt;

int N;
vector<int> A[2],V[2];
int rev[2][101010]; //rev A
int lrank[2][101010]; //lis rank

int order[2][101010]; //lis order order->id
int order_r[2][101010]; //lis order id->order
int orders[2][101010]; //lis order per rank
ll dp[2][2][101010];
BIT_mod<ll,20> bit;

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	A[0].push_back(0);
	A[1].push_back(0);
	FOR(i,N) cin>>x, A[0].push_back(x), A[1].push_back(N+1-x);
	A[0].push_back(N+1);
	A[1].push_back(N+1);
	N+=2;
	
	int ma=-1;
	FOR(i,2) {
		FOR(j,N) rev[i][A[i][j]]=j;
		auto VL=LIS_ext(A[i]);
		ma+=VL.size()-2;
		int cur=0;
		FOR(j,VL.size()) {
			vector<int> v;
			FORR(r,VL[j]) v.push_back(A[i][r]), lrank[i][r]=j;
			sort(ALL(v));
			FOR(x,v.size()) order[i][orders[i][j]+x]=rev[i][v[x]], order_r[i][rev[i][v[x]]] = orders[i][j]+x, V[i].push_back(v[x]);
			orders[i][j+1]=orders[i][j+2]=orders[i][j]+VL[j].size();
		}
	}
	
	ll momo[]={1000000007,1000000009,1000000021};
	FORR(r,momo) {
		mo = r;
		FOR(i,2) {
			
			// normal order
			bit.clear();
			dp[i][0][0]=1;
			bit.add(1,1);
			for(x=1;x<N;x++) {
				int ra=lrank[i][x];
				int L=orders[i][ra-1];
				int R=lower_bound(V[i].begin()+L,V[i].begin()+orders[i][ra],A[i][x])-V[i].begin();
				dp[i][0][x]=(bit(R)+mo-bit(L))%mo;
				bit.add(1+order_r[i][x],dp[i][0][x]);
			}
			// rev order
			bit.clear();
			dp[i][1][N-1]=1;
			bit.add(N,1);
			for(x=N-2;x>=0;x--) {
				int ra=lrank[i][x];
				int R=orders[i][ra+2];
				int L=lower_bound(V[i].begin()+orders[i][ra+1],V[i].begin()+R,A[i][x])-V[i].begin();
				dp[i][1][x]=(bit(R)+mo-bit(L))%mo;
				bit.add(1+order_r[i][x],dp[i][1][x]);
			}
		}
		
		ll tot=dp[0][0][N-1]*dp[1][0][N-1]%mo;
		ll tot2=0;
		for(x=1;x<N-1;x++) (tot2 += dp[0][0][x]*dp[0][1][x]%mo*dp[1][0][x]%mo*dp[1][1][x])%=mo;
		
		if(tot!=tot2) {
			ma++;
			break;
		}
	}
	
	cout<<ma<<endl;

}

まとめ

LIS/LDSの共有要素判定に組み合わせ数を使うのは思いつかなかった…。