3380: 小Q的新玩具
时间限制: 1 Sec 内存限制: 128 MB题目描述
期末考试完了,小Q得到了一件套新玩具,总共有N个零件。
现在小Q想把新玩具搬回家里,可是他遇到了新的问题:
每个零件有自己的重量Wi,小Q要租车把它们带回家。车每次只能运总重量和小于Lim的玩具,按照其中最重的玩具的重量收费。
零件不能拆分成更小的部分。为了不打乱零件的顺序,增加自己拼装的难度,每次装车只能装连续的部分。
现在想请你帮助小Q计算把玩具全部搬回家的最小费用。
输入
第一行两个整数N和Limit。
接下来的N行,每行一个整数,代表第i个零件的重量。
输出
第一行一个数字,表示答案。
样例输入
8 17 2 2 2 8 1 8 2 1
样例输出
12
提示
N<=300000
Wi小于int范围
定义Dp[i]表示前搬运前i个玩具回家所需要的最小花费
Dp[i] = min(Dp[j] + w[j + 1][i])
w[j + 1][i]表示从j + 1 到 i 的最大玩具重量。
N很大,300000,w[j + 1][i]肯定不能预处理,而且N^2的DP肯定超时。
暴力的话可以用线段树维护w,然后N ^ 2,有30分。
正解,考虑优化
对于当前的i,它的转移来自于它前面的点,而且这些转移点的重量将呈不上升状:
图中蓝色表示决策点的对应需要加上的重量w[][],那么此时可以知道,对于每一种重量,它的最靠左点将是它的最优(因为靠右可能会引起Dp值增大,而靠左一定不会)
我们将上面的点放入set中维护Dp[j - 1] + w[j]的最小值,在满足lim的情况下,用set.begin()更新Dp[i],然后得到Dp[i]值。
单调队列维护一个不上升的w,此时红色代表w[i],单调队列弹出时,应同时弹出set中的Dp[j - 1] + w[j],换成Dp[j - 1] + w[i]再次放入,直到满足单调队列中不上升,放入。
线段树也可以实现,维护一个最小值。
Code:
一(可能二更容易理解):学的耿神的Set优化
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
const int Max = 300000;
typedef long long LL;
struct node{
multiset<LL>Set;
void push(LL v){ Set.insert(v);}
void del(LL v){ Set.erase(Set.lower_bound(v));}
LL top(){ return *Set.begin();}
}P;
int N, Lim;
int Q[Max + 5], A[Max + 5];
LL sum[Max + 5], Dp[Max + 5];
bool getint(int & num){
char c; int flg = 1; num = 0;
while((c = getchar()) < '0' || c > '9'){
if(c == '-') flg = -1;
if(c == -1) return 0;
}
while(c >= '0' && c <= '9'){
num = num * 10 + c - 48;
if((c = getchar()) == -1) return 0;
}
num *= flg;
return 1;
}
int main(){
//freopen("toy.in", "r", stdin);
//freopen("toy.out","w",stdout);
getint(N), getint(Lim);
for(int i = 1; i <= N; ++ i) getint(A[i]), sum[i] = sum[i - 1] + A[i];
int fro = 1, back = 0, lf = 0;
Q[++ back] = 1;
Dp[1] = A[1];
P.push(Dp[1]);
for(int i = 2; i <= N; ++ i){
while(sum[i] - sum[lf] > Lim && lf < i){
P.del(A[Q[fro]] + Dp[lf]);
if(Q[fro] == lf + 1) ++ fro, ++ lf;
else ++ lf, P.push(A[Q[fro]] + Dp[lf]);
}
while(fro <= back && A[Q[back]] <= A[i]){
if(fro == back) P.del(A[Q[back]] + Dp[lf]);
else P.del(A[Q[back]] + Dp[Q[back - 1]]);
-- back;
}
Q[++ back] = i;
if(fro == back) P.push(A[i] + Dp[lf]);
else P.push(A[i] + Dp[Q[back - 1]]);
Dp[i] = P.top();
}
printf("%lld\n", Dp[N]);
return 0;
}
解释一下这个代码,我也是好久才弄懂。
Q中存放的是各个不同的重量(理论上不能重复元素,但是去掉等号因为数据水也可以过)
但实际上是上图中每个高度最靠右的那一个值(而不是标出来的靠左的粉色!)
因为每次更新set中值的时候仍然是利用Q中的值更新(可以想象Q中存的就是j - 1,j是每条线最靠左的,所以j - 1就是前一条线最靠右)。
假设用A[Q[t]]来更新,那么按转移方程就应该是Dp[Q[t]这个的最靠左的那个值,也就是Q[t - 1]] + A[Q[t]]。
这里很巧妙,值得好好理解。
二:(同学代码)
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
int n,lim,a[300055],head,tail,minn;
long long sum[300055],dp[300055];
struct node{
int num,id,setnum;
node(){}
node(int a,int b,int c):num(a),id(b),setnum(c){}
}q[300055];
int main(){
//freopen("toy.in","r",stdin);
//freopen("toy.out","w",stdout);
set<long long>s;
int tmp;
scanf("%d%d",&n,&lim);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),
sum[i]=sum[i-1]+a[i];
head=tail=0;
for(int i=1;i<=n;i++){
while(head<tail && q[tail-1].num <= a[i]){
if(tail-1>head)
s.erase(q[tail-1].setnum);
tail--;
}
tmp=lower_bound(sum,sum+1+n,sum[i]-lim)-sum;
if(tail>head){
q[tail++]=node(a[i],i,a[i]+dp[q[tail-1].id]);
s.insert(q[tail-1].setnum);
}
else q[tail++]=node(a[i],i,0);
while(head<tail && sum[i]-sum[q[head].id]>lim){
if(head<tail-1)
s.erase(q[head+1].setnum);
head++;
}
dp[i]=dp[tmp]+q[head].num;
if(head<tail-1 && (!s.empty())) dp[i]=min(dp[i],*s.begin());
}
printf("%lld",dp[n]);
}
三:耿神给的线段树:
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int n,lim;
ll s[1010],mx[4010],f[1010];
void build(int x,int l,int r)
{
if(l==r)
{
mx[x]=s[l]-s[l-1];
return;
}
build(x<<1,l,l+r>>1);
build(x<<1|1,(l+r>>1)+1,r);
mx[x]=max(mx[x<<1],mx[x<<1|1]);
}
ll query(int x,int l,int r,int L,int R)
{
if(l==L&&r==R)return mx[x];
int mid=l+r>>1;
if(R<=mid)return query(x<<1,l,mid,L,R);
if(L>mid)return query(x<<1|1,mid+1,r,L,R);
return max(query(x<<1,l,mid,L,mid),query(x<<1|1,mid+1,r,mid+1,R));
}
int main()
{
freopen("toy.in","r",stdin);
freopen("toy.out","w",stdout);
scanf("%d%d",&n,&lim);
for(int i=1; i<=n; ++i)scanf("%lld",&s[i]),s[i]+=s[i-1];
build(1,1,n);
for(int i=1; i<=n; ++i)
{
ll mi=1ll<<60;
for(int j=i-1; ~j; --j)
{
if(s[i]-s[j]>lim)break;
mi=min(mi,f[j]+query(1,1,n,j+1,i));
}
f[i]=mi;
}
printf("%lld\n",f[n]);
}