DP2 1008

hotel

Miss D和gnaw出去玩的时候,发现一个很奇怪的旅馆,宾馆老板特别喜欢数字4和7,如果一个房间里住4或7个人,他就会很开心,不然他甚至不想让这个房间里住人。现在告诉你每个房间住的人数(7人以内),将一个原在i号房间的人移动到j房间的代价是abs(i-j),要想能满足老板的要求,花费的代价是多少?

输入
第一行一个数n,表示房间数
第二行n个数,表示每个房间原先住着的人数
输出
一个数,表示按照老板要求最小的花费
如果不能按要求分配,输出-1

100%:1<=n<=1e5

题解

状态设计是最难想的,f[i][j]为第i个房间向后一个房间移动j个人且第i个房间满足条件的最小操作数。

因为一个房间最多7个人,所以最多向后移动7个人,不过也看是移进来,所以第二维14,以7为基底。

在转移的时候枚举从这个房间转出和下个房间转出多少人,这样可以考虑到所有情况。

最后从第n-1个房间找答案,因为第n个房间不能转出。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=100005;
const int inf=1061109567;
int n,a[maxn];
int f[maxn][20];//从第i个房间向i+1的房间出去j个人的最小操作(j<7为进入 

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

int abs(int x){return x<0 ? -x : x ;}
bool cx(int x){
    if(!x||x==4||x==7) return true;
    return false;
}

int main(){
    freopen("hotel.in","r",stdin);
    freopen("hotel.out","w",stdout);
    read(n);
    for(int i=1;i<=n;i++) read(a[i]);
    memset(f,0x3f,sizeof(f));
    f[0][7]=0;
    for(int i=0;i<n;i++)
     for(int j=0;j<=14;j++)
      if(f[i][j]!=inf){
          int in=j-7;
          for(int k=0;k<=14;k++){
              int out=k-7;
              if(cx(a[i+1]+in-out))
                  f[i+1][k]=min(f[i+1][k],f[i][j]+abs(out));
            }
        }
    int ans=inf;
    for(int i=0;i<=14;i++)
     if(cx(a[n]+i-7))//n没有后继,所以从n-1找答案 
      ans=min(ans,f[n-1][i]);
    printf("%d", ans==inf ? -1 : ans);
}
hotel

 


gift

Gnaw给Miss D准备了一个长为n的礼物,还让m个朋友来帮他完成这个礼物,每个人开始是站在位置p,最多可以装扮连续的长为x的部分,一个人可以不参与,一旦参与,他装扮的部分一定包含他一开始站的位置。每个人装扮长为1的部分能让整个礼物增加y的美丽度,gnaw想让整个礼物的美丽值最高,会是多少?

输入
第一行两个整数n m,表示礼物长度和朋友数。
接下来n行,每行3个整数,分别为一个人最多装扮长度,单位美丽值和初始位置。
输出
一个整数,表示整个礼物最大的美丽值

1 <= n <= 16 000
1 <= m <= 100
1 <= y <= 10 000

ps:只要p在他涂的区间就合法。

题解

可以想到f[i][j]表示前i个人涂前j个单位的最大美丽值,枚举区间右端点j和左端点k(k,j]。

f[i][j]=max(f[i-1][k]+(j-k)*y) k+1<=p<=j并且区间长度不超过j

这样就能保证区间一定选到了p

变一下形f[i][j]=max(f[i-1][k]-ky+jy)

jy是定值,所以只要f[i-1][k]-ky最大即可,可以用单调队列维护(滑动区间求最大值)

不过发现要对人按p排序,因为如果前面有人能够放而且对某个状态没有影响,那么那个状态就可以更大。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=16005;
const int maxm=105;
int n,m;
int f[maxm][maxn];//前i个人装饰前j个单位的最大值 
int h,t,s[maxn];
struct person{
    int x,y,pos;
    bool operator < (const person a){return pos<a.pos;}
}a[maxm];

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

int get(int i,int j,int w){return f[i][j]-j*w;}

int main(){
    freopen("gift.in","r",stdin);
    freopen("gift.out","w",stdout);
    read(n);read(m);
    for(int i=1;i<=m;i++) read(a[i].x),read(a[i].y),read(a[i].pos);
    sort(a+1,a+m+1);
    for(int i=1;i<=m;i++){
        h=1,t=0;
        for(int j=0;j<=n;j++){
            f[i][j]=max(f[i-1][j],f[i][j-1]);//前缀求最大值 
            if(j<a[i].pos&&j+a[i].x>=a[i].pos){//把上一层的f放进去 
                while(h<=t&&get(i-1,s[t],a[i].y)<get(i-1,j,a[i].y)) t--;
                s[++t]=j;
            }
            if(j>=a[i].pos&&j<a[i].pos+a[i].x){//右端点在pos后才更新,保证选择了pos 
                while(h<=t&&s[h]+a[i].x<j) h++;
                f[i][j]=max(f[i][j],get(i-1,s[h],a[i].y)+j*a[i].y);
            }
        }
    }
    printf("%d",f[m][n]);
}
gift

 

posted @ 2019-10-09 10:30  _JSQ  阅读(110)  评论(0编辑  收藏  举报