题解 : 盲盒流水线
给定 \(n\) 个物品,每个物品有价值和质量,\(q\) 次询问,在 \([l , r]\) 中选质量不超过 \(m\) 的物品,每个物品至多选一次,并且要让价值最大
1 \\leq n \\leq 20000
1 \\leq m_i \\leq 500
1 \\leq q \\leq 100000
第一眼,这不是背包吗?
暴力:\(O(nmq)\) \(49pts\)
cpp
const int N = 2e4 + 20, mod = 998244353;
int n, v[N], w[N], q, g[N], f[N], p[N];
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i)cin >> v[i] >> w[i];
cin >> q;
for(int qwq = 1, l, r, m, cnt = 0; qwq <= q; ++qwq, cnt = 0){
cin >> l >> r >> m;
g[0] = 1;
for(int i = l; i <= r; ++i){
for(int j = m; j >= w[i]; --j){
if(f[j] < f[j - w[i]] + v[i])f[j] = f[j - w[i]] + v[i], g[j] = g[j - w[i]] % mod ;
else if(f[j] == f[j - w[i]] + v[i])f[j] = f[j - w[i]] + v[i], (g[j] += g[j - w[i]]) %= mod;
}
}
for(int i = 1; i <= m; ++i)(cnt += (f[i] == f[m]) * g[i] % mod) %= mod;
cout << f[m] << ' ' << cnt << '\n';
for(int i = 0; i <= m; ++i)f[i] = g[i] = 0;
}
return 0;
}
然后可以上一些数据结构? 本机房大蛇bjt赛时似乎用一些数据结构优化到了 \(66pts\), 但是我没看懂
我们发现,这个区间会被反复查询导致有重复,由于背包只能加不能删,所以赛时我用回滚莫队优化 \(dp\),单次移动指针是 \(O(m)\) 的,最终复杂度 \(O(n m \sqrt q)\), 可以获得 \(83pts\)
机房大佬 洛阳民宿的 tj
赛时代码:
cpp
const int M = 510, Q = 1e5 + 20, N = 2e4 + 20, mod = 998244353;
int n, qwq, v[N], w[N], B, cnt, L[N], R[N], bel[N], z, g[M], f[M], f_[M], g_[M], Mx[Q];
struct Que{
int l, r, m, id;
}q[Q];
pair<int,int> ans[Q];
vector<Que>G[Q];
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i)cin >> v[i] >> w[i];
cin >> qwq;
B = n / (sqrt(qwq) + 1) + 1, cnt = n / B;
for(int i = 1; i <= cnt; ++i)L[i] = (i - 1) * B + 1, R[i] = B * i;
if(R[cnt] < n)++cnt, L[cnt] = R[cnt - 1] + 1, R[cnt] = n;
for(int i = 1; i <= cnt; ++i)for(int j = L[i]; j <= R[i]; ++j)bel[j] = i;
for(int i = 1, l_, r_, m_; i <= qwq; ++i){
cin >> l_ >> r_ >> m_;
if(bel[l_] == bel[r_]){
g[0] = 1;int as = 0, zz = 0;
for(int i = l_; i <= r_; ++i){
for(int j = m_; j >= w[i]; --j){
if(f[j] < f[j - w[i]] + v[i])f[j] = f[j - w[i]] + v[i], g[j] = g[j - w[i]] % mod ;
else if(f[j] == f[j - w[i]] + v[i])f[j] = f[j - w[i]] + v[i], (g[j] += g[j - w[i]]) %= mod;
zz = f[zz] < f[j] ? j : zz;
}
}
for(int i = 1; i <= m_; ++i)(as += (f[i] == f[zz]) * g[i] % mod) %= mod;
ans[i] = {f[zz], as};
for(int i = 0; i <= m_; ++i)f[i] = g[i] = 0;
}
else G[bel[l_]].push_back({l_, r_, m_, i}), Mx[bel[l_]] = max(m_, Mx[bel[l_]]);
}
for(int i = 1; i <= cnt; ++i)
if(G[i].size())sort(G[i].begin(), G[i].end(), [](Que a, Que b){return a.r < b.r;});
for(int b = 1; b <= cnt; ++b){
if(!G[b].size())continue;
memset(g, 0, sizeof(g));memset(f, 0, sizeof(f));
g[0] = 1;
int l_ = R[b], r_ = R[b] + 1, zz = 0;
int mm = Mx[b];
for(auto [l, r, m, id] : G[b]){
int as = 0;
for(int i = r_; i <= r; i++){
for(int j = mm; j >= w[i]; --j){
if(f[j] < f[j - w[i]] + v[i])f[j] = f[j - w[i]] + v[i], g[j] = g[j - w[i]] % mod ;
else if(f[j] == f[j - w[i]] + v[i])f[j] = f[j - w[i]] + v[i], (g[j] += g[j - w[i]]) %= mod;
}
}
r_ = r + 1;
for(int i = 0; i <= mm; ++i)f_[i] = f[i], g_[i] = g[i];
for(int i = l_; i >= l; --i){
for(int j = mm; j >= w[i]; --j){
if(f_[j] < f_[j - w[i]] + v[i])f_[j] = f_[j - w[i]] + v[i], g_[j] = g_[j - w[i]] % mod ;
else if(f_[j] == f_[j - w[i]] + v[i])f_[j] = f_[j - w[i]] + v[i], (g_[j] += g_[j - w[i]]) %= mod;
}
}
for(int i = 1; i <= m; ++i)(as += (f_[i] == f_[m]) * g_[i] % mod) %= mod;
ans[id] = {f_[m] , as};
for(int i = 0; i <= mm; ++i)f_[i] = g_[i] = 0;
}
}
for(int i = 1; i <= qwq; ++i)cout << ans[i].first << ' ' << ans[i].second << '\n';
return 0;
}
由于没有修改操作,我们考虑猫树分治
其实在很早之前一场abc,我了解过猫树分治的说,但是我把他放到任务清单里后就没有再理他。。。
ABC426G,P6240 都是原题
算法大概是这样的:
我们选定当前区间 \([l, r]\) 的 \(mid\) ,从 \(mid\) 向前向后预处理处理到边界,将左右边界在这个区间内的询问进行处理答案
合并两个背包是 \(O(n^2)\) 的,但是我们单纯统计答案,只需要 res = max(res, L[b[i].l][j] + R[b[i].r][b[i].m - j]) 就可以 \(O(m)\) 的时间内统计答案
时间复杂度应该是 \(T(n) = 2T(n / 2) + O(n) = O(n \log n)\)
总时间复杂度是 \(O(nm \log n + qm)\)
关于初始化,我一开始发现将 只将 GL[mid + 1][0] 和 GR[mid][0] 设成\(1\),发现答案会小,把 GL[mid + 1] GR[mid] 都设成 \(1\), 发现答案会变大
然后,我将 GL[mid + 1][0] 和 GR[mid] 设成 \(1\) 就过了,我自己的解释就是说,我们对于左边的只取恰好的,右边的取小于等于的,这样每一种方案只会被统计一次
cpp
const int mod = 998244353, N = 2e4 + 20, Q = 1e5 + 20, M = 502;
int n, v[N], w[N], qwq, L[N][M + 20], R[N][M + 20], GL[N][M + 20], GR[N][M + 20];
pair<int, int>ans[Q];
struct Que{
int l, r, m, id;
}q[Q], b[Q];
void solve(int l, int r, int ql, int qr){
int mid = (l + r) >> 1;
if(ql > qr)return;
for(int i = 0; i <= M + 5; ++i)L[mid + 1][i] = 0, GL[mid + 1][i] = !i;
for(int i = mid; i >= l; --i){
for(int j = 0; j <= M; j++){
GL[i][j] = GL[i + 1][j], L[i][j] = L[i + 1][j];
if(j >= w[i]){
if(L[i + 1][j - w[i]] + v[i] > L[i][j])
L[i][j] = L[i + 1][j - w[i]] + v[i], GL[i][j] = GL[i + 1][j - w[i]];
else if(L[i + 1][j - w[i]] + v[i] == L[i][j])(GL[i][j] += GL[i + 1][j - w[i]]) %= mod;
}
}
}
for(int i = 0; i <= M + 5; ++i)R[mid][i] = 0, GR[mid][i] = 1;
for(int i = mid + 1; i <= r; ++i){
for(int j = 0; j <= M; ++j){
GR[i][j] = GR[i - 1][j], R[i][j] = R[i - 1][j];
if(j >= w[i]){
if(R[i - 1][j - w[i]] + v[i] > R[i][j])
R[i][j] = R[i - 1][j - w[i]] + v[i], GR[i][j] = GR[i - 1][j - w[i]];
else if(R[i - 1][j - w[i]] + v[i] == R[i][j])(GR[i][j] += GR[i - 1][j - w[i]]) %= mod;
}
}
}
int nql = ql, nqr = qr;
for(int i = ql; i <= qr; ++i)b[i] = q[i];
for(int i = ql; i <= qr; ++i){
if(b[i].r < mid)q[nql++] = b[i];
else if(b[i].l > mid)q[nqr--] = b[i];
else{
int res = 0, as = 0;
for(int j = 0; j <= b[i].m; ++j)
res = max(res, L[b[i].l][j] + R[b[i].r][b[i].m - j]);
for(int j = 0; j <= b[i].m; ++j){
if(res == L[b[i].l][j] + R[b[i].r][b[i].m - j]){
(as += 1ll * GL[b[i].l][j] * GR[b[i].r][b[i].m - j] % mod) %= mod;
}
}
ans[b[i].id] = {res, as};
}
}
solve(l, mid, ql, nql - 1), solve(mid + 1, r, nqr + 1, qr);
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i)cin >> v[i] >> w[i];
cin >> qwq;
for(int i = 1; i <= qwq; ++i)cin >> q[i].l >> q[i].r >> q[i].m, q[i].id = i;
solve(1, n, 1, qwq);
for(int i = 1; i <= qwq; ++i){
if(ans[i].first)cout << ans[i].first << ' ' << ans[i].second << '\n';
else cout << 0 << ' ' << 0 << '\n';
}
return 0;
}