#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;
}