LOJ#534 花团
LOJ#534 花团
题目链接:#534. 「LibreOJ Round #6」花团 - 题目 - LibreOJ (loj.ac)
由于我们一开始就知道一个物品存在的时间段,我们考虑线段树分治。
我们对时间轴建立一颗线段树,线段树上每一个节点都是一个 vector,然后我们在插入时直接在对应节点插入这个物品即可,由于我们的查询是单点查询,那么我们对根节点便利到目标叶子节点路径上经过所有节点包含的物品做一个背包就可以得出答案了。
容易证明,一条线段在线段树上最多被分成 \(\log(len)\) 条,所以一次插入最多插入 \(\log(len)\) 个物品。
这么做的时间复杂度是 \(O(q^2\times v\times \log (q))\)。不足以通过此题,虽然这个复杂度容易卡,但是这种做法依然能拿到 \(75\) pts。
我们得考虑优化,注意到本题的一个性质,插入的物体的时间段的左端点是单调递增的。所以我们在查询的时候遍历过的节点一定不会再次有物品插入。证明如下:
假设我们当前是第 \(i\) 次查询。当前遍历到节点管辖的范围是 \([l,r]\),那么显然有 \(l\le i \le r\)。之后插入的物品的时间段是 \([l_q,r_q]\) 那么显然有 \(i<l_q\) ,当且仅当 \(l_q\le l \le r\le r_q\) 的时候,这个物品会被插入到当前遍历的节点,而我们知道 \(l\le i< l_q\),所以这个物品不会插入当前遍历到的这个节点。
此外,由于我们是单点查询,所以每一层只会有一个节点是被遍历到的。那么我们可以对于每层都开一个 dp 数组。然后进行以下操作:
我们插入依然是插入到 vector 里面。而查询的时候,我们的操作就不一样了。如果是第一次遍历到这个节点那么我们先将上一层的 dp 数组赋值给这一层的 dp 数组,然后将这个节点中的物品取出来,进行一个 01 背包。如此一来,查询完毕后,我们叶子节点那一层的 dp 数组就是路径上所有物品的总和。
这种操作较优化前的操作就是抓住每个节点被遍历后就不会再有物品插入的性质,避免了重复对同一物品进行 dp。那么最后的总时间复杂度就是 \(O(q\times v\times \log(q))\)
代码如下:
#include<bits/stdc++.h>
#define ls(k) (k<<1)
#define rs(k) (k<<1|1)
using namespace std;
const int MAXN = 2e4+5;
int q,maxv,T,dp[20][MAXN],dep[MAXN<<2];
bool vis[MAXN<<2];
struct node
{
int v,w;
};
vector <node> t[MAXN<<2];
void upd(int le,int ri,int k,int l,int r,int v,int w)
{
if(le<=l&&r<=ri)
{
t[k].push_back(node{v,w});
return ;
}
int mid=l+r>>1;
if(le<=mid) upd(le,ri,ls(k),l,mid,v,w);
if(ri>mid) upd(le,ri,rs(k),mid+1,r,v,w);
}
int query(int pos,int k,int l,int r,int vv)
{
if(!vis[k])
{
for(int i=0;i<=maxv;++i)
dp[dep[k]][i]=dp[dep[k]-1][i];
for(int i=0;i<t[k].size();++i)
{
int v=t[k][i].v,w=t[k][i].w;
for(int j=maxv;j>=v;--j)
dp[dep[k]][j]=max(dp[dep[k]][j],dp[dep[k]][j-v]+w);
}
vis[k]=1;
}
if(l==r) return dp[dep[k]][vv];
int mid=l+r>>1;
if(pos<=mid) return query(pos,ls(k),l,mid,vv);
else return query(pos,rs(k),mid+1,r,vv);
}
void build(int k,int l,int r,int d)
{
dep[k]=d;
if(l==r) return ;
int mid=l+r>>1;
build(ls(k),l,mid,d+1);
build(rs(k),mid+1,r,d+1);
}
int main()
{
// freopen("bag.in","r",stdin);
// freopen("bag.out","w",stdout);
scanf("%d %d %d",&q,&maxv,&T);
memset(dp[0],-0x3f,sizeof dp[0]);dp[0][0]=0;
build(1,1,q,1);
int ans=0;
for(int i=1;i<=q;++i)
{
int d=T*ans,v,w,e,opt;
scanf("%d %d",&opt,&v);
v-=d;
if(opt==1)
{
scanf("%d %d",&w,&e);
w-=d;e-=d;
upd(i,e,1,1,q,v,w);
}
else
{
ans=query(i,1,1,q,v);
if(ans<0) printf("0 0\n"),ans=0;
else printf("1 %d\n",ans),ans=1^ans;
}
}
return 0;
}
总结:线段树分治好题。(有人模拟赛此题 75 -> 35 我不说是谁)