牛客 周赛113 20251017

牛客 周赛113 20251017

https://ac.nowcoder.com/acm/contest/119225

A:
题目大意:给定正整数计算所有数位之和对 \(9\) 取模的结果

void solve(){
	string s;
	cin >> s;
	int sum = 0;
	for (auto c : s)
		sum += c - '0';
	cout << sum % 9;
}

签到

B:
题目大意:给定 \(n\) 个负数 \(a+b\times i\)\(a_i,b_i\) ,计算这 \(n\) 个负数的乘积

const int mod = 1e9 + 7;

void solve(){
	int n;
	cin >> n;
	vector<pair<LL, LL>> vt(n);
	for (auto &x : vt) cin >> x.first >> x.second;
	pair<LL, LL> ans = vt.front();
	
	auto cal = [&](pair<LL, LL> a, pair<LL, LL> b){
		LL x = a.first * b.first - a.second * b.second;
		LL y = a.first * b.second + a.second * b.first;
		x %= mod;
		y %= mod;
		if (x < 0) x += mod;
		if (y < 0) y += mod;
		return make_pair(x, y);
	};
	
	for (int i = 1; i < n; i ++)
		ans = cal(ans, vt[i]);
	cout << ans.first << ' ' << ans.second;
}

模拟负数计算的方式即可,时间复杂度 \(O(n)\)

C:

题目大意:给定长为 \(n\) 的数组,数组所有元素乘积为 \(x\) ,计算最大的满足 \(x\%30^k=0\)\(k\) 为多少

void solve(){
	int n;
	cin >> n;
	vector<LL> a(n);
	for (auto &x : a) cin >> x;
	map<int, int> mp;
	int ans = 0;
	for (auto x : a){
		while (x % 2 == 0){
			mp[2] ++;
			x /= 2;
		}
		while (x % 3 == 0){
			mp[3] ++;
			x /= 3;
		}
		while (x % 5 == 0){
			mp[5] ++;
			x /= 5;
		}
	}
	cout << min(mp[2], min(mp[3], mp[5]));
}

\(30\) 进行质因数分解得到 \(30=2\times3\times 5\) ,那么合适的 \(k\)\(x\) 满足关系:

\[x=2^k\times3^k\times5^k\times\alpha \]

对数组所有元素分解后计算 \(2,3,5\) 这三个因子的数量,最终答案为 \(\min(cnt_2,cnt_3,cnt_5)\)

D:
题目大意:

image-20251016215559532

void solve(){
	int n;
	cin >> n;
    vector<LL> p(n + 1, 1);
	for (int i = 1; i <= n; i ++) p[i] = p[i - 1] * i % mod;
	LL ans = 0;
	for (int i = 1; i <= n; i ++){
		ans += p[n - 1] * (i % 5);
		ans %= mod;
	}
	cout << ans;
}

一个元素对 \(5\) 取模的结果是这个数个位对 \(5\) 取模的结果

简单证明:设 \(x\) 是一个大于 \(10\) 的整数,那么 \(x\) 可以被表示为 \(10\alpha +\beta\) ,显然 \(10\alpha\) 一定为 \(5\) 的倍数,所以 \((10\alpha+\beta)\%5=\beta\%5\)

考虑一个元素在个位产生的贡献,其余 \(n-1\) 个元素有 \((n-1)!\) 种方案

E:
题目大意:

image-20251016220126576

LL dpa[2][310][N], dpb[2][310][N];
LL pb[310][N];

