[FROM WOJ]#2501 圣诞节的花环

#2501 圣诞节的花环

这个做法是有一天在ssldw打代码时听说的

题面
圣诞节就要到了。现在有个困难的任务摆在你眼前——装饰房间。

你手头上有n朵大小相同的花,第i朵花的重量为wi。现在打算用一根绳将这n朵花按顺序穿起来,挂在天花板上。绳子被m个点固定,也就是绳子的一头被固定在1号点,另外一头固定在m号点,中间部分需要固定在剩余的点。当然,装饰还有一些规则要注意:

(i) 每一段需要包含非0的偶数个花朵。正因如此,我们可以将每一段划分为两个半段。

(ii) 为了减小你的客人撞到花环的可能,花环不能挂的太低:也就是说,每个半段不能超过d朵花。

(iii) 最后,你需要让所有半段的重量的最大值最小。

下图是一个不错的安排方案,圈中的数字代表花朵的重量。
一个不错的安排方案
输入
第一行包含三个正整数n,m和d(1 ≤ n ≤ 15000,2 ≤ m ≤ 10000,1 ≤ d ≤ 2000,且n*d ≤ 5000000)。

接下来一行包含n个正整数w1,w2,…,wn(1 ≤ wi ≤ 10000),代表对应花的重量。

输出
输出一行一个整数,代表最小的所有半段重量的最大值。如果没有方案满足条件,那么你只要输出“BAD”(不包括引号)

样例输入
6 3 10
1 1 100 100 1 1

样例输出
200

SOL
这是一个简单的二分题,做这道题是很容易让人想起NOIP的跳石头,不过这道题的限制条件有点复杂——首先当然得满足分成m-1段,但其次每段得保证可以分成长度大于0的两个半段,也就是说每段长度必须为大于0的偶数,然后每个半段的长度不超过d
所以这道题有一点皮,不能直接二分了事,于是ssldw就想到了二分+DP的做法来处理这个限制条件
设flag[i][p]为前i个数在当前限制下的最少段数,p表示当前段的奇偶(这么做是为了找上一段的状态),每次枚举i,然后从小到大枚举半段长度即可找到答案。

代码:

#include<bits/stdc++.h>
#define ll long long
#define N 15015
using namespace std;
inline int rd(){
	int data=0;static char ch=0;ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
	return data;
}
inline void write(int x){if(x>9)write(x/10);putchar(x%10+'0');}
int n,m,d,ans=-1,s[N],l,r,flag[N][2];
int main(){
	n=rd();m=rd();d=rd();
	m--;
	if((n>>1)<m){puts("BAD");return 0;} 
	l=-1;
	for(int register i=1;i<=n;i++){
		int x=rd();
		s[i]=s[i-1]+x;
		l=max(x,l);
	}
	r=s[n];
	int mid;
	while(l<=r){
		mid=(l+r)>>1;
        for(int i=0;i<=n;i++)flag[i][0]=flag[i][1]=m+1;
        flag[0][0] = 0;
		for(int register i=1;i<=n;i++){
			int maxl=min((i>>1),d);//枚举节点位置 
			for(int register p=0;p<=1;p++){//奇偶(整段) 
				int j=1;
				while(j<=maxl&&s[i]-s[i-j]<=mid){//枚举半段的长度 
					int t=i-j;
					if(s[t]-s[t-j]<=mid&&flag[t-j][1-p]+1<flag[i][p])flag[i][p]=flag[t-j][1-p]+1;
					j++;
				}
			}	
		}
		if(flag[n][m&1]<=m){
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	if(ans==-1)puts("BAD");
	else write(ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值