ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

2022“杭电杯”中国大学生算法设计超级联赛(8) 题解

2022-08-13 23:03:16  阅读:124  来源: 互联网

标签:return int 题解 sum tree MAXN 2022 杭电杯 MOD


A. Theramore

考虑只对长度为3的子串进行操作,发现偶数位置的字符不会出现在奇数位置,奇数位置的字符不会出现在偶数位置。

对奇偶位置字符进行排序即可。

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 1e5 + 5;

string S;
char T[MAXN];
int num[3], n;

void Solve() {
	cin >> S; n = S.length();
	num[0] = num[1] = 0;
	for(int i = 0; i < n; i += 2) num[ S[i] - '0' ]++;
	for(int i = 0; i < n; i += 2) {
		if(num[0]) num[0]--, T[i] = '0';
		else num[1]--, T[i] = '1';
	}
	num[0] = num[1] = 0;
	for(int i = 1; i < n; i += 2) num[ S[i] - '0' ]++;
	for(int i = 1; i < n; i += 2) {
		if(num[0]) num[0]--, T[i] = '0';
		else num[1]--, T[i] = '1';
	}
	for(int i = 0; i < n; i++) cout << T[i]; cout << "\n";
}

signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	int T; cin >> T;
	while(T--) Solve();
	return 0;
}

B. Darkmoon Faire

设 \(f[i]\) 为 \([1,i]\) 的划分方案数,则 \(f[i]\) 可以从 \(f[j],j<i\) 转移,问题转化成快速找符合条件的 \(j\)。

首先利用单调栈维护以 \(i\) 为结尾的区间最大值和区间最小值。

以最大值为例(最小值同理),设单调栈里相邻的两个位置为 \(x, y\),此时如果选择 \(s \in (x, y]\),则 \([s, i]\) 的区间最大值位置一定在 \(y\)。

如果 \(y\) 为奇数/偶数,则 \(s\) 必须为奇数/偶数,这样才能满足最大值位置在奇数的情况。

对所有奇数位置和所有偶数位置分别建一个线段树,线段树维护两个变量:区间中所有点覆盖次数的最大值 \(st\),区间中满足覆盖次数等于 \(st\) 的 \(f[i]\) 之和 \(sum\)。

将 \((x, y]\) 中所有的奇数/偶数位置提出来进行区间覆盖。对于 \(f[i]\),满足条件的 \(j\) 一定会被覆盖两次。可利用线段树进行区间查询。

查询完后需要将 \(f[i]\) 放入线段树并更新 \(sum\)。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int MAXN = 3e5 + 5;
const int MOD = 998244353;

struct Node {
	int sum, st, tag;
}Odd[MAXN << 2], Even[MAXN << 2];

int n, f[MAXN], id[MAXN], a[MAXN];
vector<int> Max, Min;

void PushUp(Node* tree, int l, int r, int p) {
	if(tree[p << 1].st == tree[p << 1 | 1].st) tree[p].sum = (tree[p << 1].sum + tree[p << 1 | 1].sum) % MOD;
	if(tree[p << 1].st > tree[p << 1 | 1].st) tree[p].sum = tree[p << 1].sum;
	if(tree[p << 1].st < tree[p << 1 | 1].st) tree[p].sum = tree[p << 1 | 1].sum;
	tree[p].st = max(tree[p << 1].st, tree[p << 1 | 1].st);
}

void PushDown(Node* tree, int l, int r, int p) {
	tree[p << 1].tag += tree[p].tag;
	tree[p << 1 | 1].tag += tree[p].tag;
	tree[p << 1].st += tree[p].tag;
	tree[p << 1 | 1].st += tree[p].tag;	
	tree[p].tag = 0;
}

void Build(int l = 1, int r = n, int p = 1) {
	Odd[p].sum = Odd[p].st = Odd[p].tag = 0;
	Even[p].sum = Even[p].st = Even[p].tag = 0;
	if(l == r) return ;
	int mid = l + r >> 1; 
	Build(l, mid, p << 1);
	Build(mid + 1, r, p << 1 | 1);
}

void ModifySt(Node* tree, int nl, int nr, int val, int l = 1, int r = n, int p = 1) {
	if(nl > nr || nr < l || nl > r) return ;
	if(nl <= l && nr >= r) {
		tree[p].tag += val;
		tree[p].st += val;
		return ;
	}
	PushDown(tree, l, r, p);
	int mid = l + r >> 1;
	if(nl <= mid) ModifySt(tree, nl, nr, val, l, mid, p << 1);
	if(nr > mid) ModifySt(tree, nl, nr, val, mid + 1, r, p << 1 | 1);
	PushUp(tree, l, r, p);
}

