Codevs 抄书问题1&2&3

对于抄书问题1,由于标签上写的DP,我就写了DP。。。。
设dp[I][j]表示前i本书由j个人抄的最小答案,则状态转移方程为
dp[I][j]=min{max(dp[k][j-1],s[I]-s[k])},其中s[]为前缀和,k从j-1到1枚举。
输出方案时,由于题目要求多解时使前面的人抄的尽量少,因此可以贪心地输出。
由于我们在前面的DP过程后已经知道了每个人抄书的页数的最大值的最小值dp[m][k],因此可以逆序枚举第i个人,将不超过dp[m][k]的书都交给i抄,然后得出的答案保证前边的人抄书尽可能少,这样就能AC抄书问题1了。时间复杂度
O(k*m^2+k);
#include<iostream>
#define maxn 105
#define inf 0x7fffff
using namespace std;
int m,k,book[maxn],s[maxn];
struct Anses{
	int from,to;
};
int dp[maxn][maxn];
int main(){
	ios::sync_with_stdio(false);
	cin>>m>>k;
	for(int i=1;i<=m;i++){
		cin>>book[i];
		s[i]=s[i-1]+book[i];
		dp[i][1]=s[i];
	}
	for(int i=2;i<=m;i++){
		for(int j=2;j<=k&&j<i;j++){
			int minx=inf;
			for(int k=i-1;k>=1;k--){
				int maxx=0;
				if(i-k<j-1)continue;
				if(maxx<dp[i-k][j-1]) maxx=dp[i-k][j-1];
				if(maxx<s[i]-s[i-k])  maxx=s[i]-s[i-k];
				if(minx>maxx) minx=maxx;
			}
			dp[i][j]=minx;
		}
	}
	int ansxx=dp[m][k];
	Anses ans00[maxn];
	int i=m,k0=k;
	while(k0){
		int j=i-1;
		while(s[i]-s[j]<ansxx&&j>=0) j--;
		if(s[i]-s[j]==ansxx){
			ans00[k0].from=j+1;
			ans00[k0].to=i;
			k0--;
			i=j;
		}
		else if(s[i]-s[j+1]<=ansxx){
			ans00[k0].from=j+2;
			ans00[k0].to=i;
			k0--;
			i=j+1;
		}
	}
	for(int i=1;i<=k;i++)cout<<ans00[i].from<<' '<<ans00[i].to<<endl;
	return 0;
}
但是对于抄书问题2&3,以上DP显然不行,因此得换思路。
刚才的讨论中,DP过程的作用,是求出m本书分成k份的最大代价的最小值,之后贪心地输出就可以了,是不是有二分答案的味道?
我们可以二分查找最大价值的最小值,然后用刚刚所得的贪心策略验证,如果分给了>k个人,说明二分的答案偏小,反之偏大。
但是codevs的数据神坑。。。。。
由于我们刚才的贪心策略,有可能根本用不了K个人就能在最优策略下抄完m本书,此时程序将前边的人分配了0本书!但是根据codevs上的数据,应该是每个人都至少抄一本书,呵呵。。。。
#include<iostream>
#define maxn 1000000+5
using namespace std;
int s[maxn],n,k,begin[maxn],end[maxn]; 
int main(){
	ios::sync_with_stdio(false);
	cin>>n>>k;
	if(!k)return 0;
	for(int i=1;i<=n;i++){
		cin>>s[i];
		s[i]+=s[i-1];
	}
	int lf=1,ri=s[n],ans,mid;
	for(int i=1;i<=k;i++)begin[i]=end[i]=i;
	while(lf<ri){
		drg:;
		mid=(lf+ri)>>1;
		int b=n,e=n,cnt=0;
		while(b>=0){
			if(s[b]-s[b-1]>mid){
				lf=mid+1;
				goto drg;
			}
			if(s[e]-s[b-1]<=mid) b--;
			else{cnt++;e=b;}
		}
		cnt++;
		if(cnt>k) lf=mid+1;
		else ri=mid;
	}
	int b=n,e=n,mark=0;
	for(int i=k;i>=1;i--){
		b=e;
		while(s[e]-s[b-1]<=ri){
			if(b==i){
				end[i]=e;
				for(int i=1;i<=k;i++)cout<<begin[i]<<' '<<end[i]<<endl;
				return 0;
			}
			b--;
		}
		end[i]=e;
		begin[i]=b+1;
		e=b;
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值