2021CCPC东北赛题解ACDEIJKM

2021CCPC东北赛题解ACDEIJKM

I. Takeaway

题意

有七种外卖,编号 1 ∼ 7 1\sim 7 17,价格分别为 7 , 27 , 41 , 63 , 78 , 108 7,27,41,63,78,108 7,27,41,63,78,108.现有三种优惠券:①满 69 69 69 15 15 15;②满 89 89 89 30 30 30;③满 120 120 120 50 50 50.每次只能用一张外卖券,一张外卖券只能用一次.

t    ( 1 ≤ t ≤ 1 e 6 ) t\ \ (1\leq t\leq 1\mathrm{e}6) t  (1t1e6)组测试数据.每组测试数据第一行输入一个整数 n    ( 1 ≤ n ≤ 7 ) n\ \ (1\leq n\leq 7) n  (1n7),表示某人一次性点的外卖的数量.第二行输入 n n n个整数 a 1 , ⋯   , a n a_1,\cdots,a_n a1,,an,表示某人点的外卖的编号.

对每组测试数据,输出他至少需付多少钱.

代码 -> 2021CCPC东北赛-I(模拟)
int dish[10] = { 0,7,27,41,49,63,78,108 };

void solve() {
	int n; cin >> n;
	int sum = 0;
	while (n--) {
		int a; cin >> a;
		sum += dish[a];
	}

	if (sum >= 120) sum -= 50;
	else if (sum >= 89) sum -= 30;
	else if (sum >= 69) sum -= 15;
	cout << sum << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


E. Easy Math Problem

题意

t    ( 1 ≤ t ≤ 4000 ) t\ \ (1\leq t\leq 4000) t  (1t4000)组测试数据.每组测试数据输入一个整数 p    ( 1 ≤ p ≤ 1 e 9 ) p\ \ (1\leq p\leq 1\mathrm{e}9) p  (1p1e9).

对每组测试数据,求一个 p p p的倍数 k    ( 1 ≤ k ≤ 1 e 18 )   s . t .   k k\ \ (1\leq k\leq 1\mathrm{e}18)\ s.t.\ k k  (1k1e18) s.t. k除自身外的所有正因数构成的元素个数不超过 1000 1000 1000的集合中的数之和为 k k k.第一行输出两个整数,分别表示 k k k和集合的元素个数 n n n.第二行输出 n n n个整数,表示 k k k除自身外的所有正因数构成的集合.

思路

注意到 1 + 2 + 3 = 6 1+2+3=6 1+2+3=6,两边乘 p p p即可.

代码 -> 2021CCPC东北赛-E(构造)
void solve() {
	ll p; cin >> p;
	cout << 6 * p << ' ' << 3 << endl;
	cout << p << ' ' << 2 * p << ' ' << 3 * p << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


K. City

题意

有一张包含 n n n个节点和 m m m条边的无向图,每条边有一个权值.每进行一次伤害为 x x x的攻击时,所有边权 < x <x <x的边都会切断.问每次攻击后还有多少对节点连通.

t    ( 1 ≤ t ≤ 10 ) t\ \ (1\leq t\leq 10) t  (1t10)组测试数据.每组测试数据第一行输入三个整数 n , m , q    ( 2 ≤ n ≤ 1 e 5 , 1 ≤ m , q ≤ 2 e 5 ) n,m,q\ \ (2\leq n\leq 1\mathrm{e}5,1\leq m,q\leq 2\mathrm{e}5) n,m,q  (2n1e5,1m,q2e5),分别表示节点数、边数、询问数.接下来 m m m行每行输入三个整数 x , y , k    ( 1 ≤ x , y ≤ n , 1 ≤ k ≤ 1 e 9 ) x,y,k\ \ (1\leq x,y\leq n,1\leq k\leq 1\mathrm{e}9) x,y,k  (1x,yn,1k1e9),表示节点 x x x y y y间存在权值为 k k k的无向边.接下来 q q q行每行包含一个整数 x    ( 1 ≤ x ≤ 1 e 9 ) x\ \ (1\leq x\leq 1\mathrm{e}9) x  (1x1e9),表示询问进行一次伤害为 x x x的攻击时有多少对节点连通.

思路

显然可用并查集维护连通块,但并查集做删除操作不方便.考虑离线,先将所有边按边权降序排列,将所有询问按 x x x降序排列,则全程只需考虑加边.

n n n个节点的连通块对答案的贡献为 n ( n − 1 ) 2 \dfrac{n(n-1)}{2} 2n(n1).合并包含 x x x y y y个节点的两连通块时,答案的净增量为 ( x + y ) ( x + y ) − 1 2 − x ( x − 1 ) 2 − y ( y − 1 ) 2 = x y \dfrac{(x+y)(x+y)-1}{2}-\dfrac{x(x-1)}{2}-\dfrac{y(y-1)}{2}=xy 2(x+y)(x+y)12x(x1)2y(y1)=xy.

代码 -> 2021CCPC东北赛-K(离线+并查集)
const int MAXN = 2e5 + 5;
int n, m, q;  // 节点数、边数、询问数
struct Edge {
	int u, v, w;
	
	bool operator<(const Edge& B) { return w > B.w; }
}edges[MAXN];
pii ques[MAXN];  // 询问的x、编号
int fa[MAXN], siz[MAXN];  // 并查集的fa[]数组、集合的大小
ll ans[MAXN];  // 每个询问的答案

int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }

void solve() {
	cin >> n >> m >> q;
	for (int i = 0; i < m; i++) {
		int x, y, z; cin >> x >> y >> z;
		edges[i] = { x,y,z };
	}
	for (int i = 0; i < q; i++) {
		int x; cin >> x;
		ques[i] = { x,i };
	}

	for (int i = 1; i <= n; i++) fa[i] = i, siz[i] = 1;  // 初始化

	sort(edges, edges + m);
	sort(ques, ques + q, greater<pii>());

	ll res = 0;  // 每个询问的答案
	int idx = 0;  // 当前用到的边的下标
	for (int i = 0; i < q; i++) {  // 枚举询问
		while (idx < m && edges[idx].w >= ques[i].first) {
			int u = find(edges[idx].u), v = find(edges[idx].v);
			if (u != v) {
				res += (ll)siz[u] * siz[v];
				fa[v] = u;
				siz[u] += siz[v];
			}
			idx++;
		}
		ans[ques[i].second] = res;
	}

	for (int i = 0; i < q; i++) cout << ans[i] << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


M. Master of Shuangpin

题意

在这里插入图片描述

根据上表将输入的拼音转为双拼.规则:①若拼音长度为 1 1 1,则需重复一遍,保证长度为 2 2 2;②若拼音长度为 2 2 2,输出它本身;③对类似于ang的拼音,应先输出其首字母,再查表输出第二个字母.

有多组测试数据.每组测试数据输入一行包含若干个拼音.数据保证测试数据的组数不超过 1000 1000 1000,每组测试数据中的拼音总数不超过 500 500 500,且拼音中不含’v’.

思路

模拟即可.注意长度为 3 3 3的拼音有如下三种情况:jia、zha、ang.

代码 -> 2021CCPC东北赛-M(模拟)
map<string, string> mp = {
	{"q","q"}, {"iu","q"},{"w","w"}, {"ei","w"}, {"r","r"}, {"uan","r"},{"t","t"}, {"ue","t"},
	{"y","y"}, {"un","y"},{"u","u"}, {"sh","u"}, {"i","i"}, {"ch","i"},{"o","o"}, {"uo","o"},
	{"p","p"}, {"ie","p"},{"a","a"}, {"s","s"}, {"ong","s"}, {"iong","s"},{"d","d"}, {"ai","d"},
	{"f","f"}, {"en","f"},{"g","g"}, {"eng","g"}, {"h","h"}, {"ang","h"},{"j","j"}, {"an","j"},
	{"k","k"}, {"uai","k"},{"ing","k"}, {"l","l"}, {"uang","l"}, {"iang","l"},{"z","z"}, {"ou","z"},
	{"x","x"}, {"ia","x"},{"ua","x"}, {"c","c"}, {"ao","c"}, {"v","v"},{"zh","v"}, {"ui","v"},
	{"b","b"}, {"in","b"},{"n","n"}, {"iao","n"}, {"m","m"}, {"ian","m"}, {"e","e"}
};


void solve() {
	string line;
	while (getline(cin, line)) {
		stringstream ss(line);
		string s;
		while (ss >> s) {
			if (s.length() == 1) cout << s << s << ' ';
			else if (s.length() == 2) cout << s << ' ';
			else {
				if (mp.count(s.substr(0, 3))) {
					if (s.length() == 3) {  // 特判ang
						cout << s[0] << mp[s.substr(0, 3)] << ' ';
						continue;
					}
					cout << mp[s.substr(0, 3)];
					s = s.substr(3);
				}
				else if (mp.count(s.substr(0, 2))) {
					cout << mp[s.substr(0, 2)];
					s = s.substr(2);
				}
				else {
					cout << s[0];
					s = s.substr(1);
				}
				cout << mp[s] << ' ';
			}
		}

		cout << endl;
	}
}

int main() {
	solve();
}


C. Vertex Deletion

题意

给定一棵包含编号 1 ∼ n 1\sim n 1n n n n个节点的树,其中节点 1 1 1为根节点.每次操作可删除一个节点.好的操作定义为删除节点后没有孤立的节点,即每个节点至少存在另一个节点与之连通.求好的操作的个数,答案对 998244353 998244353 998244353取模.

t t t组测试数据.每组测试数据第一行输入一个整数 n    ( 1 ≤ n ≤ 1 e 6 ) n\ \ (1\leq n\leq 1\mathrm{e}6) n  (1n1e6).接下来 ( n − 1 ) (n-1) (n1)行每行输入两个整数 u , v    ( 1 ≤ u , v ≤ n , u ≠ v ) u,v\ \ (1\leq u,v\leq n,u\neq v) u,v  (1u,vn,u=v),表示节点 u u u v v v间存在边.数据保证所有测试数据的 n n n之和不超过 1 e 6 1\mathrm{e}6 1e6.

思路

d p [ u ] [ 0 ] dp[u][0] dp[u][0]表示删除节点 u u u; d p [ u ] [ 1 ] dp[u][1] dp[u][1]表示不删除节点 u u u,但删去所有儿子节点; d p [ u ] [ 2 ] dp[u][2] dp[u][2]表示不删除节点 u u u,且至少保留一个儿子节点.最终答案 d p [ 1 ] [ 0 ] + d p [ 1 ] [ 2 ] dp[1][0]+dp[1][2] dp[1][0]+dp[1][2].

d p [ u ] [ 0 ] dp[u][0] dp[u][0],无需考虑节点 u u u是否符合条件;对 d p [ u ] [ 1 ] dp[u][1] dp[u][1],为使得节点 u u u符合条件,需保留 u u u的父节点;对 d p [ u ] [ 2 ] dp[u][2] dp[u][2],节点 u u u始终满足条件.

设节点 u u u的一个子节点为 v v v.

①若删除 u u u,为使得 v v v符合条件,可删除 v v v或至少保留 v v v的一个子节点,状态转移方程: d p [ u ] [ 0 ] = ∏ e d g e < u , v > ( d p [ v ] [ 0 ] + d p [ v ] [ 2 ] ) \displaystyle dp[u][0]=\prod_{edge<u,v>}(dp[v][0]+dp[v][2]) dp[u][0]=edge<u,v>(dp[v][0]+dp[v][2]).

②若不删除 u u u且删除所有儿子节点,则 v v v一定被删除,状态转移方程: d p [ u ] [ 1 ] = ∏ e d g e < u , v > d p [ v ] [ 0 ] \displaystyle dp[u][1]=\prod_{edge<u,v>}dp[v][0] dp[u][1]=edge<u,v>dp[v][0].

③若不删除 u u u且至少保留一个儿子节点,考虑容斥,状态转移方程: d p [ u ] [ 2 ] = ∏ e d g e < u , v > ( d p [ v ] [ 0 ] + d p [ v ] [ 1 ] + d p [ v ] [ 2 ] ) − d p [ u ] [ 1 ] \displaystyle dp[u][2]=\prod_{edge<u,v>}(dp[v][0]+dp[v][1]+dp[v][2])-dp[u][1] dp[u][2]=edge<u,v>(dp[v][0]+dp[v][1]+dp[v][2])dp[u][1].

代码 -> 2021CCPC东北赛-C(树形DP)
const int MAXN = 2e5 + 5;
const int MOD = 998244353;
int n;
vi edges[MAXN];
// dp[u][0]表示删除节点u
// dp[u][1]表示不删除节点u,但删去所有儿子节点
// dp[u][2]表示不删除节点u,且至少保留一个儿子节点
int dp[MAXN][3];  

void dfs(int u, int fa) {  // 当前节点、前驱节点
	dp[u][0] = dp[u][1] = dp[u][2] = 1;  // 乘法,初始化为1

	for (auto v : edges[u]) {
		if (v == fa) continue;

		dfs(v, u);  // 递归求子树信息

		dp[u][0] = ((ll)dp[v][0] + dp[v][2]) % MOD * dp[u][0] % MOD;
		dp[u][1] = (ll)dp[u][1] * dp[v][0] % MOD;
		dp[u][2] = ((ll)dp[v][0] + dp[v][1] + dp[v][2]) % MOD * dp[u][2] % MOD;
	}
	dp[u][2] = ((dp[u][2] - dp[u][1]) % MOD + MOD) % MOD;
}

void solve() {
	cin >> n;

	for (int i = 1; i <= n; i++) edges[i].clear();

	for (int i = 1; i < n; i++) {
		int u, v; cin >> u >> v;
		edges[u].push_back(v), edges[v].push_back(u);
	}

	dfs(1, -1);  // 从根节点开始搜,根节点无前驱节点

	cout << (dp[1][0] + dp[1][2]) % MOD << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


A. Matrix

题意

1 ∼ n 2 1\sim n^2 1n2 n 2 n^2 n2个数填入 n × n n\times n n×n的矩阵,每个数只能用一次.对每个固定的方案,令 a i a_i ai为第 i    ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i  (1in)行的最小值,集合 S = { a 1 , ⋯   , a n } ⋂ { 1 , ⋯   , n } S=\{a_1,\cdots,a_n\}\bigcap\{1,\cdots,n\} S={a1,,an}{1,,n}.求所有填法的集合 S S S大小之和,即 ∑ ∣ S ∣ \displaystyle\sum |S| S,答案对 998244353 998244353 998244353取模.

t    ( 1 ≤ t ≤ 30 ) t\ \ (1\leq t\leq 30) t  (1t30)组测试数据.每组测试数据输入一个整数 n    ( 1 ≤ n ≤ 5000 ) n\ \ (1\leq n\leq 5000) n  (1n5000).

思路

显然只需考虑 1 ∼ n 1\sim n 1n作为某行的最小值时对答案的贡献.以 1 1 1作为某行的最小值为例,先将其放到某行(不妨设为第一行),有 n n n种情况.在剩下的 ( n 2 − 1 ) (n^2-1) (n21)个数中选 ( n − 1 ) (n-1) (n1) > 1 >1 >1的数放在第一行,有 C n 2 − 1 n − 1 C_{n^2-1}^{n-1} Cn21n1种情况.第一行的数和其他行的数全排列,有 n ! ( n 2 − n ) ! n!(n^2-n)! n!(n2n)!种情况.显然 a n s = n ⋅ n ! ⋅ ( n 2 − n ) ! ⋅ ∑ i = 1 n C n 2 − 1 n − i ans=n\cdot n!\cdot (n^2-n)!\cdot \displaystyle \sum_{i=1}^n C_{n^2-1}^{n-i} ans=nn!(n2n)!i=1nCn21ni.

代码 -> 2021CCPC东北赛-A(组合计数)
const int MAXN = 5005 * 5005;
const int MOD = 998244353;
int fac[MAXN], ifac[MAXN];

void init() {  // 预处理阶乘及其逆元
	fac[0] = 1;
	for (int i = 1; i < MAXN; i++) fac[i] = (ll)fac[i - 1] * i % MOD;

	ifac[MAXN - 1] = qpow(fac[MAXN - 1], MOD - 2, MOD);
	for (int i = MAXN - 1; i; i--) ifac[i - 1] = (ll)ifac[i] * i % MOD;
}

int C(int n, int m) {  // C(n,m)
	if (n < m) return 0;
	else return (ll)fac[n] * ifac[m] % MOD * ifac[n - m] % MOD;
}

void solve() {
	init();

	CaseT{
		int n; cin >> n;
		
		int ans = 0;
		for (int i = 1; i <= n; i++) ans = ((ll)ans + C(n * n - i, n - 1)) % MOD;
		ans = (ll)ans * n % MOD * fac[n] % MOD * fac[n * n - n] % MOD;
		cout << ans << endl;
	}
}

int main() {
	solve();
}


J. Transform

题意

给定两点 ( A , B , C ) (A,B,C) (A,B,C) ( x , y , z ) (x,y,z) (x,y,z).设过点 ( A , B , C ) (A,B,C) (A,B,C) ( 0 , 0 , 0 ) (0,0,0) (0,0,0)的直线为 L L L.将 ( x , y , z ) (x,y,z) (x,y,z) L L L旋转角度 r r r − r -r r分别得到点 P P P Q Q Q,输出其中 z z z坐标较大的点的坐标,误差不超过 1 e − 6 1\mathrm{e}-6 1e6.数据保证答案唯一.

t    ( 1 ≤ t ≤ 5 e 4 ) t\ \ (1\leq t\leq 5\mathrm{e}4) t  (1t5e4)组测试数据.每组测试数据第一行输入七个整数 A , B , C , x , y , z , r    ( 1 ≤ A , B , C , x , y , z ≤ 100 , 1 ≤ r < 180 ) A,B,C,x,y,z,r\ \ (1\leq A,B,C,x,y,z\leq 100,1\leq r<180) A,B,C,x,y,z,r  (1A,B,C,x,y,z100,1r<180).

代码 -> 2021CCPC东北赛-J(三维旋转公式)
struct Point {
	double x, y, z;

	Point(double _x, double _y, double _z) :x(_x), y(_y), z(_z) {}
};

Point rotate(Point P, Point Q, double theta) {  // 向量P绕向量OQ旋转theta弧度
	double len = sqrt(Q.x * Q.x + Q.y * Q.y + Q.z * Q.z);
	double tmpx = Q.x / len, tmpy = Q.y / len, tmpz = Q.z / len;
	double resx = (tmpx * tmpx * (1 - cos(theta)) + cos(theta)) * P.x
		+ (tmpx * tmpy * (1 - cos(theta)) - tmpz * sin(theta)) * P.y
		+ (tmpx * tmpz * (1 - cos(theta)) + tmpy * sin(theta)) * P.z;
	double resy = (tmpy * tmpx * (1 - cos(theta)) + tmpz * sin(theta)) * P.x
		+ (tmpy * tmpy * (1 - cos(theta)) + cos(theta)) * P.y
		+ (tmpy * tmpz * (1 - cos(theta)) - tmpx * sin(theta)) * P.z;
	double resz = (tmpz * tmpx * (1 - cos(theta)) - tmpy * sin(theta)) * P.x
		+ (tmpz * tmpy * (1 - cos(theta)) + tmpx * sin(theta)) * P.y
		+ (tmpz * tmpz * (1 - cos(theta)) + cos(theta)) * P.z;
	return Point(resx, resy, resz);
}

void solve() {
	int A, B, C, x, y, z, r; cin >> A >> B >> C >> x >> y >> z >> r;
	Point P(x, y, z), Q(A, B, C);
	Point ans1 = rotate(P, Q, r * pi / 180);
	Point ans2 = rotate(P, Q, -r * pi / 180);
	if (cmp(ans1.z, ans2.z) > 0) cout << fixed << setprecision(12) << ans1.x << ' ' << ans1.y << ' ' << ans1.z << endl;
	else cout << fixed << setprecision(12) << ans2.x << ' ' << ans2.y << ' ' << ans2.z << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


D. Lowbit

题意 ( 7   s 7\ \mathrm{s} 7 s)

维护一个序列 a 1 , ⋯   , a n a_1,\cdots,a_n a1,,an,支持如下两种操作:

1   l   r 1\ l\ r 1 l r,表示所有 a i + = l o w b i t ( a i )    ( 1 ≤ l ≤ i ≤ r ≤ n ) a_i+=lowbit(a_i)\ \ (1\leq l\leq i\leq r\leq n) ai+=lowbit(ai)  (1lirn).

2   l   r 2\ l\ r 2 l r,表示询问 ∑ i = l r a i \displaystyle\sum_{i=l}^r a_i i=lrai,答案对 998244353 998244353 998244353取模.

t    ( 1 ≤ t ≤ 20 ) t\ \ (1\leq t\leq 20) t  (1t20)组测试数据.每组测试数据第一行输入一个整数 n    ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n  (1n1e5),表示序列长度.第二行输入 n n n个整数 a 1 , ⋯   , a n    ( 1 ≤ a i < 998244353 ) a_1,\cdots,a_n\ \ (1\leq a_i<998244353) a1,,an  (1ai<998244353).第三行输入一个整数 m    ( 1 ≤ m ≤ 1 e 5 ) m\ \ (1\leq m\leq 1\mathrm{e}5) m  (1m1e5),表示操作个数.接下来 m m m行每行输入一个操作,格式如上.

思路

注意到二进制数为$100\cdots 的形式时 , 的形式时, 的形式时,+=lowbit 操作退化为 操作退化为 操作退化为=2$.当二进制数为 1 ⋯ 100 ⋯ 1\cdots 100\cdots 1100的形式时, + = l o w b i t +=lowbit +=lowbit操作会使得低位的 1 1 1向高位移动,若干次操作后低位的 1 1 1与高位的 1 1 1相邻,此时再 + = l o w b i t +=lowbit +=lowbit会发生进位,变为$100\cdots 的形式 . 综上 , 的形式.综上, 的形式.综上,lowbit 操作在不超过 操作在不超过 操作在不超过O(\log a) 次后都退化为 次后都退化为 次后都退化为=2 操作 , 只需在节点上开一个变量 操作,只需在节点上开一个变量 操作,只需在节点上开一个变量flag$记录当前区间内是否所有数的二进制表示都是$100\cdots 的形式 , 若是则按区间乘操作处理 , 打上 l a z y 标记后返回 ; 否则暴力修改叶子节点 , 每次修改后更新 的形式,若是则按区间乘操作处理,打上lazy标记后返回;否则暴力修改叶子节点,每次修改后更新 的形式,若是则按区间乘操作处理,打上lazy标记后返回;否则暴力修改叶子节点,每次修改后更新flag$.

注意 + = l o w b i t +=lowbit +=lowbit操作未退化前更新区间和不能取模,否则会影响对操作是否已退化的判断.

代码 -> 2021CCPC东北赛-D(势能线段树)
const int MAXN = 1e5 + 5;
const int MOD = 998244353;
int n;  // 序列长度
int a[MAXN];
struct Node {
	int l, r;  // 区间端点
	ll sum;  // 区间和
	bool flag;  // 记录当前区间内所有数的二进制表示是否都是100...的形式
	ll lazy;  // 区间乘懒标记
}SegT[MAXN << 2];

bool check(int x) { return x == lowbit(x); }  // 判断数的二进制表示是否是100...的形式

void push_up(int u) {
	SegT[u].sum = (SegT[u << 1].sum + SegT[u << 1 | 1].sum) % MOD;
	SegT[u].flag = SegT[u << 1].flag && SegT[u << 1 | 1].flag;
}

void build(int u, int l, int r) {
	SegT[u] = { l,r,0,false,1 };
	if (l == r) {
		SegT[u].sum = a[l];
		SegT[u].flag = check(a[l]);
		return;
	}

	int mid = l + r >> 1;
	build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
	push_up(u);
}

void push_down(int u) {
	if (SegT[u].lazy > 1) {
		SegT[u << 1].lazy = SegT[u << 1].lazy * SegT[u].lazy % MOD;
		SegT[u << 1].sum = SegT[u << 1].sum * SegT[u].lazy % MOD;
		SegT[u << 1 | 1].lazy = SegT[u << 1 | 1].lazy * SegT[u].lazy % MOD;
		SegT[u << 1 | 1].sum = SegT[u << 1 | 1].sum * SegT[u].lazy % MOD;
		SegT[u].lazy = 1;  // 注意清空为1
	}
}

void modify(int u, int l, int r) {
	if (SegT[u].l == SegT[u].r) {  // 暴力修改叶子节点
		if (SegT[u].flag) SegT[u].sum = SegT[u].sum * 2 % MOD;
		else {
			SegT[u].sum += lowbit(SegT[u].sum);  // 注意此处不能取模
			SegT[u].flag = check(SegT[u].sum);
		}
		return;
	}

	if (l <= SegT[u].l && SegT[u].r <= r) {
		if (SegT[u].flag) {  // 已退化为区间乘
			SegT[u].lazy = SegT[u].lazy * 2 % MOD;
			SegT[u].sum = SegT[u].sum * 2 % MOD;
			return;
		}
	}

	push_down(u);
	int mid = SegT[u].l + SegT[u].r >> 1;
	if (l <= mid) modify(u << 1, l, r);
	if (r > mid) modify(u << 1 | 1, l, r);
	push_up(u);
}

int query(int u, int l, int r) {  // 询问区间和[l,r]
	if (l <= SegT[u].l && SegT[u].r <= r) return SegT[u].sum % MOD;

	push_down(u);
	int mid = SegT[u].l + SegT[u].r >> 1;
	int res = 0;
	if (l <= mid) res = ((ll)res + query(u << 1, l, r))  % MOD;
	if (r > mid) res = ((ll)res + query(u << 1 | 1, l, r)) % MOD;
	return res;
}

void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];

	build(1, 1, n);
	
	CaseT{
		int op,l,r; cin >> op >> l >> r;
		if (op == 1) modify(1, l, r);
		else cout << query(1, l, r) << endl;
	}
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值