砝码称重 题解

砝码称重 题解

前言

这道题时限完全可以开到 1s,空间也开不到 1024kb

白想那么多优化(

不过这个复杂度可能是目前来看最合理(算出来保证能过)的。

题意简述

有一个长度为 n 的序列 a,有两种操作:

  1. lr 的所有数改为 x
  2. 查询用 lr 的所有数(每个数可以用无数次)能否通过相加得到 x

保证 ai 和所有操作 1x 总共最多不超过 10 种数。

思路

首先看到这个不超过 10 种数,肯定会想到状压。因为每个数可以用无数次,所以我们只需知道 [l,r] 内数的集合是什么。这个可以利用线段树来做。

然后就是一个背包了。我们设 fs,i 表示用集合 s 中的数,i 能否被表示。设我们现在用 s 中的 x 来转移,有

fs,i=fs,ifs,ix

当然,这里对于每一个 i,必须用 x 的每个倍数转移一次,因为 x 可以用无数次。

直接枚举倍数感觉非常过不去这里给出第一个优化。我们发现这里的转移很多都是重复的,比如用 3x 转移的部分,在 2xx 转移之后就都已经被转移了。所以可以用类似二进制拆分的思想,每次用 2i 倍的 x 去转移,这样就可以保证不重不漏,并且每个 x 转移的复杂度优化到了 O(mlogm)

然后还是感觉很寄我们发现每次的转移相当于是把数组整体右移 2ix,所以可以用 bitset 优化。这样,不仅时间复杂度会除以 64(32),空间复杂度也会小很多(虽然开 bool 数组也就多了 8 倍空间)。

然后我们其实就可以枚举集合,然后用集合内的数来求 f 数组了。不过,如果每次都把集合内的数都枚举一次,算出来的复杂度看上去还是过不了(O(164(32)2kkmlogm),虽然肯定跑不满,而且事实上可以过)。

然后我们发现,很多转移还是冗余的,因为在我们求新的集合 sf 时,比它小的集合的 f 已经求出来,而且其中一定有 s 的子集。因此,我们每次只需要用 s 的二进制数最高位代表的数来转移就行了。这样,k 的枚举就没了,最终复杂度 O(164(32)2kmlogm),算出来是 5×107 左右,完全可以过。

线段树自带的复杂度是 O(nlogn),所以总复杂度就是 O(164(32)2kmlogm+nlogn),仍然超不过 108

后记

其实 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;
}
posted @   霜木_Atomic  阅读(41)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示