Knapsack and Queries

来自Petrozavodsk Winter-2018. AtCoder Contest里的D题

题意是说,有QQ次操作,分为两种,一是添加一个重量为ww,价值为vv的物品,保证插入的ww单增,二是删除当前重量最小的物品。每次操作完之后,都有一个询问,询问能否从已有物品中选出一个子集,使得重量之和在模MM之后在区间[l,r][l,r]内,并且价值和最大。

数据范围,Q100000,2M500Q\le 100000,2\le M\le 500

QQMM一开始给定,之后每次操作强制在线。

做法:首先背包比较容易看出,但是有动态的插入、删除,这里的动态实际上是队列的模型,队尾入队,队首出队。我们将所有物品分成两部分,右半部分直接组成一个背包,左半部分记录从分界线开始的所有后缀组成的背包状态。那么插入就直接背进右边,删除直接删除左边第一个物品,不影响其他后缀,当左边为空时,就将右边所有物品移到左边,即将分界线移到最右侧,然后对每个后缀求背包。查询时,只需合并一次两边的背包,可以用单调队列优化。

这样每个物品最多只会用来做两次背包,做背包和合并两个背包的时间复杂度都是O(M)O(M),所以总的时间复杂度为O(QM)O(QM)。因为要记录左侧每个后缀,所以空间复杂度最多也是O(QM)O(QM)

代码仿照标程用了一些C++11的东西,比如template+using,swap两个vector,比较实用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include <bits/stdc++.h>
using namespace std;
class Crypto {
public:
Crypto() {
sm = cnt = 0;
seed();
}

int decode(int z) {
z ^= next();
z ^= (next() << 8);
z ^= (next() << 16);
z ^= (next() << 22);
return z;
}

void query(long long z) {
const long long B = 425481007;
const long long MD = 1000000007;
cnt++;
sm = ((sm * B % MD + z) % MD + MD) % MD;
seed();
}
private:
long long sm;
int cnt;

uint8_t data[256];
int I, J;

void swap_data(int i, int j) {
uint8_t tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}

void seed() {
uint8_t key[8];
for (int i = 0; i < 4; i++) {
key[i] = (sm >> (i * 8));
}
for (int i = 0; i < 4; i++) {
key[i+4] = (cnt >> (i * 8));
}

for (int i = 0; i < 256; i++) {
data[i] = i;
}
I = J = 0;

int j = 0;
for (int i = 0; i < 256; i++) {
j = (j + data[i] + key[i%8]) % 256;
swap_data(i, j);
}
}

uint8_t next() {
I = (I+1) % 256;
J = (J + data[I]) % 256;
swap_data(I, J);
return data[(data[I] + data[J]) % 256];
}
} c;

using Pair=pair<int,int>;
using LL=long long;
template<class T> using V = vector<T>;
const LL INF=(LL)1e18;

int mod,Q;
V<Pair> rq;
V<LL> now,las;
V<V<LL>> lq;
void init()
{
now.resize(mod,0);
las.resize(mod,0);
lq.push_back(V<LL>(mod,0));
for(int i=1;i<mod;++i) lq[0][i]=now[i]=las[i]=-INF;
}
int add(int x)
{
return (x>=mod)?x-mod:x;
}
void packinit(V<LL> &a)
{
a[0]=0;
for(int i=1;i<mod;++i) a[i]=-INF;
}
void pack(V<LL> &a,const V<LL> &b,int w,int v)
{
for(int i=0;i<mod;++i) a[i]=b[i];
for(int i=0;i<mod;++i) a[add(i+w)]=max(a[add(i+w)],b[i]+v);
}
void push(int w,int v)
{
rq.push_back(Pair(w,v));
pack(las,now,w,v);
swap(now,las);
}
void realloc()
{
packinit(now);
while(!rq.empty())
{
lq.push_back(V<LL>(mod));
pack(lq.back(),lq[lq.size()-2],rq.back().first,rq.back().second);
rq.pop_back();
}
}
void pop()
{
if(lq.size()==1) realloc();
lq.pop_back();
}

LL ask(const V<LL> &a,const V<LL> &pb,int l,int r)
{
static int que[1005];
static LL b[1005];
int head=0,tail=0,len=r-l+1;
for(int i=0;i<mod;++i) b[i]=b[i+mod]=pb[i];
LL ans=-INF;
for(int i=mod-1,j=l;i>=0;--i)
{
while(head<tail && que[head]<l-i+mod) ++head;
while(j<r-i+mod)
{
++j;
while(head<tail && b[que[tail-1]]<=b[j]) --tail;
que[tail++]=j;
}
ans=max(ans,a[i]+b[que[head]]);
}
if(ans<0) ans=-1;
return ans;
}
int main()
{
scanf("%d%d",&mod,&Q);
init();
while(Q--)
{
int t,w,v,l,r;
scanf("%d%d%d%d%d",&t,&w,&v,&l,&r);
t=c.decode(t);
w=c.decode(w);
v=c.decode(v);
l=c.decode(l);
r=c.decode(r);
if(t==1) push(w%mod,v);
else pop();
LL ans=ask(lq.back(),now,l,r);
c.query(ans);
printf("%lld\n",ans);
}
return 0;
}

Knapsack and Queries
https://izard.space/2018/09/12/Knapsack-and-Queries/
作者
forever_you
发布于
2018年9月12日
许可协议