AtCoder Beginner Contest 290(D-F)「数论」「算贡献」「球盒问题」

文章讨论了在编程竞赛中如何运用数论思想解决D-Marking问题,通过分析gcd(A,B)的性质推导出标记模式。接着介绍了E-MakeitPalindrome题目,通过计算贡献来确定构建回文串的最小代价。最后,文章探讨了F-MaximumDiameter问题,利用球盒模型解决最大直径的计算,涉及插板法和同球不同盒的计数策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 D - Marking (atcoder.jp)

思路:

数论。

  • 结论:如果存在A=ad,B=bd,gcd(A,B)=d,那么对于每一个 kB mod A(0<=k<=a-1)都是d的倍数且为[ 0 , a-1 ]的一种排列。
  • 证明:kB=kbd,kB mod A = kbd-yad (y=[kB/A]) = (kb-ya)d。
  • 反证法,如果存在iB mod A \equiv jB mod A,则易得ad \mid(i-j)bd,ab互素,则a \mid(i-j),但由于k的取值范围,i-j<=a-1,显然不成立,故不存在重复倍数。
  • kB mod A = kb - [ kb/a ] * a = a ( kb/a - [ kb/a ] ) < a,故范围确定。

在本题中我们可以令D为B,N为A,kD mod N,每a步都不会重复标记,在第a步时 aD mod N=abd mod ad=0,此时向前走一步,继续进行a步标记。

最后结果就是本轮次数(直接算)+轮数(偏移量)。

记得开ll。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
#define inf 0x3f3f3f3f
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数

void work() {
	int n, K, D;
	cin >> n >> D >> K;
	K--;//第一步0不用走
	int a = n / __gcd(n, D);
	int lun = K / a;
	int ci = K % a;
	ll ans = lun + D * ci % n;
	cout << ans << '\n';
}

signed main() {
	io;
	int t;
	cin >> t;
	while (t--) {
		work();
	}
	return 0;
}

E - Make it Palindrome (atcoder.jp)

思路:

典题,算贡献。

先假设每个子串都要改一半,可以算出最大代价,然后考虑如果s[i] == s[j],那么对于所有[l,r]满足l<=i,r>=j且i-l==r-j的区间都会少改一对,然后枚举相同数字,算出区间。

也可以控制区间去枚举相同的数字。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
#define inf 0x3f3f3f3f
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
vector<int>a[N];

void work() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		int cur;
		cin >> cur;
		a[cur].pb(i);
	}
	int res = 0;
	for (int i = 1; i <= n; ++i) {
		res += (i / 2) * (n - i + 1); //按长度计算修改总次
	}

	for (int i = 1; i <= n ; ++i) {
		int l = 0, r = a[i].size() - 1;
		while (l < r) {
        //枚举所有相同的数字,在每组相同的之间有(r-l)个相同的
        //对于这l->(r-l)组数来说,每一组都有min(a[i][l],n+1-a[i][r])种字串包含这组数可以不被修改
			if (a[i][l] < n + 1 - a[i][r]) {
				res -= (r - l) * a[i][l];
				l++;
			} else {
				res -= (r - l) * (n + 1 - a[i][r]);
				r--;
			}
		}
		//cout << res << "\n";
	}
	cout << res << '\n';
}

signed main() {
	io;
	work();
	return 0;
}
//直接算贡献
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
#define inf 0x3f3f3f3f
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
//vector<int>a[N];
int a[N], s[N];

void work() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		s[a[i]]++;
	}
	int res = 0;
	int num = n;
	int l = 1, r = n;
	while (l <= r) {
		s[a[l]]--;
		s[a[r]]--;
		num -= 2;
		res += l * (num - s[a[l]]);//对于这里l位置,l->r之间有多少不同的字串,对于每一个不同的都要修改。
		res += (n + 1 - r) * (num - s[a[r]]);
		if (a[l] != a[r]) {
			res += l;
		}
		l++;
		r--;
	}
	cout << res << '\n';
}

signed main() {
	io;
	work();
	return 0;
}

F - Maximum Diameter (atcoder.jp)

思路:

算贡献+球盒问题。

单次求法的类似题详见牛客练习赛65B。

前置知识:球盒问题

参考博客:思维拓展:球盒模型_少儿编程乔老师的博客-CSDN博客 

分球的八个问题 - 知乎 (zhihu.com)

这里说两个点:

1. 虚拟球的思想:如果我们提前在每一个盒子里都放一个假球保证他是非空的, 那么可以按照隔板法直接求C( n+m-1 , m-1 ),这样求出来的是n+m同球m异盒无空方案数,此时我们将每种方案中的每个盒子都拿出来一个球,就可能出现空盒了,但是由于取之前是非空,所以可保证不会出现负球盒。

2. 拆分正整数和自然数用dp,对于正整数,dp[i][j]=dp[i-1][j-1](去掉一球一盒令其分离出一个新状态)+ dp[i-j][j](将i-j状态下的每个项都+1);对于自然数:

 我!不!会!d!p!啊!啊!啊!

思路(续):

首先由上面那个题我们就可以知道的结论是:

1. 如果n个点连树,只有n-1条边,度一共有2*n-2个。

2. 最长直径是度数>1的点的个数+1(注意两题直径的定义不同)。

我们要求的就是所有树里(度数>1的点的个数+1),考虑把这两部分分开求:

1. 树的个数就是2n-2个度分给n个点,同球不同盒无空问题,插板解决。

2. 单独考虑每一个点的贡献,我们先把一个度给该点,然后让2n-3个度分给n个点,依然同球不同盒,插板解决,这里求的就是对于每一个点有多少种方案可以做贡献,最后*n即可。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
#define inf 0x3f3f3f3f
const int N = 2e6 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数

ll fac[N], inv[N];

ll C(int a, int b) {
	if (a < b or a < 0 or b < 0)
		return 0;
	return 1ll * fac[a] * inv[b] % mod * inv[a - b] % mod;
}

void init() {
	fac[0] = fac[1] = inv[0] = inv[1] = 1;
	for (int i = 2; i < N; ++i) {
		fac[i] = 1ll * fac[i - 1] * i % mod;
		inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
	}
	for (int i = 2; i < N; ++i) {
		inv[i] = 1ll * inv[i] * inv[i - 1] % mod;
	}
}

void work() {
	int n;
	cin >> n;
	ll ans = C(2 * n - 3, n - 1) % mod + n * C(2 * n - 4, n - 1) % mod;
	ans %= mod;
	cout << ans << '\n';
}

signed main() {
	io;
	int t;
	init();
	cin >> t;
	while (t--) {
		work();
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值