二进制与、平方和
时间限制: 3 S e c 3 Sec 3Sec 内存限制: 512 M B 512 MB 512MB
题目描述
请你维护一个长度为 n 的非负整数序列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,…,an,支持以下两种操作:
- 第一种操作会将序列 a l , a l + 1 , … , a r a_l,a_{l+1},…,a_r al,al+1,…,ar 中的每个元素,修改为各自和 x x x 的"二进制与"(Bitwise binary AND)的值,其中 l , r , x l,r,x l,r,x 在每次操作时会给定;
- 第二种操作会询问序列 a l , a l + 1 , … , a r a_l,a_{l+1},…,a_r al,al+1,…,ar 中所有元素的平方和模 998244353 998244353 998244353的值,即 ∑ i = l r a i 2 \sum_{i=l}^ra_i^2 ∑i=lrai2 模 998244353 998244353 998244353,其中 l , r l,r l,r在每次操作时会给定。
总共有 q q q 次操作,请你在维护序列的过程中,输出第二种操作所询问的答案。注意需要取模。
输入
第一行,一个整数
n
(
1
≤
n
≤
3
×
1
0
5
)
n (1≤n≤3×10^5)
n(1≤n≤3×105),表示序列的长度。
第二行,共
n
n
n 个整数
a
i
(
0
≤
a
i
<
2
24
)
a_i (0≤a_i<2^{24})
ai(0≤ai<224),表示序列。
第三行,一个整数
q
(
1
≤
q
≤
3
×
1
0
5
)
q (1≤q≤3×10^5)
q(1≤q≤3×105),表示询问的数量。
接下来
q
q
q 行,每行表示一个操作,输入有两种格式:
- 第一种操作的格式为 “1 l r x”,表示将序列中编号在区间 [ l , r ] [l,r] [l,r] 的所有元素,修改为和 x x x 二进制与操作后的值,其中 1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1≤l≤r≤n, 0 ≤ x < 2 24 0≤x<2^{24} 0≤x<224;
- 第二种操作的格式为 “2 l r”,表示询问序列中编号在区间 [ l , r ] [l,r] [l,r] 的所有元素的平方和,模 998244353 998244353 998244353 的值,其中 1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1≤l≤r≤n。
数据保证至少有一个第二种操作,即保证至少询问一次答案。
输出
每当遇到第二种操作时,输出询问的答案。注意需要取模。
样例输入
3
13 31 28
4
2 1 3
1 3 3 25
1 1 2 18
2 2 3
样例输出
1914
900
样例输入二
5
9 11 12 5 1
7
2 1 3
1 3 3 0
1 1 3 9
1 4 5 13
2 1 3
1 4 5 14
2 1 5
样例输出二
346
162
178
样例输入三
4
16777215 16777215 16777215 16777214
4
2 2 2
1 1 4 16777214
2 1 4
2 3 4
样例输出三
981185168
795789897
897017125
“二进制与”(Bitwise binary AND)结果的第$ i$ 个二进制位为 1,当且仅当两个操作数的第$ i$ 位都为 1。
思路:
线段树维护区间内的数每个位上的0和1的总个数和区间平方和。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn = 3e5+5, mod = 998244353;
ll q[maxn];
int n,m;
struct Node {
int l,r;
ll sum; //区间平方和 % mod
int x[25]; //维护当前区间各个位上0和1的个数
}tr[maxn*4];
void pushup(int u)
{
tr[u].sum = (tr[u<<1].sum + tr[u<<1|1].sum) % mod;
for(int i=0;i<25;++i)
{
tr[u].x[i] = tr[u<<1].x[i] + tr[u<<1|1].x[i];
}
}
void build(int u,int l,int r)
{
if(l == r) {
tr[u] = {l, r,q[r]*q[r]%mod};
for(int i=0;i<25;++i)
{
tr[u].x[i] = q[r] >> i & 1;
}
return;
}
tr[u] = {l, r};
int mid = l + r >> 1;
build(u<<1, l, mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
void modify(int u,int l,int r,ll x)
{
//修改根节点
if(tr[u].l == tr[u].r)
{
ll sum = 0;
for(int i=0;i<25;++i)
{
if(tr[u].x[i] && (x>>i&1)) //都为1
sum |= 1 << i; //加上这一位
//sum += 1 << i; //加上这一位
else tr[u].x[i] = 0;
}
tr[u].sum = sum * sum % mod;
}
if(l <= tr[u].l && tr[u].r <= r)
{
//每一位有四种情况 :
// 本来是1 ,x为0 ,需要修改 √
// 本来是1 ,x为1 ,不需要修改
// 本来是0 ,x为1 ,不需要修改
// 本来是0, x为0 ,不需要修改
bool f = true; //是否不需要修改
for(int i=0;i<25;++i)
//如果当前位置存在1 ,并且x的这一位是0
if(tr[u].x[i] && (x>>i&1)==0)
{
f = false; //需要修改子树
break;
}
if(f) return; //如果不需要修改
}
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u<<1, l,r,x);
if(r > mid) modify(u<<1|1,l,r,x);
pushup(u);
}
ll query(int u,int l,int r)
{
if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
int mid = tr[u].l + tr[u].r >> 1;
int res = 0;
if(l <= mid) res = query(u<<1, l,r) % mod;
if(r > mid) res = (res + query(u<<1|1,l,r)) % mod;
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%lld",&q[i]);
build(1,1,n);
scanf("%d",&m);
int op,l,r,x;
while(m--)
{
scanf("%d%d%d",&op,&l,&r);
if(op == 1)
{
scanf("%d",&x);
modify(1,l,r,x);
}
else
{
printf("%lld\n",query(1,l,r));
}
}
return 0;
}