void solve(){
	int n;
	cin >> n;
	vector<int> a(n + 1), b (n + 1);
	for (int i = 1; i <= n; i ++) cin >> a[i], a[i] %= N;
	for (int i = 1; i <= n; i ++) cin >> b[i], b[i] %= N;
	
	dpa[0][0][0] = 1;
	for (int i = 1; i <= n; i ++){
		
		for (int j = 0; j <= i; j ++)
			for (int k = 0; k < N; k ++)
				dpa[i % 2][j][k] = 0;
		
		for (int j = 0; j <= i; j ++){
			for (int k = 0; k < N; k ++){
				if (j > 0) 
					dpa[i % 2][j][k] += dpa[!(i % 2)][j - 1][(k - a[i] + N) % N];
				dpa[i % 2][j][k] += dpa[!(i % 2)][j][k];
				dpa[i % 2][j][k] %= mod;
			}
		}
	}
	
	dpb[0][0][0] = 1;
	for (int i = 1; i <= n; i ++){
		
		for (int j = 0; j <= i; j ++)
			for (int k = 0; k < N; k ++)
				dpb[i % 2][j][k] = 0;
		
		for (int j = 0; j <= i; j ++){
			for (int k = 0; k < N; k ++){
				if (j > 0) 
					dpb[i % 2][j][k] += dpb[!(i % 2)][j - 1][(k - b[i] + N) % N];
				dpb[i % 2][j][k] += dpb[!(i % 2)][j][k];
				dpb[i % 2][j][k] %= mod;
			}
		}
	}
	
	for (int i = 0; i < N; i ++){
		for (int j = 0; j <= n; j ++){
			pb[j][i] += dpb[n % 2][j][i];
			if (j > 0) pb[j][i] += pb[j - 1][i];
			pb[j][i] %= mod;
		}
	}

	for (int i = 0; i < N; i ++){
        LL ans = 0;
		for (int j = 0; j < N; j ++){
			for (int k = 0; k <= n; k ++){
				ans += dpa[n % 2][k][j] * pb[k][(i - j + N) % N] % mod;
				ans %= mod;
			}
		}
		cout << ans << ' ';
	}
}

因为要考虑 \(b\) 中选取的元素个数小于等于 \(a\) 中选取的元素个数

所以定义 \(dp_{i,j,k}\) 表示在数组前 \(i\) 个元素中选取 \(j\) 个,他们的和为 \(k\) 的方案数

状态转移为:

\[dp_{i,j,k}=dp_{i-1,j-1,k-a[i]} + dp_{i-1,j-1,k} \]

然后枚举计算答案

for (int i = 0; i < N; i ++){
    LL ans = 0;
	for (int j = 0; j < N; j ++){
		for (int k = 0; k <= n; k ++){
			ans += dpa[n % 2][k][j] * pb[k][(i - j + N) % N] % mod;
			ans %= mod;
		}
	}
	cout << ans << ' ';
}

元素和为 \(i\) 的方案是从 \(a\) 中选取 \(j\) 个元素且元素和为 \(k\) 的方案数,乘上从 \(b\) 中选取 \([0,j]\) 个元素且元素和为 \(495-k\) 的方案数

预处理出所有的 \(dpb\) 在第二维上,有关所有的元素和的前缀和,统计答案的时间复杂度为 \(O(495^2n)\)

F:
题目大意:

image-20251016220204432

