1505 Copying Books

問題概要

mページの本をk人の写本屋が写本する。
各ページにはそれぞれコピーにかかる時間が設定されている。


k人の写本屋を使うために、本を連続する区間k個に分割する。
写本にかかる全体の時間は、それぞれの区間のコピーにかかる時間の最大値である。


本の時間の最小値を与える区間の分割を求めよ。
それが複数あるときは、1番目の区間を最も短く(それでも複数あるなら2番目の区間を最も短く…以下同様)するような分割を求めよ。


m≦500とする。

解法

区間の和(の最小値(以下ノルマ)は二分法により求まる。
(長さを決め打ちして、長さを超えた時点で区間を切り、それが全部jでk個以下であるかを判定することによりO(mlog Sum)でできる。Sumはページ数の和)


その後、なるべく小さいページ数を先頭から取っていく。
ここでは、先ほどの二分探索の関数を使いまわせる。
すなわち自分以降の人間がすべてノルマ以下かどうかはO(m)で判定できるので、O(m^2)の計算量で題意の分割の仕方が求められる。

ソースコード

int k,m,ans[500],tmp[500];
ll b[500],one;

bool ok(ll lim,int st,int time){
	ll s=0; int cut=0;
	for(int i=st;i<m;i++){
		if(b[i]>lim)return 0;
		if(s+b[i]>lim){
			if(++cut>=time)return 0;
			s=b[i];
		}
		else s+=b[i];
	}
	return 1;
}
void rec(int c,int cut,ll s){
	if(ans[0]>=0)return;
	if(c==m){
		rep(i,m)ans[i]=tmp[i];
		return;
	}
	if(!ok(one,c,k-cut))return;
	
	if(c&&cut<k-1)
	{
		tmp[c-1]=1;
		rec(c+1,cut+1,b[c]);
		tmp[c-1]=0;
	}
	if(s+b[c]<=one)rec(c+1,cut,s+b[c]);
}
int main(){
	int T; scanf("%d",&T);
	rep(U,T){
		scanf("%d%d",&m,&k);
		ll sum=0;
		rep(i,m)scanf("%lld",b+i),sum+=b[i];
		
		ll lo=0,hi=sum,mid;
		while(lo+1<hi){
			mid=(lo+hi)/2;
			if(ok(mid,0,k))hi=mid; else lo=mid;
		}
		one=hi;
		
		rep(i,m)tmp[i]=0; ans[0]=-1;
		rec(0,0,0);
		
		rep(i,m){
			if(i)printf(ans[i-1]?" / ":" ");
			printf("%d",b[i]);
		}
		puts("");
	}
	return 0;
}