【补题笔记】AtCoder Beginner Contest 256 A~Ex

A - 2^N

题目链接

直接输出 1 1 1左移 N N N位即可。

#include
using namespace std;
typedef long long LL;

int n;

void main2() {
	cin >> n;
	cout << (1 << n);
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

B - Batters

题目链接

直接按照题目给定的流程模拟即可。移动纸片的时候需要从右往左移,防止前面的先移动,把后面的纸片覆盖掉了。

#include
using namespace std;
typedef long long LL;

int n, p;
int a[150], b[150]; 

void main2() {
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		b[i] = 0;
	}
	b[0] = 0;
	p = 0;
	for (int i = 1; i <= n; ++i) {
		b[0] = 1;
		for (int j = 3; j >= 0; --j) {
			if (b[j]) {
				int nxt = j + a[i];
				if (nxt < 4) b[nxt] = 1;
				else ++p;
				b[j] = 0;
			}
		}
	}
	cout << p;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

C - Filling 3x3 array

题目链接

数据范围很小,所以可以大胆暴力。暴力枚举左上角四个数即可,然后check剩下的 5 5 5个数是否可以符合要求,可以符合则计数器+1。

#include
using namespace std;
typedef long long LL;

int h1, h2, h3, w1, w2, w3;
int a[4][4];
int ans = 0;

void check(int i, int j, int k, int x) {
	a[1][1] = i; a[1][2] = j; a[2][1] = k; a[2][2] = x;
	a[1][3] = h1 - a[1][1] - a[1][2];
	if (a[1][3] <= 0) return;
	a[2][3] = h2 - a[2][1] - a[2][2];
	if (a[2][3] <= 0) return;
	a[3][1] = w1 - a[1][1] - a[2][1];
	if (a[3][1] <= 0) return;
	a[3][2] = w2 - a[2][2] - a[1][2];
	if (a[3][2] <= 0) return;
	a[3][3] = h3 - a[3][1] - a[3][2];
	if (a[3][3] <= 0) return;
	if (a[1][3] + a[2][3] + a[3][3] != w3) return;
	++ans;
}

void main2() {
	cin >> h1 >> h2 >> h3 >> w1 >> w2 >> w3;
	for (int i = 1; i <= h1; ++i) {
		for (int j = 1; j <= h1; ++j) {
			for (int k = 1; k <= h2; ++k) {
				for (int x = 1; x <= h2; ++x) {
					check(i, j, k, x);
				}
			}
		}
	}
	cout << ans;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

D - Union of Interval

题目链接

将所有区间按照左端点从小到大→右端点从小到大的顺序排序。从头到尾扫一遍区间,记录一个当前适用的左端点的值 x x x,每遇到一个区间,如果他的左端点在以 x x x为左端点的大区间内,则根据当前区间的右端点更新这个大区间的右端点;如果这个区间的左端点不在大区间内,则以这个区间的左端点为新的 x x x,开辟一个新的大区间。

#include
using namespace std;
typedef long long LL;

int n;
struct QUERY {
	int x, y;
}q[200005];

int mp[200005];

void main2() {
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> q[i].x >> q[i].y;
	}
	sort(q + 1, q + n + 1, [](const QUERY &A, const QUERY &B) {
		return (A.x == B.x) ? (A.y < B.y) : (A.x < B.x);
	});
	for (int i = 1; i <= 200000; ++i) {
		mp[i] = 0;
	}
	int cur = 0; mp[0] = 0;
	for (int i = 1; i <= n; ++i) {
		if (q[i].x <= mp[cur]) {
			mp[cur] = max(mp[cur], q[i].y);
		}
		else {
			cur = q[i].x;
			mp[cur] = q[i].y;
		}
	}
	for (int i = 1; i <= 200000; ++i) {
		if (mp[i] > 0) {
			cout << i << ' ' << mp[i] << '\n';
		}
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

E - Takahashi’s Anguish

题目链接

将每一个人视作图上的一个点,第 i i i个人就是第 i i i号结点,对于每一个 i i i,从第 i i i号结点向第 X i X_i Xi号结点连一条边权为 C i C_i Ci的有向边。最后连成的图如果有环,说明环上的所有人不可能有一种方案不产生不愉快的值。如果这个人不在环上,则可以通过调换位置不产生不愉快的值。

我们发现这个环上的所有点都只会出现在一个环中,不会有一个点同时出现在两个环中。因为每一个点只有一个出度,如果这个点同时出现在两个环,作为两个环的交集的点中至少有一个点要有至少为 2 2 2的出度。

身在环上的人,我们可以通过调换顺序的方式,让只有一个人产生不愉快的值。也就是说这个题的答案就是所有的环的最小边权的和。

#include
using namespace std;
typedef long long LL;

LL n, ans = 0;
LL a[200050], c[200005], mp[200005], inc[200005];

LL search(int x) {
	LL mx = 1e9, y = x, getsame = 0;
	while (1) {
		inc[y] = 1;
		mx = min(mx, c[y]);
		y = a[y];
		if (y == x) getsame = 1;
		if (getsame or inc[y]) break;
	}
	if (!getsame) return 0;
	else return mx;
}

void main2() {
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	for (int i = 1; i <= n; ++i) {
		cin >> c[i]; mp[i] = inc[i] = 0;
	}
	for (int i = 1; i <= n; ++i) {
		if (mp[i] or inc[i]) continue;
		int x = i, s = 0;
		while (1) {
			mp[x] = 1;
			x = a[x];
			if (mp[x] == 1) {
				s = x; 
				if (!inc[x]) ans += search(s);
				inc[s] = 1;
				break;
			}
			
		}
	}
	cout << ans;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

F - Cumulative Cumulative Cumulative Sum

题目链接

可以轻松推出: D x = ∑ i = 1 x ( x − i + 1 ) ( x − i + 2 ) 2 A i D_x=\displaystyle\sum\limits_{i=1}^x \frac{(x-i+1)(x-i+2)}{2}A_i Dx=i=1x2(xi+1)(xi+2)Ai

对这个式子进行一个简单的拆项,然后我们发现里面有 A i A_i Ai项、 i A i iA_i iAi项和 i 2 A i i^2A_i i2Ai项。根据这几项对式子进行一个简单的处理,得到:
D x = 1 2 ( ∑ i = 1 x i 2 A i − ( 2 x + 3 ) ∑ i = 1 x i A i + ( x + 1 ) ( x + 2 ) 2 ∑ i = 1 x A i ) D_x=\frac{1}{2}(\displaystyle\sum\limits_{i=1}^x i^2A_i-(2x+3)\displaystyle\sum\limits_{i=1}^x iA_i +\frac{(x+1)(x+2)}{2}\displaystyle\sum\limits_{i=1}^x A_i) Dx=21(i=1xi2Ai(2x+3)i=1xiAi+2(x+1)(x+2)i=1xAi)

我们用线段树/树状数组分别维护 A i A_i Ai i A i iA_i iAi i 2 A i i^2A_i i2Ai,对于每一次询问,分别求三个前缀和,然后根据上式进行求值即可。(取模有点小烦)

#include
using namespace std;
typedef long long LL;
const LL MOD = 998244353;

LL n, m;
LL a[200005];

template<int MOD, int RT> struct mint {
	static const int mod = MOD;
	static constexpr mint rt() { return RT; } // primitive root for FFT
	int v; explicit operator int() const { return v; } // explicit -> don't silently convert to int
	mint():v(0) {}
	mint(LL _v) { v = int((-MOD < _v && _v < MOD) ? _v : _v % MOD);
		if (v < 0) v += MOD; }
	bool operator==(const mint& o) const {
		return v == o.v; }
	friend bool operator!=(const mint& a, const mint& b) { 
		return !(a == b); }
	friend bool operator<(const mint& a, const mint& b) { 
		return a.v < b.v; }
   
	mint& operator+=(const mint& o) { 
		if ((v += o.v) >= MOD) v -= MOD; 
		return *this; }
	mint& operator-=(const mint& o) { 
		if ((v -= o.v) < 0) v += MOD; 
		return *this; }
	mint& operator*=(const mint& o) { 
		v = int((LL)v*o.v%MOD); return *this; }
	mint& operator/=(const mint& o) { return (*this) *= inv(o); }
	friend mint pow(mint a, LL p) {
		mint ans = 1; assert(p >= 0);
		for (; p; p /= 2, a *= a) if (p&1) ans *= a;
		return ans; }
	friend mint inv(const mint& a) { assert(a.v != 0); 
		return pow(a,MOD-2); }
		
	mint operator-() const { return mint(-v); }
	mint& operator++() { return *this += 1; }
	mint& operator--() { return *this -= 1; }
	friend mint operator+(mint a, const mint& b) { return a += b; }
	friend mint operator-(mint a, const mint& b) { return a -= b; }
	friend mint operator*(mint a, const mint& b) { return a *= b; }
	friend mint operator/(mint a, const mint& b) { return a /= b; }
};

using mi = mint<MOD, 5>;

struct Edge {
	int l, r;
	mi sum, sum1, sum2;
}t[1000005];

void pu(int ni) {
	t[ni].sum = t[ni << 1].sum + t[ni << 1 | 1].sum;
	t[ni].sum1 = t[ni << 1].sum1 + t[ni << 1 | 1].sum1;
	t[ni].sum2 = t[ni << 1].sum2 + t[ni << 1 | 1].sum2;
}

void build_tree(int ni, int l, int r) {
	t[ni].l = l; t[ni].r = r;
	if (l == r) {
		t[ni].sum = (mi)a[l];
		t[ni].sum1 = (mi)l * (mi)a[l];
		t[ni].sum2 = (mi)l * (mi)l * (mi)a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build_tree(ni << 1, l, mid);
	build_tree(ni << 1 | 1, mid + 1, r);
	pu(ni);
}

void fix(int ni, int l, LL x) {
	if (l == t[ni].l and t[ni].r == l) {
		t[ni].sum = (mi)x;
		t[ni].sum1 = (mi)l * (mi)x;
		t[ni].sum2 = (mi)l * (mi)l * (mi)x;
		return;
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	if (l <= mid) fix(ni << 1, l, x);
	else fix(ni << 1 | 1, l, x);
	pu(ni);
}

tuple<mi, mi, mi> query(int ni, int l, int r) {
	if (l <= t[ni].l and t[ni].r <= r) {
		return {t[ni].sum, t[ni].sum1, t[ni].sum2};
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	tuple<mi, mi, mi> tmp;
	mi ans = 0, ans1 = 0, ans2 = 0;
	if (l <= mid) {
		tmp = query(ni << 1, l, r);
		ans = ans + get<0>(tmp); 
		ans1 = ans1 + get<1>(tmp);
		ans2 = ans2 + get<2>(tmp);
	}
	if (mid < r) {
		tmp = query(ni << 1 | 1, l, r);
		ans = ans + get<0>(tmp); 
		ans1 = ans1 + get<1>(tmp);
		ans2 = ans2 + get<2>(tmp);
	}
	return {ans, ans1, ans2};
} 

void main2() {
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	build_tree(1, 1, n);
	for (int i = 1; i <= m; ++i) {
		LL o, x, y;
		cin >> o;
		if (o == 1) {
			cin >> x >> y;
			fix(1, x, y);
		}
		else {
			cin >> x;
			tuple<mi, mi, mi> tmp = query(1, 1, x);
			mi d = ((mi)x + 1) * ((mi)x + 2) * get<0>(tmp);
			mi d1 = (2 * (mi)x + 3) * get<1>(tmp);
			mi d2 = get<2>(tmp);
			mi res = ((mi)(d2 - d1 + d) / (mi)2);
			cout << (int)res << '\n'; 
		}
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

G - Black and White Stones

题目链接

我们发现边的长度很小,也就是说我们每一条边可以选择的相同石子数量的可能取值并不多。那么我们可以考虑将问题拆解成:对于所有的 i ( 0 ≤ i ≤ D + 1 ) i(0\leq i\leq D+1) i(0iD+1),每一条边都有 i i i个黑色石头有多少种放法。最后的答案就是这 D + 2 D+2 D+2个问题的答案的和。

现在求解这样的子问题:每一条边都有 k k k个黑色石头有多少种放法。对于这道题目而言,边与边的交界点是非常特殊的点,因为如果这个点是黑色石头,那么这个黑色石头会对两条边都产生贡献。题目中本质上是一个 N N N条边的环,但是除了边与边的交界处以外,剩下的点之间怎么选取,彼此之间是完全没有联系的,所以我们要限制的只有交界处的点。

考虑随便选取一个两边交界处,将这个环拆成一条直链。对于这条直链的限制条件就是:首尾的颜色必须相同。

然后对于每一条边的点,由于交界处会对两条边都产生贡献,所以我们就以交界处的情况作为状态。对于每一条边,我们有以下四种情况:

【补题笔记】AtCoder Beginner Contest 256 A~Ex_第1张图片
当每条边只能有两个黑色石头的时候,情况如上。我们假设从左向右是我们放石头的顺序,同时也是访问边的顺序,那么通过图可以看出,大圆是边与边的交界处,其颜色会影响两条边;小圆在边的中间,其黑色石子的数量与两边的大圆有关,但是内部这些小圆顺序的调换与否不会对其他边产生任何影响。

我们设上一条边处理完后,末尾是白色的方案数是 x x x,末尾是黑色的方案数是 y y y,这一条边处理完后末尾是白色的方案数是 X X X,末尾是黑色的方案数是 Y Y Y。于是有:

X = x × ( D − 2 k ) + y × ( D − 2 k − 1 ) X=x\times \binom{D-2}{k} + y\times \binom{D-2}{k-1} X=x×(kD2)+y×(k1D2)
Y = x × ( D − 2 k − 1 ) + y × ( D − 2 k − 2 ) Y=x\times \binom{D-2}{k-1} + y\times \binom{D-2}{k-2} Y=x×(k1D2)+y×(k2D2)

可以看出,这一条边的方案数可以通过上一条边的结果来推出,也就是构成了一个递推式。也就是说,我们只需要将初始值设置好,然后每一条边递推一次就可以得出这个问题的结果。但是,边数是 1 0 12 10^{12} 1012的范围,如果真的按边递归,一定会超时。

我们发现,每一次从上一个边的方案数推出这条边的方案数时,所乘的系数都没有变。这样的话,就可以用矩阵快速幂解决这个问题:我们试着将上面两个公式进行一个变型,变成矩阵的形式:
[ X e Y e ] = [ x 0 y 0 ] [ ( D − 2 k ) ( D − 2 k − 1 ) ( D − 2 k − 1 ) ( D − 2 k − 2 ) ] n \left[ \begin{matrix} X_e & Y_e \end{matrix} \right] = \left[ \begin{matrix} x_0 & y_0 \end{matrix} \right] \left[ \begin{matrix} \binom{D-2}{k} & \binom{D-2}{k-1} \\ \binom{D-2}{k-1} & \binom{D-2}{k-2} \end{matrix} \right]^n [XeYe]=[x0y0][(kD2)(k1D2)(k1D2)(k2D2)]n

其中 X e , Y e X_e,Y_e Xe,Ye表示这 n n n条边走完之后以白色石头结尾、以黑色石头结尾分别的方案数, x 0 , y 0 x_0,y_0 x0,y0是整个过程开始前的最初状态(一会儿进行讨论)。

要注意的是,在计算组合数时,对于 ( n m ) \binom{n}{m} (mn),我们可能会出现 n < m nn<m m < 0 m<0 m<0 n < 0 n<0 n<0的情况。当出现这些情况时,说明这个情形不可能存在,方案数就是 0 0 0,所以直接返回 0 0 0即可。

这样的话,由于矩阵很小,可以作为一个常数,然后我们就从 O ( n ) O(n) O(n)变成了 O ( log ⁡ n ) O(\log n) O(logn)

我们设以白色石头作为链的最开始,经过 n n n条边后以白色石头作为结尾的方案数为 W W W,以黑色石头作为链的最开始,经过 n n n条边后以黑色石头作为结尾的方案数为 B B B

这个子问题的最终答案就是 W + B W+B W+B

也就是说,我们要求的就是这两种链的情况。

  1. x 0 = 1 , y 0 = 0 x_0=1,y_0=0 x0=1,y0=0,进行矩阵快速幂运算后, W = X e W=X_e W=Xe
  2. x 0 = 0 , y 0 = 1 x_0=0,y_0=1 x0=0,y0=1,进行矩阵快速幂运算后, B = Y e B=Y_e B=Ye

将所有的子问题答案算完之后进行累加并取模,就得到了最终的结果。

#include
using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL p = 998244353;
const LL matSize = 4;

struct Matrix
{
	LL matA[matSize][matSize];
	inline Matrix() { memset(matA, 0, sizeof(matA)); }
	inline Matrix operator - (const Matrix &MAT_T) const {
		Matrix MAT_RES;
		for (int i = 0; i < matSize; ++i) for (int j = 0; j < matSize; ++j)
			MAT_RES.matA[i][j] = (matA[i][j] - MAT_T.matA[i][j]) % p;
		return MAT_RES;
	}
	inline Matrix operator + (const Matrix &MAT_T) const {
		Matrix MAT_RES;
		for (int i = 0; i < matSize; ++i) for (int j = 0; j < matSize; ++j)
			MAT_RES.matA[i][j] = (matA[i][j] + MAT_T.matA[i][j]) % p;
		return MAT_RES;
	}
	inline Matrix operator * (const Matrix &MAT_T) const {
		Matrix MAT_RES; int MAT_R;
		for (int i = 0; i < matSize; ++i) for (int k = 0; k < matSize; ++k){
			MAT_R = matA[i][k];
			for (int j = 0; j < matSize; ++j)
				MAT_RES.matA[i][j]+=MAT_T.matA[k][j]*MAT_R, MAT_RES.matA[i][j]%=p;
		}
		return MAT_RES;
	}
	inline Matrix operator ^ (LL MAT_X) const {
		Matrix MAT_RES, MAT_BAS;
		for (int i = 0; i < matSize; ++i) MAT_RES.matA[i][i] = 1;
		for (int i = 0; i < matSize; ++i) for (int j = 0; j < matSize; ++j)
			MAT_BAS.matA[i][j] = matA[i][j] % p;
		while (MAT_X) {
			if (MAT_X&1)MAT_RES=MAT_RES*MAT_BAS; 
			MAT_BAS=MAT_BAS*MAT_BAS; MAT_X>>=1;
		}
		return MAT_RES;
	}
};

LL jc[20050], inv[20050];

template<class T> T power(T a, LL b) {
	T res = 1;
    for (; b; b >>= 1) {
        if (b % 2) res = (res * a) % mod;
        a = (a * a) % mod;
    }
    return res;
}

void init(LL iN) {
	jc[0] = 1;
	for (LL i = 1; i <= iN + 1; ++i) {
		jc[i] = (jc[i - 1] * i) % mod;
	}
	inv[iN + 1] = power(jc[iN + 1], mod - 2);
	for (LL i = iN; i >= 0; --i) {
		inv[i] = inv[i + 1] * (i + 1) % mod;
	} 
}

LL C(LL N, LL M) {
	return jc[N] * inv[M] % mod * inv[N - M] % mod;
}

LL n, d;
LL ans = 0;

void main2() {
	cin >> n >> d;
	++d;
	for (int i = 0; i <= d; ++i) {
		Matrix dp1, dp2;
		dp1.matA[0][0] = dp2.matA[0][1] = 1;
		dp1.matA[0][1] = dp2.matA[0][0] = 0;
		Matrix tmp;
		tmp.matA[0][0] = ((d - 2 >= i) ? C(d - 2, i) : 0);
		tmp.matA[0][1] = tmp.matA[1][0] = ((i >= 1 and d - 2 >= i - 1) ? C(d - 2, i - 1) : 0);
		tmp.matA[1][1] = ((i >= 2 and d >= i) ? C(d - 2, i - 2) : 0);
		dp1 = dp1 * (tmp ^ n);
		dp2 = dp2 * (tmp ^ n);
		ans = (ans + dp1.matA[0][0] + dp2.matA[0][1]) % mod;
	}
	cout << ans;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	init(10000);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

Ex - I like Query Problem

题目链接

势能线段树的模板题。吉老师的线段树论文

考虑到操作只有对区间每个数做除法,以及区间赋值。所以我们为了精简写法,将除法操作用赋值的方式来实现。

我们对线段树的每一个节点维护区间和、赋值标记、最大值、最小值。在进行除法的时候,判断最大值与最小值分别除以 x x x,看看两者结果是否相同,如果相同,则说明这个区间内的所有数值在除以 x x x后的结果都是相同的,那么就对这个区间进行区间赋值。否则,递归往孩子结点去寻找。其他的和普通线段树是一样的操作。

#include
using namespace std;
typedef long long LL;

int n, q;
int a[500005];

struct Tree {
	int l, r;
	LL sum, mn, mx, chg;
}t[2000005];

void pu(int ni) {
	t[ni].sum = t[ni << 1].sum + t[ni << 1 | 1].sum;
	t[ni].mn = min(t[ni << 1].mn, t[ni << 1 | 1].mn);
	t[ni].mx = max(t[ni << 1].mx, t[ni << 1 | 1].mx);
	t[ni].chg = -1;
}

void pd(int ni) {
	int li = (ni << 1), ri = (ni << 1 | 1);
	int ls = t[li].r - t[li].l + 1, rs = t[ri].r - t[ri].l + 1;
	if (t[ni].chg >= 0) {
		t[li].sum = t[ni].chg * ls; t[li].chg = t[ni].chg;
		t[ri].sum = t[ni].chg * rs; t[ri].chg = t[ni].chg;
		t[li].mx = t[li].mn = t[ri].mx = t[ri].mn = t[ni].chg;
		t[ni].chg = -1;
	}
}

void build_tree(int ni, int l, int r) {
	t[ni].l = l; t[ni].r = r;
	if (l == r) {
		t[ni].sum = t[ni].mn = t[ni].mx = a[l];
		t[ni].chg = -1;
		return;
	}
	int mid = (l + r) >> 1;
	build_tree(ni << 1, l, mid);
	build_tree(ni << 1 | 1, mid + 1, r);
	pu(ni);
}

void div(int ni, int l, int r, LL x) {
	if (l <= t[ni].l and t[ni].r <= r) {
		LL res1 = t[ni].mx / x, res2 = t[ni].mn / x;
		if (res1 == res2) {
			t[ni].chg = t[ni].mx = t[ni].mn = res1;
			t[ni].sum = res1 * (t[ni].r - t[ni].l + 1);
			return;
		}
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	pd(ni);
	if (l <= mid) div(ni << 1, l, r, x);
	if (mid < r) div(ni << 1 | 1, l, r, x);
	pu(ni);
}

void fix(int ni, int l, int r, LL x) {
	if (l <= t[ni].l and t[ni].r <= r) {
		t[ni].chg = x;
		t[ni].sum = x * (t[ni].r - t[ni].l + 1);
		t[ni].mx = t[ni].mn = x;
		return;
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	pd(ni);
	if (l <= mid) fix(ni << 1, l, r, x);
	if (mid < r) fix(ni << 1 | 1, l, r, x);
	pu(ni);
}

LL query(int ni, int l, int r) {
	if (l <= t[ni].l and t[ni].r <= r) {
		return t[ni].sum;
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	pd(ni);
	LL ans1 = 0, ans2 = 0;
	if (l <= mid) ans1 = query(ni << 1, l, r);
	if (mid < r) ans2 = query(ni << 1 | 1, l, r);
	return ans1 + ans2;
}

void main2() {
	cin >> n >> q;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	build_tree(1, 1, n);
	for (int i = 1; i <= q; ++i) {
		int o, l, r, x; cin >> o;
		if (o == 1) {
			cin >> l >> r >> x;
			div(1, l, r, x);
		}
		else if (o == 2) {
			cin >> l >> r >> x;
			fix(1, l, r, x);
		}
		else {
			cin >> l >> r;
			cout << query(1, l, r) << '\n';
		}
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

你可能感兴趣的:(算法学习,做题笔记,算法,数据结构,acm竞赛,c++)