void solve(){
	int n, q;
	cin >> n >> q;
	vector<LL> a(n + 1);
	for (int i = 1; i <= n; i ++) cin >> a[i];
	
	vector<vector<LL>> tr(13,vector<LL> (n + 1));
	
	auto update = [&](int st, int x, int k){
		while (x <= n){
			tr[st][x] += k;
			x += x & -x;
		}
	};
	
	auto query = [&](int st, int x){
		int res = 0;
		while (x){
			res += tr[st][x];
			x -= x & -x;
		}
		return res;
	};
	
	const int M[] = {0, 100, 200, 110, 210, 101, 201, 111, 211, 10, 1, 11};
	
	auto endo = [&](int x){
		int d[3] = {0};
		while (x % 3 == 0){
			d[0] ++;
			x /= 3;
		}
		while (x % 5 == 0){
			d[1] ++;
			x /= 5;
		}
		while (x % 11 == 0){
			d[2] ++;
			x /= 11;
		}
		d[0] = min(d[0], 2LL);
		d[1] = min(d[1], 1LL);
		d[2] = min(d[2], 1LL);
		int sum = d[0] * 100 + d[1] * 10 + d[2];
		for (int i = 0; i < 12; i ++) if (sum == M[i]) return i;
		return 0LL;
	};
	
	auto check = [&](int x, int y){
		int a[3] = {x / 100, x % 100 / 10, x % 10};
		int b[3] = {y / 100, y % 100 / 10, y % 10};
		return a[0] + b[0] >= 2 && a[1] + b[1] >= 1 && a[2] + b[2] >= 1;
	};
	
	for (int i = 1; i <= n; i ++)
		update(endo(a[i]), i, 1);
	
	while (q --){
		int op, x, y;
		cin >> op >> x >> y;
		if (op == 1){
			update(endo(a[x]), x, -1);
			a[x] = y;
			update(endo(a[x]), x, 1);
		}
		else{
			int cnt[12];
			LL ans = 0;
			for (int i = 0; i < 12; i ++) 
				cnt[i] = query(i, y) - query(i, x - 1);
			for (int i = 0; i < 12; i ++){
				if (check(M[i], M[i])) ans += cnt[i] * (cnt[i] - 1) / 2;
				for (int j = i + 1; j < 12; j ++)
					if (check(M[i], M[j])) ans += cnt[i] * cnt[j];
			}
			cout << ans << '\n';
		}
	}
}

\(495\) 进行质因数分解有 \(495=3^2\times 5\times 11\)

对于一个询问,选出的两个数只要满足他们的质因子的数量满足 \(cnt_3\ge 2,cnt_5\ge1.cnt_{11}\ge1\) 那么这就是一种方案

不难想到利用树状数组进行 \(O(\log n)\) 的修改和查询操作

我们将数组的每个元素根据 \(3,5,11\) 这三个质因子的数量划分为 \(12\)

\(cnt_3\in\{0,1,\ge2\},cnt_5\in\{0,\ge1\},cnt_{11}\in\{0,\ge1\}\)

const int M[] = {0, 100, 200, 110, 210, 101, 201, 111, 211, 10, 1, 11};//对这里类别进行编号
	
auto endo = [&](int x){//对x进行划分类别操作
	int d[3] = {0};
	while (x % 3 == 0){
		d[0] ++;
		x /= 3;
	}
	while (x % 5 == 0){
		d[1] ++;
		x /= 5;
	}
	while (x % 11 == 0){
		d[2] ++;
		x /= 11;
	}
	d[0] = min(d[0], 2LL);
	d[1] = min(d[1], 1LL);
	d[2] = min(d[2], 1LL);
	int sum = d[0] * 100 + d[1] * 10 + d[2];//用百位记录3的个数,十位记录5的个数,个位记录11的个数
	for (int i = 0; i < 12; i ++) if (sum == M[i]) return i;
	return 0LL;
};

然后构造 \(12\) 个树状数组记录区间的信息

for (int i = 1; i <= n; i ++)
	update(endo(a[i]), i, 1);

对于询问的处理,判断哪两种类别的乘积可以为 \(495\) 的倍数

auto check = [&](int x, int y){
	int a[3] = {x / 100, x % 100 / 10, x % 10};
	int b[3] = {y / 100, y % 100 / 10, y % 10};
	return a[0] + b[0] >= 2 && a[1] + b[1] >= 1 && a[2] + b[2] >= 1;
};

就是对这两个类别计算他们是否满足 \(cnt_3\ge 2,cnt_5\ge1.cnt_{11}\ge1\)

for (int i = 0; i < 12; i ++){//枚举第一个元素的类别
	for (int j = i + 1; j < 12; j ++)//枚举第二个元素的类别,从 i+1 开始
		if (check(M[i], M[j])) ans += cnt[i] * cnt[j];
}

如果一种类别自己和自己也可以满足要求,那么这样的方案数需要单独计算

if (check(M[i], M[i])) ans += cnt[i] * (cnt[i] - 1) / 2;
posted @ 2025-10-17 15:27  才瓯  阅读(4)  评论(0)    收藏  举报