void ModifySum(Node* tree, int loc, int val, int l = 1, int r = n, int p = 1) {
	if(loc < l || loc > r) return ;
	if(l == r) {
		tree[p].sum = val;
		return ;
	}
	PushDown(tree, l, r, p);
	int mid = l + r >> 1;
	if(loc <= mid) ModifySum(tree, loc, val, l, mid, p << 1);
	else ModifySum(tree, loc, val, mid + 1, r, p << 1 | 1);
	PushUp(tree, l, r, p);
}

void CalcMax(int l, int r, int val) {
	//cout << "calcmax : " << l << " " << r << " " << val << "\n";
	if(r % 2) {
		if(l % 2 == 0) ++l;
		ModifySt(Odd, id[l], id[r], val);
	} else {
		if(l % 2 == 1) ++l;
		ModifySt(Even, id[l], id[r], val);
	}
}

void CalcMin(int l, int r, int val) {
	//cout << "calcmin : " << l << " " << r << " " << val << "\n";
	if(r % 2) {
		if(l % 2 == 1) ++l;
		ModifySt(Even, id[l], id[r - 1], val);
	} else {
		if(l % 2 == 0) ++l;
		ModifySt(Odd, id[l], id[r - 1], val);
	}
}

void Solve() {
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i];
	int cnt = 0;
	for(int i = 1; i <= n; i += 2) id[i] = ++cnt;
	cnt = 0;
	for(int i = 0; i <= n; i += 2) id[i] = ++cnt;
	Build();
	while( !Max.empty() ) Max.pop_back();
	Max.push_back(0);
	while( !Min.empty() ) Min.pop_back();
	Min.push_back(0);
	f[0] = 1; ModifySum(Odd, id[1], f[0]);
	for(int i = 1; i <= n; i++) {
		//cout << i << " i:\n";
		while( Max.back() && a[ Max.back() ] < a[i] ) {
			int b = Max.back();
			Max.pop_back();
			CalcMax(Max.back() + 1, b, -1);
		}
		CalcMax(Max.back() + 1, i, 1);
		Max.push_back(i);
		
		while( Min.back() && a[ Min.back() ] > a[i] ) {
			int b = Min.back();
			Min.pop_back();
			CalcMin(Min.back() + 1, b, -1);
		}
		CalcMin(Min.back() + 1, i, 1);
		Min.push_back(i);
		
		f[i] = 0;
		if(Odd[1].st == 2) f[i] += Odd[1].sum;
		if(Even[1].st == 2) f[i] += Even[1].sum;
		f[i] %= MOD;
		
		if(i == n) break;
		if(i & 1) ModifySum(Even, id[i + 1], f[i]);
		else ModifySum(Odd, id[i + 1], f[i]);
	}
	//for(int i = 0; i <= n; i++) cout << f[i] << " "; cout << "f\n";
	cout << f[n] << "\n";
}

signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T; cin >> T;
    while(T--) Solve();
    return 0;
}

E. Ironforge

先不考虑向左走的情况,预处理每个点向右走最远能到达哪,设 \(i\) 向右走最远能到 \(R[i]\),向左走最远能到 \(L[i]\)

具体思路是从大到小枚举点,对于枚举到的 \(i\),先判断 \(i\) 能不能走到 \(i+1\),能的话就代表其也能走到 \(R[i+1]\)。继续判断能不能走到 \(R[i+1]+1\),直至走到边界或者不能继续走时退出。

考虑从小到大枚举每个点,向左扩展区间,分为以下几种情况:

① \(i\) 和 \(i-1\) 互相都能走到,此时更新 \(R[i]=max(R[i],R[i-1]),\ L[i]=L[i-1]\)

② \(i\) 无论如何都走不到 \(i-1\),此时更新 \(L[i]=i\)

③ \(i\) 能走到 \(i-1\),但 \(i-1\) 走不到 \(i\),此时反复扩展左右区间直至不能继续走下去或走到边界

感性理解一下,对于上述操作,我们每条边如果查询成功,则被合并到当前点,下次查询时直接跳过这条边。如果查询失败则退出,最多有 \(O(n)\) 次查询失败。

每次查询可以用vector存某个质数出现的所有点,二分查找位置即可。

时间复杂度 \(O(n \log n)\)

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 2e5 + 5;

int n, m, a[MAXN], b[MAXN];
int cnt, isprime[MAXN], prime[MAXN], Map[MAXN];
int L[MAXN], R[MAXN];
vector<int> pos[MAXN];

bool Query(int l, int r, int p) {
    int posl = lower_bound(pos[p].begin(), pos[p].end(), l) - pos[p].begin(), posr = upper_bound(pos[p].begin(), pos[p].end(), r) - pos[p].begin() - 1;
    return (posr - posl + 1 > 0);
}

