砝码称重 题解

砝码称重 题解

前言

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

白想那么多优化(

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

题意简述

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

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

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

思路

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

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

\[f_{s, i} = f_{s, i} \lor f_{s, i-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;
}

posted @ 2023-09-22 20:38  霜木_Atomic  阅读(23)  评论(2编辑  收藏  举报