砝码称重 题解
砝码称重 题解
前言
这道题时限完全可以开到 1s,空间也开不到 1024kb
白想那么多优化(
不过这个复杂度可能是目前来看最合理(算出来保证能过)的。
题意简述
有一个长度为 \(n\) 的序列 \(a\),有两种操作:
- 把 \(l\) 到 \(r\) 的所有数改为 \(x\);
- 查询用 \(l\) 到 \(r\) 的所有数(每个数可以用无数次)能否通过相加得到 \(x\)。
保证 \(a_i\) 和所有操作 \(1\) 的 \(x\) 总共最多不超过 \(10\) 种数。
思路
首先看到这个不超过 \(10\) 种数,肯定会想到状压。因为每个数可以用无数次,所以我们只需知道 \([l, r]\) 内数的集合是什么。这个可以利用线段树来做。
然后就是一个背包了。我们设 \(f_{s, i}\) 表示用集合 \(s\) 中的数,\(i\) 能否被表示。设我们现在用 \(s\) 中的 \(x\) 来转移,有
当然,这里对于每一个 \(i\),必须用 \(x\) 的每个倍数转移一次,因为 \(x\) 可以用无数次。
直接枚举倍数感觉非常过不去这里给出第一个优化。我们发现这里的转移很多都是重复的,比如用 \(3x\) 转移的部分,在 \(2x\) 和 \(x\) 转移之后就都已经被转移了。所以可以用类似二进制拆分的思想,每次用 \(2^i\) 倍的 \(x\) 去转移,这样就可以保证不重不漏,并且每个 \(x\) 转移的复杂度优化到了 \(O(m \log m)\)。
然后还是感觉很寄我们发现每次的转移相当于是把数组整体右移 \(2^ix\),所以可以用 bitset 优化。这样,不仅时间复杂度会除以 \(64(32)\),空间复杂度也会小很多(虽然开 bool 数组也就多了 \(8\) 倍空间)。
然后我们其实就可以枚举集合,然后用集合内的数来求 \(f\) 数组了。不过,如果每次都把集合内的数都枚举一次,算出来的复杂度看上去还是过不了($O(\frac{1}{64(32)} 2^{k} k m \log m) $,虽然肯定跑不满,而且事实上可以过)。
然后我们发现,很多转移还是冗余的,因为在我们求新的集合 \(s\) 的 \(f\) 时,比它小的集合的 \(f\) 已经求出来,而且其中一定有 \(s\) 的子集。因此,我们每次只需要用 \(s\) 的二进制数最高位代表的数来转移就行了。这样,\(k\) 的枚举就没了,最终复杂度 \(O(\frac{1}{64(32)} 2^{k} m \log m)\),算出来是 \(5 \times 10^7\) 左右,完全可以过。
线段树自带的复杂度是 \(O(n \log n)\),所以总复杂度就是 \(O(\frac{1}{64(32)} 2^{k} m \log m + n \log n)\),仍然超不过 \(10^8\)。
后记
其实 dp 部分我想麻烦了,并不需要枚举每个倍数,只需要向前转移就行,这样在枚举到 \(i\) 之前就可以转移完 \(i\) 了,不过这样也就不能整体右移了所以我的复杂度还是少一半。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10, M = 1030, MM = 1e5+10;
int read() {
int x = 0; char ch = getchar();
while(ch<'0' || ch>'9') ch = getchar();
while(ch>='0'&&ch<='9') x = x*10+(ch-48), ch = getchar();
return x;
}
bitset<100010> f[1030];
struct Question{
int op, l, r, x;
}q[N];
int n, m, Q;
int vis[MM];//每个数的编号
int a[N];
int nums[12], totn;
struct node{
int st;//记录区间内的集合
int tag;//标记被改成了哪个集合
};
struct segmentTree{
node tree[N<<2];
#define ls tr<<1
#define rs tr<<1 | 1
void push_up(int tr) {
tree[tr].st = (tree[ls].st | tree[rs].st);
}
void push_down(int tr) {
if(tree[tr].tag) {
tree[ls].st = tree[rs].st = tree[ls].tag = tree[rs].tag = tree[tr].tag;
tree[tr].tag = 0;
}
}
void build(int tr, int L, int R) {
if(L == R) {
tree[tr].st = (1<<(vis[a[L]] - 1));
return;
}
int mid = (L+R)>>1;
build(ls, L, mid);
build(rs, mid+1, R);
push_up(tr);
}
void modify(int tr, int L, int R, int lq, int rq, int val) {
if(lq <= L && R <= rq) {
tree[tr].st = tree[tr].tag = val;
return;
}
push_down(tr);
int mid = (L+R)>>1;
if(lq <= mid) modify(ls, L, mid, lq, rq, val);
if(mid < rq) modify(rs, mid+1, R, lq, rq, val);
push_up(tr);
}
int query(int tr, int L, int R, int lq, int rq) {
if(lq <= L && R <= rq) {
return tree[tr].st;
}
push_down(tr);
int mid = (L+R)>>1, ret = 0;
if(lq <= mid) ret|=query(ls, L, mid, lq, rq);
if(mid < rq) ret|=query(rs, mid+1, R, lq, rq);
return ret;
}
}seg;
int main() {
// cout << sizeof(f) << endl;
n = read(), m = read(), Q = read();
for(int i = 1; i<=n; ++i) {
a[i] = read();
if(!vis[a[i]]) {
vis[a[i]] = ++totn;
nums[totn] = a[i];
}
}
for(int i = 1; i<=Q; ++i) {
q[i].op = read(), q[i].l = read(), q[i].r = read(), q[i].x = read();
if(q[i].op == 1) {
if(!vis[q[i].x]) {
vis[q[i].x] = ++totn;
nums[totn] = q[i].x;
}
}
}
seg.build(1, 1, n);
f[0][0] = 1;
for(int s = 1; s<=(1<<totn)-1; ++s) {
// f[s][0] = 1;
int pos = 0;
for(int i = totn; i>=1; --i) {
if((s>>(i-1)) & 1) {
//// int x = nums[i];
// for(int j = nums[i]; j<=m; j*=2) {
// f[s] |= (f[s]<<j);
// }//旧的转移
pos = i;//找到最高位在哪里
break;
}
}
int ts = (s ^ (1<<(pos-1)));
f[s] = f[ts];
for(int j = nums[pos]; j<=m; j*=2) {
f[s] |= (f[s] << j);
}
}
for(int i = 1; i<=Q; ++i) {
if(q[i].op == 1) {
seg.modify(1, 1, n, q[i].l, q[i].r, 1<<(vis[q[i].x] - 1));
} else {
int st = seg.query(1, 1, n, q[i].l, q[i].r);
if(f[st][q[i].x]) {
puts("Yes");
} else {
puts("No");
}
}
}
return 0;
}