4 2025 04 26 模拟赛总结


成绩表

![[12 题解/photo/{C872C414-70A3-427C-8A97-864E7898AC31}.png]]


做题情况

  • T1:水题一道,100pts
  • T2:细节很多的水题(花了很长时间才写完,感觉细节很多很难写),100pts
  • T3:赛时感觉像是字符串kmp一类的东西,赛后看题解发现是一个用各种stl写的贪心,赛时打的暴力也没拿上分,0pts
  • T4:场上想到了大概的解法,但是没有时间了,所以只打了一个暴力,10pts

赛时心态

这场模拟赛的瓶颈大概是T2,赛场上做这题的时候感觉很痛苦,想到要处理一种情况,然后突然又想到其他很多情况,感觉很难处理,赛后看赛时代码,感觉还可以,不算太麻烦,就是要考虑清楚每种情况该如何处理。

T3这道题再给时间恐怕也做不出来,因为题解中所使用的string,set,我都不太熟悉。

T4这道题再给点时间我应该能再打点部分分,赛时的大致思路与题解一致,但实现在赛时大概是写不出来的


题面及题解

T1

题面

给定n个数,任意选择两个不同的数进行一次(不能不用)\(a\bmod b=c\)的操作,将\(a\)替换为\(c\)
求新的\(n\)个数字的和最大为多少


题解

这里注意,如果\(n\)个数的权值相同,那么减掉一个权值,否则我们一定能找到\(a<b\)使得\(a\bmod b=a\),求和不变


code

typedef long long ll;
const int N = 2e5 + 10;
int n;
int a[N];
ll sum;
int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) 
		scanf("%d", &a[i]), sum += a[i];
	int cnt = unique(a + 1, a + 1 + n) - a - 1;
	if(cnt == 1) {
		sum -= a[1];
	}
	printf("%lld\n", sum);
	return 0;
}

T2

题面

\(n\)个数字,我们要从中选出一些数来使得选出数的乘积最大(至少选一个),求选了哪些数。


题解

这题最大的特点是细节很多,我们必须考虑到每个可能会导致答案出错的小细节。

我的做法是先记录下来有多少个负数以及多少个正数,然后将原数组按照绝对值从小到大排序,然后根据负数个数的奇偶分别处理。

需要注意的点

  1. 必须至少选择一个数,即使这个数可能是\(0或1或一个负数\)
  2. 多出来的\(1和-1\)不选

最难处理的是如何处理多出来的\(-1和1\)

  • 先看1
    我们要考虑当前已经选择的数,如果当前遍历到1时还没有选择任何一个数,那么我们这个1应该放入答案数组,因为当前1是最大的
  • 再看-1
    我们要考虑当前已经选择的负数的数量,如果当前选择的负数数量为偶数,那么说明我们已经选择的负数的符号已经能够相互抵消,那么当前-1对于答案来说没有贡献。同样这里的-1也要考虑数组中没有元素的情况

时间复杂度\(O(n\log n)\)
具体实现看代码

code

const int N = 1e5 + 10;
int n;
int a[N], b[N];
vector<int> ans;

