Codeforces 356(#207 Div1) D. Bags and Coins

問題

n枚の袋があって、i番目の袋の中にはa[i]枚のコインが入っている。コインは全体でs枚である。


ただし、袋の中にはさらに別の袋が入っていることがある。
a[i] = {1, 1, 3}で、全体でコインが3枚ということがある。
(3番目の袋には1番目の袋と2番目の袋とコインが一枚入っている状態が考えられる)


袋とコインの状態としてありうるものをどれか一通り出力せよ。
存在しない場合は-1を出力せよ。

制約条件

n, s, a[i]≦70000

方針

袋を全く入れ子にしないとするとコインの枚数はΣa[i]枚。
i番目の袋をj番目の袋に入れるとコインの総数はa[i]枚だけ減る。


つまり、減らさない袋を上手く選んでΣ{a[i] | i∈減らさない袋} = sにすればよい。
一番コインの入っている袋さえ使えば、どのような集合の選び方でも上手くいく。
(コインの少ない順に袋を並べたときに、次の袋に入れるようにすればよいから)


なので、ナップサック(partition問題)+経路復元を解けばよい。
だけなんだけど、品物と容量が大きいので解けない。
ビット並列化が必要。
でもメモリが足りないので、分割統治的に前半でx, 後半でs - xを作れるか見て、
作れるなら前半を更に再帰、後半も再帰……と経路復元ができる大きさまで分割してやる。


正直クソ問すぎる(解法は思いついてたけど、これが想定解のわけないだろうなと思ってた)けど、bitsetの使い方がわかったのでまあよい。

ソースコード

const int N = 20000;
int n, s, a[70000];
bool use[70000];

typedef bitset<70001> B;
B dp[N];

inline const B check(int l, int r){
	B res;
	res[0] = 1;
	for(int i = l; i < r; i++) res |= res << a[i];
	return res;
}
inline bool can(int l, int r, int s){
	
	if(r - l < N){
		dp[0] = 1;
		for(int i = l, j = 0; i < r; i++, j++) dp[j + 1] = dp[j] | dp[j] << a[i];
		if(!dp[r - l][s]) return 0;
		for(int i = r - 1, j = r - l - 1; i >= l; i--, j--){
			if(s >= a[i] && dp[j][s - a[i]]){
				s -= a[i];
				use[i] = 1;
			}
		}
		return 1;
	}
	
	int m = (l + r) / 2;
	const B dp1 = check(l, m), dp2 = check(m, r);
	
	rep(i, s + 1) if(dp1[i] && dp2[s - i]){
		
		return can(l, m, i) && can(m, r, s - i);
	}
	return 0;
}

int main(){
	scanf("%d%d", &n, &s);
	
	vector<pi> v;
	rep(i, n){
		scanf("%d", a + i);
		v.pb(mp(a[i], i));
	}
	static int to[70000];
	sort(all(v));
	sort(a, a + n);
	rep(i, n) to[v[i].second] = i;
	
	if(a[n - 1] > s){
		cout << -1 << endl;
		return 0;
	}
	use[n - 1] = 1;
	s -= a[n - 1];
	int m = n - 1;
	for(; m > 0 && a[m - 1] > s; m--);
	
	if(!can(0, m, s)){
		cout << -1 << endl;
		return 0;
	}
	
	rep(ii, n){
		int i = to[ii];
		if(i && !use[i - 1]) printf("%d 1 %d\n", a[i] - a[i - 1], v[i - 1].second + 1);
		else printf("%d 0\n", a[i]);
	}
	
	return 0;
}