洛谷P2577 [ZJOI2005]午餐 dp

正解:序列dp

解题报告:

传送门!

这题首先要想到一个显然的贪心:每个窗口的排队顺序都是按照吃饭时间从大到小排序的

证明如下:

这种贪心通常都是用微扰法,这题也不例外

现在假如已经确定了每个窗口有哪些人辣,那就是要排除最优顺序使得吃完饭的时间最短

因为两个窗口直接互不影响,所以可以先单独抽出一个窗口来看,就相当于只有一个窗口了

然后选取两个人x,y,设a表示排队时间b表示吃饭时间,令b[x]>b[y]

于是可以列出式子

对于x排在前面,T1=a[x]+max(b[x],a[y]+b[y])

对于y排在前面,T2=a[y]+max(b[y],a[x]+b[x])

因为已经令b[x]>b[y]辣,所以T2=a[y]+a[x]+b[x]

所以T1-T2=max(b[x],a[y]+b[y])-a[y]-b[x]

当b[x]>a[y]+b[y]时,T1-T2=-a[y]<0,T1<T2

b[x]<a[y]+b[y]时,T1-T2=b[y]-b[x]<0,T1<T2

综上,当x排在前面是T1<T2

所以按照吃饭时间顺序从大到小排是最优的

然后得到了这个贪心之后就变成,已知排队顺序,有两个窗口,求怎么安排时间最小

显然是设f[i][j]:排到第i个人辣一号窗口的排队时间为j的最小吃饭时间

然后记个前缀和sum可以求出二号窗口的排队时间嘛

然后转移就一样是分类讨论走一波就欧克了鸭

还是大概港下QwQ

首先可以到一号窗口,然后到了一号窗口之后有两个情况

第一个是前面的人吃完饭的时间比第i个人的吃饭时间还要长,那就f[i][j+a[i]]=f[i-1][j]

第二个是前面的人吃完饭的时间比第i个人的吃饭时间短一些,那就f[i][j+a[i]]=j+a[i]+b[i]

当然实际上是不用分类讨论的取个max就好QwQ

然后也可以到二号窗口,一样的两个情况一样的思路,懒得详细写辣,反正就max(f[i-1][j],sum[i]-j+a[i]+b[i])

然后放下代码,,,

这个代码是我5个月之前写的辣,,,其实码风差不多,就麻油快读,麻油压行,然后代码里的变量名有点不一样,但思路理顺辣看代码还是比较轻松的辣QwQ所以我就懒得改辣QwQ

over!

 

#include<bits/stdc++.h>
using namespace std;
int n,f[202][40002],sum[202];
struct str
{
    int d,e;
}a[202];
bool cmp(str x,str y)
{
    return x.e>y.e;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i].d>>a[i].e;
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i].d;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=sum[n];j++)f[i][j]=10000;
    int ans=100000;
    f[0][0]=0;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=sum[i];j++)
        {
            if(j>=a[i].d)f[i][j]=min(f[i][j],max(f[i-1][j-a[i].d],j+a[i].e));
            f[i][j]=min(f[i][j],max(f[i-1][j],sum[i]-j+a[i].e));
        }
    for(int j=0;j<=sum[n];j++)ans=min(ans,f[n][j]);
    cout<<ans;
    return 0;
}
View Code

 

猜你喜欢

转载自www.cnblogs.com/lqsukida/p/10396908.html