void Solve() {
    cin >> n >> m;
    for(int i = 1; i <= n; ++i) cin >> a[i];
    for(int i = 1; i < n; ++i) cin >> b[i];
    for(int i = 1; i <= n; ++i) pos[i].clear();
    for(int i = 1; i <= n; ++i) {
        int cur = a[i];
        for(int j = 1; j <= prime[0] && prime[j] * prime[j] <= cur; ++j) {
            if(cur % prime[j]) continue;
            while(cur % prime[j] == 0) cur /= prime[j];
            pos[ Map[ prime[j] ] ].push_back(i);
        }
        if(Map[cur]) pos[ Map[cur] ].push_back(i);
    }
    
    for(int i = n; i >= 1; --i) {
    	R[i] = i;
        while(R[i] < n && Query(i, R[i], Map[ b[ R[i] ] ]) ) R[i] = R[ R[i] + 1 ];
    }
    
    for(int i = 1; i <= n; ++i) {
        if(i > 1 && R[i - 1] >= i) {
            if( Query(i, R[i], Map[ b[i - 1] ]) ) L[i] = L[i - 1], R[i] = max(R[i], R[i - 1]);
            else L[i] = i;
        } else {
            L[i] = i;
            while(1) {
                bool flag = 1;
                while(L[i] > 1 && Query(L[i], R[i], Map[ b[ L[i] - 1 ] ]) ) L[i] = L[ L[i] - 1 ], flag = 0;
                while(R[i] < n && Query(L[i], R[i], Map[ b[ R[i] ] ]) ) R[i] = R[ R[i] + 1 ], flag = 0;
                if(flag) break;
            }
        }
    }
    
    for(int k = 1; k <= m; ++k) {
        int x, y; cin >> x >> y;
        if(L[x] <= y && y <= R[x]) cout << ("Yes\n");
        else cout << ("No\n");
    }
}

void Init(int n) {
    cnt = 0;
    isprime[1] = 1;
    for(int i = 2; i <= n; i++) {
        if(!isprime[i]) prime[++cnt] = i;
        for(int j = 1; j <= cnt && i * prime[j] <= n; j++) {
            isprime[ i * prime[j] ] = 1;
            if(i % prime[j] == 0) break;
        }
    }
    prime[0] = cnt;
    for(int i = 1; i <= cnt; i++) Map[ prime[i] ] = i;
}

signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T; cin >> T;
    Init(2e5);
    while(T--) Solve();
    return 0;
}

H. Orgrimmar

设 \(f[u][0/1/2]\) 分别为 ① \(u\) 不被选中;② \(u\) 被选中且不与儿子 \(v\) 连边;③ \(u\) 被选中且与儿子 \(v\) 连边 的最大集合数。

\[f[u][0] = \sum\max(f[v][0], f[v][1], f[v][2]) \\ f[u][1] = \sum f[v][0] + 1 \\ f[u][2] = \sum f[v][0] - \min(f[v][0] - f[v][1]) + 1 \\ \]

直接跑个树形dp即可。

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 5e5 + 5;

int f[MAXN][4], n;
vector<int> G[MAXN];

void DFS(int u, int fa) {
	int sum = 0;
	f[u][0] = f[u][1] = f[u][2] = 0;
	for(auto v : G[u]) {
		if(v == fa) continue;
		DFS(v, u);
		f[u][0] += max({f[v][0], f[v][1], f[v][2]});
		sum += f[v][0];
		f[u][1] += f[v][0];
	}
	f[u][1]++;
	for(auto v : G[u]) {
		if(v == fa) continue;
		f[u][2] = max(f[u][2], sum - f[v][0] + f[v][1]);
	}
	f[u][2]++;
}

void Solve() {
	cin >> n;
	for(int i = 1; i <= n; i++) G[i].clear();
	for(int i = 1; i < n; i++) {
		int u, v; cin >> u >> v;
		G[u].push_back(v);
		G[v].push_back(u); 
	}
	DFS(1, 0);
	//for(int i = 1; i <= n; i++) cout << f[i][0] << " "; cout << "f0\n";
	//for(int i = 1; i <= n; i++) cout << f[i][1] << " "; cout << "f1\n";
	//for(int i = 1; i <= n; i++) cout << f[i][2] << " "; cout << "f2\n";
	
	cout << max({f[1][0], f[1][1], f[1][2]}) << "\n";
}

signed main()
{
	int size(512<<20);  // 512M
    __asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	int T; cin >> T;
	while(T--) Solve();
	exit(0);
}

I. Gilneas

