势能线段树

Problem1

题意:

一个长度为 n n n的数组 a a a,操作1:求区间 [ l , r ] [l,r] [l,r]的和,操作2:将区间 [ l , r ] [l,r] [l,r] a [ i ] a[i] a[i]变为 a [ i ] − l o w b i t ( a [ i ] ) a[i]-lowbit(a[i]) a[i]lowbit(a[i]),操作3:将区间 [ l , r ] [l,r] [l,r] a [ i ] a[i] a[i]变为 a [ i ] + 2 k ,其中 k 满足 2 k ≤ a [ i ] < 2 k + 1 a[i]+2^k,其中k满足2^k\leq a[i]< 2^{k+1} a[i]+2k,其中k满足2ka[i]<2k+1

思路:

我们发现两种操作不会改变数在二进制下的1的个数,且操作2会每次减少一个1。那么如果某个区间的所有数的二进制1的个数为0,那么不再修改,结束递归,否则就一直递归进行单点修改。对于操作3,则相对于二进制下的区间乘。

代码:
const int N = 1e5 + 50; 
const int M = N << 1;
const int mod = 998244353;
#define int LL
struct Node {
	int l, r;
	int high, low, lazy, cnt;
}tr[N << 2];
int n, a[N], Left[N * 2];

void pushup(int u) {
	tr[u].high = (tr[lson].high + tr[rson].high) % mod;
	tr[u].low = (tr[lson].low + tr[rson].low) % mod;
	tr[u].cnt = max(tr[lson].cnt, tr[rson].cnt);
}
void pushdown(int u) {
	if(tr[u].lazy) {
		tr[lson].high = tr[lson].high * Left[tr[u].lazy] % mod;
		tr[rson].high = tr[rson].high * Left[tr[u].lazy] % mod;
		tr[lson].lazy += tr[u].lazy;
		tr[rson].lazy += tr[u].lazy;
		tr[u].lazy = 0;
	}
}
int get(int x) {
	for(int i = 30; i >= 0; i--) {
		if(x >> i & 1) {
			return 1 << i;
		}
	}
}
void build(int u, int l, int r) {
	tr[u] = {l, r, 0, 0, 0, 0};
	if(l == r) {
		tr[u].high = get(a[l]);
		tr[u].low = a[l] - tr[u].high;
		tr[u].cnt = __builtin_popcount(a[l]);
		return;
	}
	int mid = l + r >> 1;
	build(lson, l, mid);
	build(rson, mid + 1, r);
	pushup(u);
}
void modify1(int u, int l, int r) {
	if(!tr[u].cnt) return;
	if(tr[u].l == tr[u].r) {
		if(tr[u].cnt > 1) {
			tr[u].low -= lowbit(tr[u].low);
			tr[u].cnt--;
		} else {
			tr[u].high = tr[u].low = tr[u].cnt = 0;
		}
		return;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) modify1(lson, l, r);
	if(r > mid) modify1(rson, l, r);
	pushup(u);
}
void modify2(int u, int l, int r) {
	if(l <= tr[u].l && tr[u].r <= r) {
		tr[u].high *= 2;
		tr[u].high %= mod;
		tr[u].lazy++;
		return;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) modify2(lson, l, r);
	if(r > mid) modify2(rson, l, r);
	pushup(u);
}
int query(int u, int l, int r) {
	if(!tr[u].cnt) return 0;
	if(l <= tr[u].l && tr[u].r <= r) {
		return tr[u].high + tr[u].low;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	int res = 0;
	if(l <= mid) {
		res += query(lson, l, r);
		res %= mod;
	}
	if(r > mid) {
		res += query(rson, l, r);
		res %= mod;
	}
	return res;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	Left[1] = 2;
	for(int i = 2; i < N * 2 - 10; i++) {
		Left[i] = Left[i - 1] * 2 % mod;
	}
	int T;
	cin >> T;
	while(T--) {
		cin >> n;
		for(int i = 1; i <= n; i++) cin >> a[i];
		build(1, 1, n);
		int q; cin >> q;
		while(q--) {
			int op, l, r; cin >> op >> l >> r;
			if(op == 1) {
				cout << query(1, l, r) << '\n';
			} else if(op == 2) {
				modify1(1, l, r);
			} else {
				modify2(1, l, r);
			}
		}
	}
	
	return 0;
}

Problem2

题意:

给定一个长度为 n n n的序列 a , 0 < a [ i ] ≤ 100 a,0< a[i]\leq100 a,0<a[i]100。操作0:将区间 [ l , r ] [l,r] [l,r]都乘 w , 0 < w ≤ 100 w,0<w\leq 100 w,0<w100,操作1:求区间 [ l , r ] [l,r] [l,r]的数的欧拉函数值的和。

思路:

首先有以下结论( p p p是质数):
i % p = 0 : ψ ( i ∗ p ) = ψ ( i ) ∗ p i\%p=0:\psi(i*p)=\psi(i)*p i%p=0:ψ(ip)=ψ(i)p
i % p ≠ 0 : ψ ( i ∗ p ) = ψ ( i ) ∗ ( p − 1 ) i\%p \neq 0:\psi(i*p)=\psi(i)*(p-1) i%p=0:ψ(ip)=ψ(i)(p1)
观察数据范围,100以内的质数不到30个,那么我们可以将 w w w拆分成质数进行区间乘的修改。
那么如果对于某个区间全部含有因子质数 p p p,那么直接进行区间乘,否则继续递归修改。细节较多。

代码:
#define int LL
struct Node {
	int val, lazy;
	bitset<30> f;
}tr[N << 2];
int n, a[N];
int cnt[111][35];
int pri[111], vis[111], ouler[111];
bitset<30>st[111];

void pushup(int u) {
	tr[u].val = (tr[lson].val + tr[rson].val) % mod;
	tr[u].f = tr[lson].f & tr[rson].f;
}
void pushdown(int u) {
	if(tr[u].lazy == 1) return;
	assert(tr[u].lazy != 0);
	tr[lson].val = 1LL * tr[lson].val * tr[u].lazy % mod;
	tr[rson].val = 1LL * tr[rson].val * tr[u].lazy % mod;
	tr[lson].lazy = 1LL * tr[lson].lazy * tr[u].lazy % mod;
	tr[rson].lazy = 1LL * tr[rson].lazy * tr[u].lazy % mod;
	tr[u].lazy = 1;
}
void build(int u, int l, int r) {
	if(l == r) {
		tr[u] = {ouler[a[l]], 1, st[a[l]]};
		return;
	}
	tr[u].lazy = 1;
	int mid = l + r >> 1;
	build(lson, l, mid);
	build(rson, mid + 1, r);
	pushup(u);
}
void modify(int u, int l, int r, int L, int R, int p1, int p2) {
	if(tr[u].f[p2] && L <= l && r <= R) {
		tr[u].lazy = 1LL * tr[u].lazy * ksm(pri[p2], p1) % mod;
		tr[u].val = 1LL * tr[u].val * ksm(pri[p2], p1) % mod;
		return;
	}
	if(l == r) {
		if(tr[u].f[p2])
			tr[u].val = (tr[u].val + ksm(pri[p2], p1)) % mod;
		else {
			tr[u].f[p2] = 1;
			tr[u].val = 1LL * tr[u].val * (pri[p2] - 1) % mod;
			tr[u].val = 1LL * tr[u].val * ksm(pri[p2], p1 - 1) % mod;
		}
		return;
	}
	pushdown(u); 
	int mid = l + r >> 1;
	if(L <= mid) modify(lson, l, mid, L, R, p1, p2);
	if(R > mid) modify(rson, mid + 1, r, L, R, p1, p2);
	pushup(u);
}
int query(int u, int l, int r, int L, int R) {
	if(L <= l && r <= R) return tr[u].val;
	pushdown(u);
	int mid = l + r >> 1;
	int res = 0;
	if(L <= mid) {
		res += query(lson, l, mid, L, R);
		res %= mod;
	}
	if(R > mid) {
		res += query(rson, mid + 1, r, L, R);
		res %= mod;
	}
	return res;
}
LL ans[N];
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	ouler[1] = 1;
	for(int i = 2; i <= 100; i++) {
		if(!vis[i]) {
			pri[++pri[0]] = i;
			ouler[i] = i - 1;
		}
		for(int j = 1; j <= pri[0]; j++) {
			assert(pri[j] != 0);
			if(i * pri[j] > 100) break;
			vis[i * pri[j]] = 1;
			if(i % pri[j] == 0) {
				ouler[i * pri[j]] = ouler[i] * pri[j];
				break;
			} else {
				ouler[i * pri[j]] = ouler[i] * ouler[pri[j]];
			}
		}
	}
	for(int i = 2; i <= 100; i++) {
		for(int j = 1; j <= pri[0]; j++) {
			int x = i;
			while(x % pri[j] == 0) {
				assert(pri[j] != 0);
				cnt[i][j]++;
				x /= pri[j];
			}
			st[i][j] = cnt[i][j];
		}
	}
	// D(pri[0])
	int m;
	cin >> n >> m;
	for(int i = 1; i <= n; i++) cin >> a[i];
	build(1, 1, n);
	while(m--) {
		int op, l, r; cin >> op >> l >> r;
		if(op == 1) {
			ans[++ans[0]] = query(1, 1, n, l, r);
		} else {
			int x; cin >> x;
			for(int j = 1; j <= pri[0]; j++) {
				if(st[x][j]) {
					modify(1, 1, n, l, r, cnt[x][j], j);
				}
			}
		}
	}
	for (int i = 1; i < ans[0]; i++) {
	        cout << ans[i] << '\n';
	}
	if (ans[0]) {
        cout << ans[ans[0]];
    }
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值