BZOJ2794/POI2012 Cloakroom

7 篇文章 0 订阅

Task
有n件物品,每件物品有三个属性a[i], b[i], c[i] .(a[i]<b[i])
再给出q个询问,每个询问由非负整数m, k, s组成,问是否能够选出某些物品使得:
1. 对于每个选的物品i,满足a[i]<=m且b[i]>m+s。
2. 所有选出物品的c[i]的和正好是k。
n<=1,000, q<=1,000,000.
c[i]<=1,000, 1<=a[i]<b[i]<=10^9
1<=m<=10^9, 1<=k<=100,000, 0<=s<=10^9

Solution
 离线.
 把询问和节点都根据左端点排序,保证询问[l,r]时,所有考虑的物品的左端点都满足条件.满足左端点的物品个数是不断增加的,一旦满足,接下来所有询问都会满足.那么现在只用考虑右端点了.
 直接确定所有物品再进行01背包肯定不行.那么考虑新思路.假如因为现在可选物品的范围一直增大,我们可以对当前物品的信息进行记录.由于k并不大,可以考虑把k存入下标:
 设dp[x]为当前价值构成x最远的b[i].
 对于询问可以直接判断dp[k]与m+s的关系,
 对于更新,用01背包进行更新即可.

题外话:
这道题的思路来源于BestCoder round#86 1005(hdu5808,题解戳这,题目链接戳这)的思路,都是询问一些符合范围的物品价值是否能够达到k.而且思路也是相近的,只不过BC的这道题信息多一维,用了分治来优化,思路更复杂一些.

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#define ll long long
#include<queue>
#include<set>
using namespace std;
inline void rd(int &res){
    res=0;char c;
    while(c=getchar(),c<48);
    do res=(res<<1)+(res<<3)+(c^48);
    while(c=getchar(),c>=48);
}
inline void print(ll x){
    if(!x)return ;
    print(x/10);
    putchar((x%10)^48);
}
inline void sc(ll x){
    if(x<0){x=-x;putchar('-');}
    print(x);
    if(!x)putchar('0');
    putchar('\n');
}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
const int M=1005;
const int N=1e6+5;
const int P=100000;
int dp[P+5],n,m,ans[N];
char s[2][3];
struct node{
    int l,r,v,id;
    node(){id=-1;}
    bool operator<(const node &tmp)const{
        if(l!=tmp.l)return l<tmp.l;
        return id<tmp.id;
    }
}A[M+N];
void pt(int c){
    for(int i=0;i<3;i++)putchar(s[c][i]);
    putchar('\n');
}
int main(){
    int n,i,j,mx=0,sum=0,cas,a,b,k;
    memset(dp,-1,sizeof(dp));
    rd(n);
    s[0][0]='N',s[0][1]='I',s[0][2]='E';
    s[1][0]='T',s[1][1]='A',s[1][2]='K';
    for(i=1;i<=n;i++){
        rd(A[i].v);rd(A[i].l);rd(A[i].r);
        Max(mx,A[i].r);
    }
    rd(m);
    for(i=1;i<=m;i++){
        rd(A[i+n].l);rd(A[i+n].v);rd(A[i+n].r);
        A[i+n].r+=A[i+n].l;
        A[i+n].id=i;
    }
    sort(A+1,A+1+n+m);
    dp[0]=mx+1;
    for(i=1;i<=n+m;i++){
        int l=A[i].l,r=A[i].r,v=A[i].v,id=A[i].id;
        if(~id){//询问 
            if(~dp[v]&&dp[v]>r)ans[id]=1;
            else ans[id]=0;
            continue;
        }//update (j+v)<=P  j<=P-v 
        for(j=min(P-v,sum);j>=0;j--){
            if(~dp[j])Max(dp[v+j],min(dp[j],r));//更新dp值
        }
        sum+=v;
    }
    for(i=1;i<=m;i++)pt(ans[i]);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值