考虑计算一条边 \(u-v\) 对答案的贡献,其中 \(u\) 是 \(v\) 的父亲。

要令这条边染色,有 \(v\) 及其子树中的点进行access操作成功,且其后的 \(u\) 及其子树中的点进行access操作失败。

用一个线段树对所有access操作维护两个变量:\(sum\) 和 \(prod\),其中 \(prod\) 为区间内 \((1-p_i)\) 之积,\(sum\) 为 \(u\) 及其子树中的点中的所有access操作的 \(c_i \cdot p_i\) 乘以紧随在其后的 \((1-p_i)\) 的和。

枚举每个点,首先将其儿子的线段树合并在该节点的线段树上,然后直接将所有区间的 \(sum\) 加到答案中。发现对 \(u\) 的access操作会影响到最终答案,需要减去这一部分。

我们直接暴力枚举对于 \(u\) 的access操作,计算该操作成功,其后的 \(u\) 及其子树中的点进行access操作失败的期望。

时间复杂度 \(O(nlogn)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int MAXN = 4e5 + 5;
const int MOD = 1e9 + 7;

int qpow(int a, int p) {
	int res = 1;
	while(p) {
		if(p & 1) res = res * a % MOD;
		a = a * a % MOD;
		p >>= 1;
	}
	return res;
}
int Add(int a, int b) { return (a + b + MOD * 2) % MOD; }
int Sub(int a, int b) { return (a - b + MOD * 2) % MOD; }
int Mul(int a, int b) { return (a * b % MOD + MOD) % MOD; }
int Div(int a, int b) { return (a * qpow(b, MOD - 2) % MOD + MOD) % MOD; }

struct Node {
	int ls, rs, prod = 1, sum;
}tree[MAXN * 40], NUL;

int n, m, tot, root[MAXN], fa[MAXN];
vector<int> vec[MAXN];

void PushUp(int p) {
	int ls = tree[p].ls, rs = tree[p].rs;
	if(ls == 0 && rs == 0) {
		tree[p] = NUL;
	} else if(ls == 0) {
		tree[p].prod = tree[rs].prod;
		tree[p].sum = tree[rs].sum;
	} else if(rs == 0) {
		tree[p].prod = tree[ls].prod;
		tree[p].sum = tree[ls].sum;		
	} else {
		tree[p].prod = Mul(tree[ls].prod, tree[rs].prod);
		tree[p].sum = Add(Mul(tree[ls].sum, tree[rs].prod), tree[rs].sum);
	}
}

int Merge(int a, int b) {
	if(a == 0 || b == 0) return a + b;
	tree[a].ls = Merge(tree[a].ls, tree[b].ls);
	tree[a].rs = Merge(tree[a].rs, tree[b].rs);
	PushUp(a);
	return a;
}

void Modify(int& p, int loc, int prod, int sum, int l = 1, int r = m) {
	if(!p) tree[p = ++tot] = NUL;
	if(l == r) { tree[p].prod = prod, tree[p].sum = sum; return ; }
	int mid = l + r >> 1;
	if(loc <= mid) Modify(tree[p].ls, loc, prod, sum, l, mid);
	else Modify(tree[p].rs, loc, prod, sum, mid + 1, r);
	PushUp(p);
}

int Query(int p, int loc, int rprod = 1, int l = 1, int r = m) {
	if(l == r) return Mul(tree[p].sum, rprod); 
	int mid = l + r >> 1;
	if(loc <= mid) return Query(tree[p].ls, loc, Mul(rprod, tree[ tree[p].rs ].prod), l, mid);
	else return Query(tree[p].rs, loc, rprod, mid + 1, r);
}

void Solve() {
	cin >> n >> m; tree[tot = 0] = NUL;
	for(int i = 1; i <= n; i++) vec[i].clear(), root[i] = 0;
	for(int i = 2; i <= n; i++) cin >> fa[i];
	for(int i = 1; i <= m; i++) {
		int x, c, p; cin >> x >> c >> p;
		Modify(root[x], i, Sub(1, p), Mul(c, p) );
		vec[x].push_back(i);
	}
	int ans = 0;
	for(int i = n; i; i--) {
		ans = Add(ans, tree[ root[i] ].sum );
		for(auto j : vec[i]) ans = Sub(ans, Query(root[i], j) );
		root[ fa[i] ] = Merge(root[ fa[i] ], root[i]);
	}
	cout << ans << "\n";
}

signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	int T; cin >> T;
	while(T--) Solve();
	return 0;
}

标签:return,int,题解,sum,tree,MAXN,2022,杭电杯,MOD
来源: https://www.cnblogs.com/Orzjh/p/16584453.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有