正题
luogu 4428
题目大意
给你一个01串,让你进行一下两种操作:
1.将其中一位取反
2.问你某一段中有多少个子串满足有一种排列方案,使得组成的二进制数是3的倍数
解题思路
不难发现,因为2%3=2,所以2的幂%3的结果按121212…的规律循环
如果一个子串中1的个数为偶数个,可以让它们在相邻位,这样就可以被三整除
如果一个子串中1的个数为奇数个,那么要先拿出三个1,分开放,组成10101,这样才能让其被3整除
\\
那么就有了以下两种情况:
1.1的个数为偶数
2.1的个数为奇数,且num1>1,num0⩾2num_1>1,num_0\geqslant 2num1>1,num0⩾2
如果计算这两种情况,会十分困难
那么考虑用合法方案数=总方案数-不合法方案数
不合法的用以下情况
1.1的个数为奇数,且0的个数小于2
2.只有1个1
\\
以上两种情况可以用线段树计算
每个位置维护以下信息:
1.ld/rd0/1,0/1ld/rd_{0/1,0/1}ld/rd0/1,0/1强行经过左/右端点且0的出现次数为0/1,1的出现次数为偶数/奇数
2.lo/ro0/1/2lo/ro_{0/1/2}lo/ro0/1/2,强行经过左/右端点且恰好出现1次,0的出现次数为0/1/大于2
3.s0,s1,l0,r0,0的个数,1的个数,以左端点为起点0的个数,以右端点为终点0的个数
然后计算不合法的,把01和10这两种不合法的情况减掉
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ls x*2
#define rs x*2+1
#define N 100100
#define ll long long
using namespace std;
ll n, m, x, y, a[N];
struct node
{ll ld[2][2], rd[2][2], lo[3], ro[3], s0, l0, r0, s1, s;void clean(){memset(ld, 0, sizeof(ld));memset(rd, 0, sizeof(rd));memset(lo, 0, sizeof(lo));memset(ro, 0, sizeof(ro));s1 = s0 = l0 = r0 = s = 0;}void add(ll x){clean();if (x) ld[0][1] = rd[0][1] = lo[0] = ro[0] = s1 = s = 1;else ld[1][0] = rd[1][0] = s0 = l0 = r0 = 1;return;}
}T[N<<2];
node merge(node a, node b)
{node c;c.clean();for (ll i = 0; i <= 1; ++i)for (ll j = 0; j <= 1; ++j){c.ld[i][j] += a.ld[i][j];//计算ld/rdc.rd[i][j] += b.rd[i][j];if (i >= a.s0) c.ld[i][j] += b.ld[i - a.s0][j^(a.s1&1)];//左边的0不够,右边的也要算进去if (i >= b.s0) c.rd[i][j] += a.rd[i - b.s0][j^(b.s1&1)];}for (ll i = 0; i <= 2; ++i){c.lo[i] += a.lo[i];//计算lo/roc.ro[i] += b.ro[i];if (!a.s1) c.lo[min(2ll, i + a.s0)] += b.lo[i];//左边没有1,右边的也要算进去if (!b.s1) c.ro[min(2ll, i + b.s0)] += a.ro[i];}if (a.s1 == 1 && b.l0)//左边只有1个1,右边的0也要算进去{if (!a.s0) c.lo[1]++, c.lo[2] += b.l0 - 1;//10的情况只有1个0,其他都有2个以上的0else c.lo[2] += b.l0;}if (b.s1 == 1 && a.r0){if (!b.s0) c.ro[1]++, c.ro[2] += a.r0 - 1;else c.ro[2] += a.r0;}c.s0 = a.s0 + b.s0;c.s1 = a.s1 + b.s1;c.l0 = a.s1 ? a.l0 : a.l0 + b.l0;//左边全是0,右边的也要算进去c.r0 = b.s1 ? b.r0 : b.r0 + a.r0;c.s = a.s + b.s;//子区间的c.s += a.rd[0][0] * (b.ld[0][1] + b.ld[1][1]);//奇数个1,且0的个数小于2c.s += a.rd[0][1] * (b.ld[0][0] + b.ld[1][0]);c.s += a.rd[1][0] * b.ld[0][1];c.s += a.rd[1][1] * b.ld[0][0];if (a.r0) c.s += a.r0 * (b.lo[0] + b.lo[1] + b.lo[2]) - b.lo[0];//只有1个1的,要减去0+1的情况,因为0的个数小于2,前面有计算过if (b.l0) c.s += b.l0 * (a.ro[0] + a.ro[1] + a.ro[2]) - a.ro[0];return c;
}
void build(ll x, ll l, ll r)//线段树
{if (l == r){T[x].add(a[l]);return;}ll mid = l + r >> 1;build(ls, l, mid);build(rs, mid + 1, r);T[x] = merge(T[ls], T[rs]);return;
}
void change(ll x, ll l, ll r, ll y, ll z)
{if (l == r){T[x].add(z);return;}ll mid = l + r >> 1;if (y <= mid) change(ls, l, mid, y, z);else change(rs, mid + 1, r, y, z);T[x] = merge(T[ls], T[rs]);return;
}
node ask(ll x, ll L, ll R, ll l, ll r)
{if (L == l && R == r) return T[x];ll mid = L + R >> 1;if (r <= mid) return ask(ls, L, mid, l, r);else if (l > mid) return ask(rs, mid + 1, R, l, r);else return merge(ask(ls, L, mid, l, mid), ask(rs, mid + 1, R, mid + 1, r));
}
int main()
{scanf("%lld", &n);for (ll i = 1; i <= n; ++i)scanf("%lld", &a[i]);build(1, 1, n);scanf("%lld", &m);while(m--){scanf("%lld", &x);if (x == 1){scanf("%lld", &x);a[x] ^= 1;change(1, 1, n, x, a[x]);}else{scanf("%lld%lld", &x, &y);printf("%lld\n", (y - x + 1) * (y - x + 2) / 2 - ask(1, 1, n, x, y).s);//总方案数减去不合法方案数}}return 0;
}