假如我们已知第i天可以购买的所有商品,那么剩下就是01背包的问题了.能够购买的所有商品由两个条件决定:编号下标和距离.我们可以减少一个维度使问题更简单.
假如把询问和点一起按照距离d排序,那么就能保证询问到i时,所有考虑到的点x一定满足 dis[x]<=di 这一条件.
现在问题就转化成了:
询问:在 [Li,Ri] 区间中能否找到一些点满足其价值之和= sumi .
更新:点x的价值为 val[x] .
对于区间问题,可以考虑线段树求解.既然询问价值和的可能性,而且 sumi≤100 ,那么我们可以用一个bool数组h,记录这个区间所有能够取到的价值和,作为一个区间的状态.
对于单点更新x操作:
对每个经过x的区间用 O(sum) 的复杂度更新h数组.
对区间查询 [l,r] 操作:
用 O(sum2) 的复杂度对两个区间进行合并.
最终的复杂度为: O(m∗logn∗10000+n∗logn∗100) ,你会发现它和暴力没有什么差别T T.
这里有一个大优化!
对于单点更新x操作:
对于更新前 每个的
h[j]=1
可以保证更新后 得到:
h[j]=1
,
h[j+val[x]]=1
.
假如我们把h数组看成一个01串,那么
h[j]=1
=>
h[j+val[x]]=1
可以把答案看作把这个串整体移动
val[x]
位再按位或上更新前的01串的结果.
对于区间合并:
假设当前区间的01串为a1,两个儿子的01串分别为a2,a3.
a1|=a2.
a1|=a3.
对于a3的每个值为1的位i: a1|=a2<<i
那有什么能够高效地完成以上二进制的操作?!
bitset!!
它相当于把64位的数字接在一起,形成一个01串,并且支持二进制的所有运算,比如按位或,位移等.
复杂度: O(len/64) .len表示01串的长度.
用bitset完成操作后,时间复杂度为:
O(mlogn∗sum∗sum/64+nlogn∗sum/64) .
当然这种思路有一个神奇的解法:
可用BIT求一个区间内每个商品的个数.
商品的种类最多只有100种,而每种商品的数量却很多,这种情况可以选择多重背包进行优化.把物品总数减少到 100∗logn .在dp时依然可以用bitset优化,效果显著!!
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<bitset>
using namespace std;
const int M=105;
int n,m;
int ans[100005];
int id=0,l,r,c,sum;
bitset<M>dp;
inline void rd(int &res){
char c;res=0;
while(c=getchar(),c<48);
do res=(res<<1)+(res<<3)+(c^48);
while(c=getchar(),c>47);
}
struct node{
int v,d,id;
}A[20005];
struct LZ{
int l,r,d,sum,id;
}Q[100005];
int bit[20005][105];
void add(int x,int y){
while(x<=n){
bit[x][y]++;
x+=x&-x;
}
}
int query(int x,int y){
int sum=0;
while(x){
sum+=bit[x][y];
x^=x&-x;
}
return sum;
}
bool cmp(node a,node b){return a.d<b.d;}
bool cmp1(LZ a,LZ b){return a.d<b.d;}
int val[20005];
int main(){
int cas;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)rd(A[i].v);
for(int i=1;i<=n;i++)rd(A[i].d),A[i].id=i;
sort(A+1,A+n+1,cmp);
for(int i=1;i<=m;i++){
ans[i]=0;
rd(Q[i].l);
rd(Q[i].r);
rd(Q[i].d);
rd(Q[i].sum);Q[i].id=i;
}
sort(Q+1,Q+m+1,cmp1);
int x=1;
for(int i=1;i<=m;i++){
while(x<=n&&A[x].d<=Q[i].d){
add(A[x].id,A[x].v);
x++;
}
int can=query(Q[i].r,Q[i].sum)-query(Q[i].l-1,Q[i].sum);
if(can){ans[Q[i].id]=0;}
else{
dp.reset();
dp[0]=1;
int cnt=0;
for(int j=1;j<Q[i].sum;j++){
int s=query(Q[i].r,j)-query(Q[i].l-1,j),p=1;
while(p<s){
val[++cnt]=p*j;
s-=p;
p<<=1;
}
if(s)val[++cnt]=s*j;
}
for(int j=1;j<=cnt;j++){
dp|=dp<<val[j];
if(dp[Q[i].sum])break;
}
if(!dp[Q[i].sum])ans[Q[i].id]=1;
}
}
for(int i=1;i<=m;i++)putchar(ans[i]^48);
return 0;
}
当然最稳(qi)定(pa)的正解的思路是分治!
官方题解传送门: http://bestcoder.hdu.edu.cn/
对于 [L,R] 区间内的所有询问,我们可以分为 [L,mid][mid+1,R] 两个区间,对于左右端点都在左区间或者都在右区间的询问递归求解.
那么现在只要回答左端点在左区间,右端点在右区间的询问.
假设 dpl[i][j] 表示 [I,mid] 达到价值和为j的最大距离最小值.
dpr[i][j] 表示 [mid+1,r] 达到价值为j的最大距离最小值.
可以用
(r−l+1)∗sum
的复杂度处理出以上两个数组,对于每个询问,找到
[li,ri]
区间内到达sumi的最大距离的最小值
a
,再与
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=1e5+5;
const int N=2e4+5;
const int S=105;
const int oo=1e9+5;
struct node{
int id,l,r,sum,d;
}Q[M];
inline void rd(int &res){
res=0; char c;
while (c=getchar(),c<48);
do{
res=(res<<3)+(res<<1)+(c^48);
}while (c=getchar(),c>=48);
}
bool cmp(node a,node b){
if(a.l!=b.l)return a.l<b.l;
return a.r<b.r;
}
int dp[S][N],res[M],val[N],n,m,dis[N],q[M],st[N];
void solve(int L,int R){//Q.l<=mid;Q.r>=mid
int mid=L+R>>1,i,j,tot=0;
if(L==R){
for(i=st[L];i<=st[R+1]-1;i++){
if(Q[i].r==Q[i].l&&Q[i].l==L)q[++tot]=i;
}
for(i=1;i<=tot;i++){
int id=q[i];
if(Q[id].d>=dis[L]&&Q[id].sum==val[L])res[Q[id].id]=0;
else res[Q[id].id]=1;
}
return ;
}
for(i=st[L];i<=st[mid+1]-1;i++){
if(Q[i].r>mid&&Q[i].r<=R)q[++tot]=i;
}
if(tot){
for(i=L;i<=R;++i){
for(j=1;j<S;++j)dp[j][i]=oo;
dp[0][i]=0;
}
dp[val[mid]][mid]=dis[mid];
dp[val[mid+1]][mid+1]=dis[mid+1];
for(i=mid-1;i>=L;--i){//dp[i][j]表示[i,mid]区间中,得到j 最远距离的最小值
for(j=0;j<S;++j){
dp[j][i]=dp[j][i+1];
if(j>=val[i])dp[j][i]=min(dp[j][i],max(dp[j-val[i]][i+1],dis[i]));
}
}
for(i=mid+2;i<=R;++i){
for(j=0;j<S;++j){
dp[j][i]=dp[j][i-1];
if(j>=val[i])dp[j][i]=min(dp[j][i],max(dp[j-val[i]][i-1],dis[i]));
}
}
for(i=1;i<=tot;++i){//横跨mid的询问
int id=q[i];
int mi=oo,l=Q[id].l,r=Q[id].r,sum=Q[id].sum;
for(j=0;j<=Q[id].sum;++j){
mi=min(mi,max(dp[j][l],dp[sum-j][r]));
}
if(mi<=Q[id].d)res[Q[id].id]=0;
else res[Q[id].id]=1;
}
}
solve(L,mid);
solve(mid+1,R);
}
int find(int x){//Q[i].l>=x
int l=1,r=m,res=m+1;
while(l<=r){
int mid=l+r>>1;
if(Q[mid].l>=x){
r=mid-1;
res=mid;
}else l=mid+1;
}return res;
}
int main(){
int i,j,k,cas,a,b,c;
rd(n);rd(m);
for(i=1;i<=n;i++)rd(val[i]);
for(i=1;i<=n;i++)rd(dis[i]);
for(i=1;i<=m;i++){
rd(Q[i].l);rd(Q[i].r);rd(Q[i].d);rd(Q[i].sum);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);
for(i=1;i<=n;i++){//st[i]表示L>=i的第一个询问的下标
st[i]=find(i);
}
st[n+1]=m+1;
solve(1,n);
for(i=1;i<=m;i++){
if(res[i])putchar('1');
else putchar('0');
}
puts("");
return 0;
}