kmjp's blog

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

Codeforces ECR #021 : F. Card Game

これ系久々に見たので思い出せなかった。
http://codeforces.com/contest/808/problem/F

問題

N枚のカードがあり、それぞれパワーP[i]、マジックナンバーC[i]、レベルL[i]が設定されている。

以下の条件を満たすようカードのsubsetを作りたい。
必要な自身の最小レベルを求めよ。

  • カードのパワーの総和がK以上
  • subset中2枚のカードのマジックナンバーの和は合成数
  • 各カードのレベルは自身のレベル以下

解法

自身のレベルが増えるほど使えるカードが増える。
よって自身のレベルは大きいほどよいので、二分探索で下限を求めることを考えよう。
そうすると3つ目の条件は以後考えなくてよくなる。

2つ目の条件を先に考える。
2つの正整数の和が素数になるのは、1+1を除けば奇数+偶数のケースのみである。
よってC[i]が偶数のカード群と奇数のカード群にに分けよう。

2つのグループのうち、同時に取れない条件があり、その範囲で何かしらのパラメータの総和を最大化する。
…となると、グラフのフローの最小カットを用いるいわゆる燃やす埋める問題に帰着できる。

よって、sourceから(自身のレベル以下の)偶数カード群に容量P[i]の辺を張り、同じく奇数カード群からsinkに容量P[i]の辺を張ろう。
偶数カードから奇数カードへは、C[i]の和が素数である場合に辺を張る。
なお、奇数カード群でもC[i]=1となるカードは2枚以上選択できないので、P[i]が最大のもののみ1つ用いるようにしよう。

あとは(自身のレベル以下の)P[i]の総和から、フローの最小カット=最大フロー分を引き、それがK以上かどうか判定すればよい。

int N,K;
int P[101],C[101],L[101];

template<class V> class MaxFlow_Ford {
public:
	struct edge { int to,reve;V cap;};
	static const int MV = 102;
	vector<edge> E[MV];
	int vis[MV];
	void add_edge(int x,int y,V cap,bool undir=false) {
		E[x].push_back((edge){y,(int)E[y].size(),cap});
		E[y].push_back((edge){x,(int)E[x].size()-1,undir?cap:0});
	}
	V dfs(int from,int to,V cf) {
		V tf;
		if(from==to) return cf;
		vis[from]=1;
		FORR(e,E[from]) if(vis[e.to]==0 && e.cap>0 && (tf = dfs(e.to,to,min(cf,e.cap)))>0) {
			e.cap -= tf;
			E[e.to][e.reve].cap += tf;
			return tf;
		}
		return 0;
	}
	V maxflow(int from, int to) {
		V fl=0,tf;
		while(1) {
			ZERO(vis);
			if((tf = dfs(from,to,numeric_limits<V>::max()))==0) return fl;
			fl+=tf;
		}
	}
};

const int prime_max = 1000000;
int NP,prime[100000],divp[prime_max];
map<int,int> M;

void cprime() {
	if(NP) return;
	for(int i=2;i<prime_max;i++) if(divp[i]==0) {
		prime[NP++]=i;
		for(ll j=1LL*i*i;j>=i&&j<prime_max;j+=i) divp[j]=i;
	}
}


int ok(int val) {
	int i,j;
	MaxFlow_Ford<ll> mf;
	ll tot=0;
	FOR(i,N) if(L[i]<=val && C[i]!=1) {
		tot+=P[i];
		if(C[i]%2==0) {
			mf.add_edge(100,i,P[i]);
			FOR(j,N) if(C[j]%2==1 && divp[C[i]+C[j]]==0) {
				mf.add_edge(i,j,1LL<<60);
			}
		}
		else mf.add_edge(i,101,P[i]);
	}
	
	int id=-1;
	FOR(i,N) if(C[i]==1 && L[i]<=val && (id==-1 || P[i]>P[id]) ) id=i;
	if(id>-1) {
		tot+=P[id];
		mf.add_edge(id,101,P[id]);
	}
	
	return tot-mf.maxflow(100,101)>=K;
}

まとめ

二部グラフまで思いついたのに、「フローと総和はうまく合わないな」と考えて燃やす埋める問題を忘れていた。