bool cmp(int a, int b) {
	if(abs(a) == abs(b)) return a < b;
	return abs(a) < abs(b);
}
int main() {
	scanf("%d", &n);
	int cnt = 0, cntt = 0;
	for(int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		b[i] = a[i];
		if(a[i] < 0) cnt++;//记录负数个数
		if(a[i] > 0) cntt++;//记录正数个数
	}
	sort(a + 1, a + 1 + n, cmp);
	if(cnt & 1) {
		//如果只有一个负数,并且没有正数,我们要将0以及负数放入考虑范围内
		if(cnt == 1 && cntt == 0) {
			int mx = -1e9 - 10;
			for(int i = 1; i <= n; i++) {
				mx = max(mx, a[i]);
			}
			ans.push_back(mx);
		} else {
			for(int i = n; i >= 1; i--) {
				//当前的0没有利用价值,因为按照绝对值排序,到0说明已经到数组末尾
				if(a[i] == 0) break;
				if(a[i] == 1) {
					if((int)ans.size() > 0) continue;
					else {
						//如果答案数组中没有元素,那么1有价值
						ans.push_back(a[i]);
						break;
					}
				}
				if(a[i] == -1) {
					//数组中有元素,并且现在用的负数个数为偶数
					if((cnt & 1) && (int)ans.size() > 0) {
						continue;
					} else {
						ans.push_back(a[i]);
						cnt--;
					}
				}
				if(a[i] > 1) ans.push_back(a[i]);
				if(a[i] < -1) {
					//如果不是最后一个负数,则可以放
					if(cnt != 1) {
						ans.push_back(a[i]);
						cnt--;
					} else {
						continue;
					}
				}
			}
		}
	} else {
		//0有价值
		if(cnt == 0 && cntt == 0) {
			int mx = -1e9 - 10;
			for(int i = 1; i <= n; i++) {
				mx = max(mx, a[i]);
			}
			ans.push_back(mx);
		}
		for(int i = n; i >= 1; i--) {
			if(a[i] == 0) break;
			if(a[i] == 1) {
				if((int)ans.size() > 0) continue;
				else {
					ans.push_back(a[i]);
					break;
				}
			}
			if(a[i] == -1) {
				if((cnt % 2 == 0) && (int)ans.size() > 0) {
					continue;
				} else {
					ans.push_back(a[i]);
					cnt--;
				}
			}
			if(a[i] > 1) ans.push_back(a[i]);
			if(a[i] < -1) {
				ans.push_back(a[i]);
				cnt--;
			}
		}
	}
	sort(ans.begin(), ans.end());
	for(int i = 0; i < (int)ans.size(); i++) {
		printf("%d ", ans[i]);
	}
	printf("\n");
	return 0;
}

T3

题面

给定一个只由小写字母构成的字符串\(t\),初始时\(t\)为白色
给出\(n\)个字符串\(s\)
我们可以进行无限次操作,每次选定\(s_{i}\),将\(t\)中与\(s_{i}\)完全相同的字串染成黑色
求最多能把\(t\)中多少字符染成黑色

\(t_{len}\leq 2\times 10^{5},\,s_{len} \leq 10\)
\(1\leq n\leq 10^{5}\)

题解

这题不会做,后来是看着题解改对的,那我就来说说题解的思路吧

首先,有一种很显然的暴力算法,\(dp[i]\)表示以\(i\)结尾的字符串最多能染色多少个字符,遍历\(s_{i}\),遍历\(t\),取出以\(s_{i}\)的最后一个字符结尾的与\(s_{i}\)长度相同的子串,然后判断两个串是否相等,时间复杂度\(O(|s|\times n\times 10)\)

可以进行优化,因为\(s_{i}\)的长度只有10种,因此我们可以每次去除一个长度为\(x(1\leq x\leq 10)\)的子串,然后判断是否存在\(s_{i}\)与这个子串相等。判断可以用set暴力判断,时间复杂度为\(O(|s|\times 10\times \log n \times 10)\)


code

int n;
string t;
set<string> st;

int main() {
	cin >> t >> n;
	for(int i = 1; i <= n; i++) {
		string s;
		cin >> s;
		st.insert(s);
	}
	t = " " + t;
	int m = t.size();
	vector<int> dp(m + 5);
	
	for(int i = 1; i <= m; i++) {
		dp[i] = dp[i - 1];
		string s;
		for(int j = i; j >= max(0, i - 10); j--) {
			s = t[j] + s;
			if(st.count(s)) {
				dp[i] = max(dp[i], dp[j - 1] + (int)s.size());
			}
		}
	}
	printf("%d\n", dp[m]);
	return 0;
}

T4

题面

给定一棵以1为根的树,n个节点,定义一棵子树为好子树,当且仅当该子树的所有节点权值乘积的因子数量\(\geq k\),求这棵树上有多少棵子树是好子树

\(1\leq n\leq 2 \times 10^{5},\,1\leq k\leq 10^{18}\)
\(1\leq a_{i}\leq 10^{6},\,1\leq u,\,v\leq n\)


题解

这题赛时想到了用质因数来求每个子树的因数个数,但是没时间写了,而且也没有那么熟悉STL,所以最后只拿了\(a_{i}\leq 3\)的10pts

下面说下思路,题解写的太恶心,所以我结合自己的思路,再捋一遍

首先我们要预处理出每个数分解质因数以后的结果,也就是每个数分别有哪些质因数、以及每个质因数的指数是多少

预处理的时间复杂度为\(O(M\log \log M)\),这是筛法的典型复杂度,由调和级数求和可得

void init() {  
    for(int i = 2; i <= 1e6; i++) {  
        if(p[i].size())  
            continue;  
            
        for(int j = i; j <= 1e6; j += i) {  
            int x = j;  
            int t = 0;  
            while(x % i == 0) {  
                x /= i;
                t++;  
            }
	        p[j].push_back({i, t});  
	    }  
    }  
}

然后对这棵树进行dfs,因为是乘积,所以可以转化为质因子相加

启发式合并(把小的加到大的上面)当前节点与其子节点,而后算一下每个质因子对答案的贡献,看是否\(\geq k\)即可
合并这里是时间复杂度瓶颈,每个元素至多被合并\(O(\log n)\)次,每次合并的时间复杂度为\(O(\log s)\)\(s\)为当前节点的\(mp.size()\),那么总的时间复杂度为\(O(n\log^{2}n)\)

\(d[x]\)\(x\)节点的因子个数,\(p_{i}\)表示\(i\)的指数

\[\displaystyle \begin{align} d[x]=\prod_{y\in x的质因数}p_{y} + 1 \end{align} \]

code

typedef long long ll;  
const int N = 2e5 + 10, M = 1e6 + 10;  
int n;  
ll k;  
int h[N], ver[N << 1], ne[N << 1], tot;  
vector <pair<int, int> > p[M];  
map <int, int> mp[N];  
int a[N];  
ll mul[N], ans;  

void add(int x, int y) {  
    ver[++tot] = y;  
    ne[tot] = h[x];  
    h[x] = tot;  
}  
void init() {  
    for(int i = 2; i <= 1e6; i++) {  
        if(p[i].size())  
            continue;  
        for(int j = i; j <= 1e6; j += i) {  
            int x = j;  
            int t = 0;  
            while(x % i == 0) {  
                x /= i;  
                t++;  
            }  
            p[j].push_back({i, t});  
         }  
    }  
}  
void dfs(int x, int fa) {  
        //将x节点自己的权值的贡献加入mp  
    for(auto o : p[a[x]]) {  
        mp[x][o.first] += o.second;  
    }  
        //分别加儿子的贡献  
    bool fn = 0;  
    for(int i = h[x]; i; i = ne[i]) {  
        int y = ver[i];  
        if (y == fa) continue;  
        dfs(y, x);  
        if(mul[y] >= k) {  
            fn = 1;  
        }  
        if(mp[x].size() < mp[y].size())  
            swap(mp[x], mp[y]);  
        for (auto o : mp[y]) {  
            mp[x][o.first] += o.second;  
        }  
    }  
        //统计因数个数  
    if(fn) {  
        ans++;  
        return;  
    }  
    mul[x] = 1;  
    for(auto o : mp[x]) {  
        mul[x] *= o.second + 1;  
        if(mul[x] >= k)  
            break;  
    }  
    ans += mul[x] >= k ? 1 : 0;    
}  

int main () {  
    init();  
    scanf("%d%lld", &n, &k);  
        for (int i = 1; i <= n; i++)  
        scanf("%d", &a[i]);  
    for(int i = 1; i < n; i++) {  
        int u, v;  
        scanf("%d%d", &u, &v);  
        add(u, v);  
        add(v, u);  
    }  
    dfs(1, 0);  
    printf("%lld\n", ans);  
    return 0;  
}
posted @ 2025-10-05 17:55  michaele  阅读(4)  评论(0)    收藏  举报