CF刷题(02)——难度1700~2000

有种方式叫一下子拔高思维难度,做一些比你思维量极限还要略高一筹的题目,可以迅速上升。虽然我还是想有一个比较陡的缓冲区。于是,1700~2400,直接5道题一个难度。这个博客一共20个题(1700到2000)。
这段时间做好每道题都毫无思路最后只能翻开答案的觉悟吧 😦

1700

1.D. Count the Arrays

  • 先从 1 ~ m 中选择 n - 1 个不同的数字,然后再这 n - 1 个数字中选出 1 个一样的(最大的数字不可以选)。这样子数字的组合就有了。然后将这些数字排列。
  • 其实只需要把最大的数字放到第 2 ~ n - 1 的位置,由于要把两个相同的数字放在两边,所以要在最大数左边选择出数字往里面填,假设最大数在第 k 个位置,那么就是 C n − 1 k − 2 C_{n-1}^{k-2} Cn1k2,就是 C n − 1 0 + C n − 1 1 + . . . + C n − 3 n − 3 = 2 n − 3 C_{n-1}^{0} + C_{n-1}^{1} + ... + C_{n-3}^{n-3} = 2^{n-3} Cn10+Cn11+...+Cn3n3=2n3.
  • 注意n等于2的时候需要特判。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
ll N, M;
ll mod_pow(ll x, ll n) {
	ll res = 1;
	while(n) {
		if (n & 1) res = res * x % mod;
		x = x * x % mod;
		n >>= 1;
	}
	return res;
}
ll mod_C(ll m, ll n) {
	ll res = 1;
	for (ll i = m; i >= m - n + 1; i--) res = res * i % mod;
	for (ll i = 1; i <= n; i++) res = res * mod_pow(i, mod - 2) % mod;
	return res;
}
int main() {
	cin >> N >> M;
	if (N == 2) cout << 0 << endl;
	else {
		ll ans = mod_C(M, N - 1) * (N - 2) % mod * mod_pow(2, N - 3) % mod;
		cout << ans << endl;
	}
	return 0;
}

2.C. Infinite Fence

  • 首先我们假定r,b互质,否则由于木板无限长以及我们只需要判断是否有解,我们将r,b都除以gcd(r,b)不会影响答案。
  • 假定r<=b,此时gcd(r, b) = 1。连续k个红色长度是 r(k - 1). 如果r(k−1)+1<b无解,否则有解。
  • 是这样的,由于gcd(r, b) = 1,则 rx - by = 1 一定存在,那么一定有第 pos 的位置涂蓝色,第 pos + 1 的位置涂红色。而在这个位置是 r 最容易出现连续 k 个的地方,如果这里不会出现,那么其他地方也不会出现。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
int gcd(int a, int b) {
	if (b == 0) return a;
	return gcd(b, a % b);
}
int main() {
	int T;
	cin >> T;
	while (T--) {
		int a, b, k;
		cin >> a >> b >> k;
		if (a > b) swap(a, b);
		int x = gcd(a, b);
		a /= x, b /= x;
		if ((ll)a * ((ll)k - 1) + 1 < b) cout << "REBEL" << endl;
		else cout << "OBEY" << endl;
	}
	return 0;
}

3.C. Trailing Loves (or L’oeufs?)

  • 题意:求 n ! n! n! 转化成 b 进制后,末尾会有多少个连续的0.
  • 万万没想到,这个题的答案很大,ans的初始值得设置成 9e18 才行。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<unordered_map>
using namespace std;
typedef long long ll;
ll N, B;
unordered_map<ll, ll> divisor(ll N) {
	unordered_map<ll, ll> res;
	for (ll i = 2; i <= N / i; i++) {
		while (N % i == 0) {
			res[i]++;
			N /= i;
		}
	}
	if (N != 1) res[N]++;
	return res;
}
ll get(ll N, ll p) {
	ll res = 0;
	while (N) {
		res += N / p;
		N /= p;
	}
	return res;
}
int main() {
	cin >> N >> B;
	ll ans = 9e18;
	unordered_map<ll, ll> m = divisor(B);
	//cout << ans << endl;
	for (auto p : m) {
		ll a = p.first, b = p.second;
		ll res = get(N, a);
		ans = min(ans, res / b);
	}
	cout << ans << endl;
	return 0;
}
/*
1000000000000000000 1000000000000
1000000000000000000 2
*/

4.A. Ivan the Fool and the Probability Theory

  • 涂色,相邻的格子最多一个颜色和它不一样。求方案数。
  1. 如果第一列确定,后面的全部可以确定。
  2. 第一列有两个相邻颜色一样的方块儿,这四行就可以确定颜色。
  3. 因此,只需查看第一列涂色的方案数有多少,记为 g n g_n gn.
  4. 当然,第一行有连续颜色的时候,计算方法是一样的。而且,3中所说的只要有两个竖着的相邻的颜色一样,那么整幅 N ∗ M N * M NM 的图中都不会出现横着两个相邻的颜色相同的情况(画一下就知道)。所以,记这个为 g m g_m gm.
  5. 那么答案就是 g m + g n − 2 g_m + g_n - 2 gm+gn2,减去的是正好没有任何相邻的点颜色相同的情况。会重复计算两次(想一下就明白)。
  6. 最后,如何计算 g n g_n gn 呢?假设现在排到第 i 位。首先,我们假设第 i 位和第 i - 1 位颜色相同,那么,如果第 i - 2 位为0,那么后面跟的是11;如果第 i - 2 位为1,那么后面跟的00,显然,这个答案为 g i − 2 g_{i-2} gi2,因为对于 i - 2 长度的序列,最后一位也就0和1两种情况嘛。接着,对于第 i 位和第 i - 1 位颜色不同的情况,我们发现,这个和 g n − 1 g_{n-1} gn1 是一样的,因为此时只要确定第 i - 1 位,第 i 位就跟着确定了。因此 g n = g n − 1 + g n − 2 . g_n = g_{n - 1} + g_{n - 2}. gn=gn1+gn2.
  7. 然后,手算一下, g 1 = 2 , g 2 = 4 g_1 = 2, g_2 = 4 g1=2,g2=4,然后最后答案就出来了。
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 100010;
typedef long long ll;
const ll mod = 1e9 + 7;
ll g[maxn] = { 2LL, 2LL };
int main() {
	for (int i = 2; i <= 100000; i++) g[i] = (g[i - 1] + g[i - 2]) % mod;
	ll N, M;
	cin >> N >> M;
	cout << ((g[N] + g[M] - 2 + mod) % mod) << endl;
	return 0;
}

5.D. New Year and the Permutation Concatenation

  • 把 n 的全排列按照字典序排成一个序列,问有多少种长度为 n 的子列,使得他们仍为一个n 的全排列。
  • 我们这样想,能够凑成一样串,如果不是那 n 的全排列本身构成的串的话,一定是两块拼出来的。那么,我们先枚举是由前 i 个和后 n - i 个串拼起来的。那么,当两个相邻的串前 i 项相等的话,就可以拼出来这种样子的。
  • 那么,先选出前 i 个前缀 C n i C_{n}^{i} Cni,然后,这 i 个前缀有 i ! i! i! 种排列方式。此时此刻,是 C n i ∗ i ! = n ! ( n − i ) ! C_{n}^{i} * i! = \frac{n!}{(n-i)!} Cnii!=(ni)!n! 种排列方式。
  • 最后,对于每一种排列方式,有多少种合并呢?由于新序列前 i 个数字是确定的,而前 ( n − i ) ! (n-i)! (ni)! 项正好是剩余的 ( n − i ) ! (n-i)! (ni)! 的所有排列,并且每一个排列都是一个合法解。但是,要减去第一个。因为第一个数的前 i 个数字是无法和前面合并的。
  • 因此,最后答案是: n ! + ∑ i = 1 n − 1 n ! ( n − i ) ! ( ( n − i ) ! − 1 ) n! + \sum\limits_{i=1}^{n-1}\frac{n!}{(n-i)!}((n-i)!-1) n!+i=1n1(ni)!n!((ni)!1).
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
const int maxn = 1000010;
ll fact[maxn] = { 1 }, infact[maxn] = { 1 };
ll mod_pow(ll x, ll n) {
	ll res = 1;
	while (n) {
		if (n & 1) res = res * x % mod;
		x = x * x % mod;
		n >>= 1;
	}
	return res;
}

int main() {
	for (ll i = 1; i <= 1000000; i++) {
		fact[i] = fact[i - 1] * i % mod;
		infact[i] = infact[i - 1] * mod_pow(i, mod - 2) % mod;
	}
	ll N;
	cin >> N;
	ll ans = fact[N];
	for (ll i = 1; i < N; i++) {
		ans = (ans + fact[N] * infact[N - i] % mod * ((fact[N - i] - 1 + mod) % mod)) % mod;
	}
	cout << ans << endl;
	return 0;
}
/*
1 1
2 2
3 9
4 56
5 395
6 3084
7 26621
8 253280
9 2642391
10 30052700
*/

1800

1.D. Same GCDs

  • 题意:给定 a, m, 寻找满足 0 ≤ x ≤ m − 1 0\le x \le m - 1 0xm1 g c d ( x , m ) = g c d ( a , m ) gcd(x, m) = gcd(a, m) gcd(x,m)=gcd(a,m) 的数量。
  1. 首先,我们知道, g c d ( a , b ) = g c d ( a % b , b ) gcd(a, b) = gcd(a \% b, b) gcd(a,b)=gcd(a%b,b).
  2. x ∈ [ 0 , m − 1 ] x \in [0, m - 1] x[0,m1]时, x % m x \% m x%m 会得到 [ 0 , m − 1 ] [0, m-1] [0,m1] 这 m 个数字。因此原问题等价为:令 g c d ( a , m ) = d gcd(a, m) = d gcd(a,m)=d. 当 x ′ ∈ [ 0 , m − 1 ] x'\in [0, m-1] x[0,m1] 时,求 g c d ( x ′ , m ) = d gcd(x', m) = d gcd(x,m)=d 的个数。
  3. 可以进一步等价。令 x ′ ′ = x ′ / d , m ′ = m / d x'' = x' / d, m' = m / d x=x/d,m=m/d,则原问题等价为:令当 x ′ ′ ∈ [ 0 , ( m − 1 ) / d ] x'' \in [0, (m - 1) / d] x[0,(m1)/d],即 [ 0 , m ′ − 1 ] [0, m' - 1] [0,m1] 时, g c d ( x ′ ′ , m ′ ) = 1 gcd(x'', m') = 1 gcd(x,m)=1
  4. 再稍微转化一下,因为 x < m,因此 m / d m / d m/d 一定是大于1的,因此,原问题其实等价找 x ′ ∈ [ 1 , m ′ ] x' \in [1, m'] x[1,m] 中与 m’ 互素的数的个数,及欧拉函数 φ ( m / d ) \varphi(m/d) φ(m/d).
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b) {
	if (b == 0) return a;
	return gcd(b, a % b);
}
ll phi(ll n) {
	ll res = n;
	for (ll i = 2; i <= n / i; i++) {
		if (n % i == 0) {
			res = res / i * (i - 1);
			while (n % i == 0) n /= i;
		}
	}
	if (n != 1) res = res / n * (n - 1);
	return res;
}
int main() {
	int T;
	cin >> T;
	while (T--) {
		ll a, m;
		cin >> a >> m;
		ll d = gcd(a, m);
		cout << phi(m / d) << endl;
	}
	return 0;
}

2.C. Primitive Primes

  • 本原多项式 (primitive polynomial):设 f ( x ) = a 0 + a 1 x + a 2 x 2 + . . . + a n x n f(x) = a_0 + a_1x + a_2x^2 + ... + a_nx^n f(x)=a0+a1x+a2x2+...+anxn 是唯一分解整环 D D D 上的多项式,如果 g c d ( a 0 , a 1 , . . . , a n ) = 1 gcd(a_0, a_1, ..., a_n) = 1 gcd(a0,a1,...,an)=1,则称 f ( x ) f(x) f(x) 为 D上的一个本原多项式。
  • 首先,我们从头开始遍历数组 a 和数组 b ,找到第一项不能整除 p 的位置,分别记为 x 和 y ,那么这个题目的答案就是 x + y 了
  • 因为 a 0 a_0 a0 ~ a x − 1 a_{x - 1} ax1 b 0 b_0 b0 ~ b y − 1 b_{y - 1} by1 都是可以被 p 整除的,而能够组成 x + y 的系数,无非就是 ( a 0 ∗ b x + y + a 1 ∗ b x + y − 1 + . . . + a x − 1 ∗ b y + 1 ) + a x ∗ b y + ( a x + 1 ∗ b y − 1 . . . + a x + y − 1 ∗ b 1 + a x + y ∗ b 0 ) (a_0* b_{x + y} + a_1 * b_{x + y - 1} + ... + a_{x-1} * b_{y+1})+ a_x * b_y+(a_{x + 1}*b_{y - 1}...+ a_{x + y - 1} * b_1 + a_{x + y} * b_0) (a0bx+y+a1bx+y1+...+ax1by+1)+axby+(ax+1by1...+ax+y1b1+ax+yb0),可以发现,上面的式子由 x + y + 1 项组成,前 x 项都包括了 a[ 0 ] ~ a[ x - 1 ],而后 y 项都包括了b[ 0 ] ~ b[ y - 1 ],换句话说,这些项相乘后仍然是 p 的倍数,自然也能被 p 整除,而只有最中间的 a[ x ] * b[ y ] 都无法整除 p 所以自然第 x + y 项无法整除 p 了.
  • 看到这个结论后,发现异常简单呀!
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
ll N, M, P;
const int maxn = 1000010;
ll a[maxn], b[maxn];
int main() {
	scanf("%lld%lld%lld", &N, &M, &P);
	for (int i = 0; i < N; i++) scanf("%lld", &a[i]);
	for (int j = 0; j < M; j++) scanf("%lld", &b[j]);
	int id1, id2;
	for (id1 = 0; id1 < N; id1++) {
		if (a[id1] % P != 0) break;
	}
	for (id2 = 0; id2 < M; id2++) {
		if (b[id2] % P != 0) break;
	}
	printf("%d\n", id1 + id2);
	return 0;
}

3.A. Enlarge GCD

  • 题意:给 n 个数字,去掉最少的数字,使得他们的 gcd 比之前的大。
  • 这题,真是不会写。但是,我发现,似乎涉及 gcd 的题,经常会把所有数字先除以 gcd.
  1. 我们先把每个数字都除以 gcd,然后开始枚举所有素数p,看看有哪些数可以整除 p,这些数组成的集合大小的最大值就是所求值。但是枚举素数 p 又会超时。因为很多并不能被 p 整除,但会被判断一次。
  2. 这里有一个很重要的地方。我平时用的素数线性筛叫欧拉线性筛法。这个筛法的核心是每次筛掉的都是这个数的最小素因数。我们将这个最小素因数记下来,之前的 st 数组改为保存最大素因数。这样,我们其实可以直接对每个数进行素数分解。详情看代码。
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 300010, maxa = 15000010;
int st[maxa], prime[maxa], cnt;
void sieve(int N) {
	for (int i = 2; i <= N; i++) {
		if (!st[i]) st[i] = prime[cnt++] = i;
		for (int j = 0; prime[j] <= N / i; j++) {
			st[prime[j] * i] = prime[j];
			if (i % prime[j] == 0) break;
		}
	}
}
int N, a[maxn];
int res[maxa];
int gcd(int a, int b) {
	if (b == 0) return a;
	return gcd(b, a % b);
}
int main() {
	scanf("%d", &N);
	int d = 0;
	sieve(maxa - 1);
	
	for (int i = 1; i <= N; i++) {
		scanf("%d", &a[i]);
		//这里取了个巧。当 d == 0时,gcd(0, a) = a.
		d = gcd(d, a[i]);
	}
	for (int i = 1; i <= N; i++) {
		for (int j = a[i] / d; j > 1;) {
			//找到 j 的最小质因数
			int x = st[j];
			// 更新答案
			res[x]++;
			//把j一直除以最小质因数,直到不能被 x 整除为止。
			while (j % x == 0) {
				j /= x;
			}
		}
	}
	int ans = 0;
	for (int i = 0; i < cnt; i++) {
	//仍然小心这个地方!不要把 i 当成 prime[i] !!!
		int p = prime[i];
		ans = max(ans, res[p]);
	}
	printf("%d\n", ans ? N - ans : -1);
	return 0;
}

4.C. Beautiful Numbers

  • 题意:这个题有点玄乎呀,问长度为 n 的数,只有 a 和 b 组成,并且各位数字之和也是由 a 和 b 组成。问方案数。
  • 欸,其实很简单的。因为每位数字相加,数字的顺序就不重要了。从 0 到 n n n 枚举 a a a 的个数,然后b的个数就是 n − i n - i ni. 判断一下合法方案就可以了, a n s = ∑ i = 0 n C n i , μ ( i )   i s   t r u e . ans = \sum\limits_{i=0}^{n}C_{n}^{i}, \mu(i)\ is\ true. ans=i=0nCni,μ(i) is true.
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const int maxn = 1000010;
ll fact[maxn], infact[maxn];
ll mod_pow(ll x, ll n) {
	ll res = 1;
	while (n) {
		if (n & 1) res = res * x % mod;
		x = x * x % mod;
		n >>= 1;
	}
	return res;
}
ll a, b, n;
bool mu(ll x) {
	ll res = x * a + (n - x) * b;
	while (res) {
		ll d = res % 10;
		if (d != a && d != b) return false;
		res /= 10;
	}
	return true;
}
ll C(ll m, ll n) {
	return fact[m] * infact[n] % mod * infact[m - n] % mod;
}
int main() {
	cin >> a >> b >> n;
	ll ans = 0;
	fact[0] = infact[0] = 1;
	for (ll i = 1; i <= n; i++) {
		fact[i] = fact[i - 1] * i % mod;
		infact[i] = infact[i - 1] * mod_pow(i, mod - 2) % mod;
	}
	for (ll i = 0; i <= n; i++) {
		if (mu(i)) {
			ans = (ans + C(n, i)) % mod;
		}
	}
	cout << ans << endl;
	return 0;
}

5.D. Rescue Nibel!

  • 题意:给出 n n n 盏灯的点亮和熄灭时间,要挑选出 k k k 盏灯,使得他们存在同时亮的一段时间。问方案数。
  • 我一直在想,怎么样统计穿过这个时间点的时间。其实就是把所有的时间点放在一个数组里面,然后排序就行。每到一个右端点,就 cnt–。否则就 cnt++,然后进入选择的环节,强制选择现在这个时间点开始的灯,然后加上 C c n t − 1 k − 1 C_{cnt-1}^{k-1} Ccnt1k1 即可。
  • 为什么要强制选择最后一个呢?这是因为防止重复。这样子,就是选择当前开始的一个灯,以及其他通过此时间的 k - 1 个灯(当然可能包含从当前时间点开始的起面已经数过的灯)。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 300010;
const ll mod = 998244353;

typedef pair<int, bool> P;
vector<P> v;
ll N, K, fact[maxn] = { 1 }, infact[maxn] = { 1 };
ll mod_pow(ll x, ll n) {
	ll res = 1;
	while (n) {
		if (n & 1) res = res * x % mod;
		x = x * x % mod;
		n >>= 1;
	}
	return res;
}
ll C(ll m, ll n) {
	if (n > m) return 0;
	return fact[m] * infact[n] % mod * infact[m - n] % mod;
}

int main() {
	scanf("%d%d", &N, &K);
	for (ll i = 1; i <= N; i++) {
		fact[i] = fact[i - 1] * i % mod;
		infact[i] = infact[i - 1] * mod_pow(i, mod - 2) % mod;
	}
	for (int i = 0; i < N; i++) {
		int l, r;
		scanf("%d%d", &l, &r);
		v.push_back(P(l, false));
		v.push_back(P(r, true));
	}
	sort(v.begin(), v.end());
	int cnt = 0;
	ll ans = 0;
	for (int i = 0; i < 2 * N; i++) {
		if (v[i].second) cnt--;
		else {
			cnt++;
			if (cnt < K) continue;
			ans = (ans + C((ll)cnt - 1, K - 1)) % mod;
		}
	}
	printf("%lld\n", ans);
	return 0;
}

1900

1.H. Hate That You Know Me

  • 2 n 2^n 2n 取模,其实只是截取后面的 n 位,因此 ( a ⊕ b )   m o d   2 n = ( a   m o d   2 n )   ⊕ ( b   m o d   2 n ) (a \oplus b)\ mod\ 2^n = (a\ mod\ 2^n) \ \oplus (b\ mod\ 2^n) (ab) mod 2n=(a mod 2n) (b mod 2n). 先求出两边的值,最后再异或。
  • 小心除法!这个不满足封闭性。
  • 合适的分类讨论可以大大减少讨论数量。
#include<iostream>
#include<algorithm>
using namespace std;
typedef unsigned long long ll;
ll A, B, N;
ll sum1(ll x) {
	if (x & 1) return (x + 1) / 2 * x;
	else return x / 2 * (x + 1);
}
ll sum2(ll x) {
	ll a[3] = { x, x + 1, 2 * x + 1 };
	int id1 = 0, id2 = 0;
	for (int i = 0; i < 3; i++) {
		if (a[i] % 2 == 0) id1 = i;
		if (a[i] % 3 == 0) id2 = i;
	}
	a[id1] /= 2, a[id2] /= 3;
	return a[0] * a[1] * a[2];
}
ll sum3(ll x) {
	if (x & 1) return ((x + 1) / 2) * ((x + 1) / 2) * x * x;
	else return (x / 2) * (x / 2) * (x + 1) * (x + 1);
}
ll sumup(ll l, ll r, ll k) {
	if (k == 0) {
		return r - l + 1;
	}
	else if (k == 1) {
		return sum1(r) - sum1(l - 1);
	}
	else if (k == 2) {
		return sum2(r) - sum2(l - 1);
	}
	else if (k == 3) {
		return sum3(r) - sum3(l - 1);
	}
}
int main() {
	cin >> A >> B >> N;
	ll res1 = 0, res2 = 0;
	for (ll l = 1, r; l <= N; l = r + 1) {
		r = min(N / (N / l), N);
		res1 += (N / l) * sumup(l, r, A);
		res2 += (N / l) * sumup(l, r, B);
	}
	cout << (res1 ^ res2) << endl;
	return 0;
}

2.B. Nauuo and Circle

  • 首先考虑树形dp,由于在圆周上,一个子树肯定是一段连续的区间,那么也就是对于以u为根的来说,他的几个儿子之间是可以随意交换位置的,而且保证不会相交,所以加上u指向父亲那条边,所有边都可以任意交换位置,也就是u这个点度数的阶乘种交换位置,我们再乘上每棵子树的答案树,由于我们固定以1为根,而1又有n个位置可以选择,所以最后dp[1]*n就是答案。注意根结点没有父亲,所以只有儿子之间任意交换即可。
  • 我只想说,妙啊!
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 200010;
typedef long long ll;
const ll mod = 998244353;
ll deg[maxn], fact[maxn] = { 1 };
int N;
int main() {
	scanf("%d", &N);
	for (ll i = 1; i <= N; i++) {
		fact[i] = fact[i - 1] * i % mod;
	}
	for (int i = 1; i < N; i++) {
		int a, b;
		scanf("%d%d", &a, &b);
		deg[a]++, deg[b]++;
	}
	ll ans = N;
	for (int i = 1; i <= N; i++) {
		ans = ans * fact[deg[i]] % mod;
	}
	printf("%lld\n", ans);
	return 0;
}

3.A. As Fast As Possible

  • 其实很容易猜到,因为答案取决于最后一个到达的人的时间,所以所有人在车上的时间应该是一样长。
  • 车辆载人次数 c n t = ⌈ n k ⌉ cnt = \lceil\frac{n}{k}\rceil cnt=kn. 最后一组上车的地点 x = 2 ∗ v 1 ∗ l ∗ ( c n t − 1 ) ( 2 ∗ c n t − 1 ) v 1 + v 2 x = \frac{2 * v_1 * l * (cnt - 1)}{(2 * cnt - 1)v_1 + v_2} x=(2cnt1)v1+v22v1l(cnt1)
  • a n s = x v 1 + l − x v 2 ans = \frac{x}{v_1} + \frac{l - x}{v_2} ans=v1x+v2lx.
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
ll n, l, v1, v2, k;
int main() {
	cin >> n >> l >> v1 >> v2 >> k;
	ll cnt = (n + k - 1) / k;
	double x = 2.0 * v1 * l * (cnt - 1) / ((double)(2 * cnt - 1) * v1 + v2);
	double ans = x / v1 + (l - x) / v2;
	printf("%.18f\n", ans);
	return 0;
}

4.B. Recover the String

  • 设 0 有 a 个,1 有 b 个,那么, a 00 = C a 2 , a 11 = C b 2 a_{00} = C_{a}^{2}, a_{11} = C_{b}^{2} a00=Ca2,a11=Cb2,我们发现,将一个序列中的 ‘10’ 变成 ‘01’ 之后, a 01 a_{01} a01 比之前多了1, a 10 a_{10} a10 比之前少了1.
  • 因此, a 01 + a 10 a_{01} + a_{10} a01+a10 是不变的,因此前a个全是0,后b个全是1,那么此时 a 01 + a 10 = a ∗ b a_{01} + a_{10} = a * b a01+a10=ab,那么,如果不相等的话,说明是无解的。
  • a 01 = 0 , a 10 = 0 a_{01} = 0, a_{10} = 0 a01=0,a10=0,则 a 和 b 至少有1个为0
  • 否则, a ≥ 1 , b ≥ 1 a\ge 1, b\ge 1 a1,b1.
  • 假设一开始的序列从左往右是b个1,a个0,则设 x 1 = a ∗ b − a 10 b x_1 = \frac{a*b - a_{10}}{b} x1=baba10 x 2 = ( a ∗ b − a 10 )   m o d   b x_2 = (a*b - a_{10})\ mod\ b x2=(aba10) mod b。则序列是: x 1 x_1 x1个0, b − x 2 b - x_2 bx2个1,1个0, x 2 x_2 x2个1,以及 a − x 1 − 1 a - x_1 - 1 ax11 个0.
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
ll a00, a01, a10, a11, a, b;
ll binary_find(ll x) {
	ll lb = 1, ub = x + 3;
	while (ub - lb > 1) {
		ll mid = (lb + ub) / 2;
		if (mid * (mid - 1) / 2 <= x) lb = mid;
		else ub = mid;
	}
	if (lb * (lb - 1) / 2 != x) return -1;
	return lb;
}
int main() {
	scanf("%lld%lld%lld%lld", &a00, &a01, &a10, &a11);
	if (a01 == 0 && a10 == 0) {
		if (a00 == 0 && a11 == 0) a = 0, b = 0;
		else if (a00 == 0) a = 0, b = binary_find(a11);
		else if (a11 == 0) a = binary_find(a00), b = 0;
		else{
			printf("Impossible\n");
			return 0;
		}
		if (a == -1 || b == -1) {
			printf("Impossible\n");
			return 0;
		}
		for (int i = 0; i < a; i++) printf("0");
		for (int i = 0; i < b; i++) printf("1");
		if (a == 0 && b == 0) printf("0");
		printf("\n");
	}
	else {
		a = binary_find(a00), b = binary_find(a11);
		if (a == -1 || b == -1 || a * b != a01 + a10) {
			printf("Impossible\n");
		}
		else {
			ll x1 = (a * b - a10) / b, x2 = (a * b - a10) % b;
			for (int i = 0; i < x1; i++) printf("0");
			a -= x1;
			for (int i = 0; i < b - x2; i++) printf("1");
			b = x2;
			if (x2 != 0 && a > 0) {
				printf("0");
				a--;
			}
			for (int i = 0; i < b; i++) printf("1");
			for (int i = 0; i < a; i++) printf("0");
			
			printf("\n");
		}
	}
	return 0;
}

5.B. Petya and Divisors

  • 题意:对该个询问,是这个x的约数却不是上面y个x的约数的约数个数是多少。
  • 就是记录一下这个约数最后出现的位置就可以了呀。

法一:

  • O ( n 3 2 ) O(n^{\frac{3}{2}}) O(n23),但只用了1M左右的空间
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 100010;
int divisor_id[maxn];
int prime[maxn], cnt, st[maxn];
vector<int> divisor(int N) {
	vector<int> res;
	for (int i = 1; i <= N / i; i++) {
		if (N % i == 0) {
			res.push_back(i);
			if (i != N / i) res.push_back(N / i);
		}
	}
	return res;
}
int main() {
	int N;
	scanf("%d", &N);
	for (int i = 1; i <= N; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		vector<int> v = divisor(x);
		int ans = 0;
		for (int u : v) {
			if (divisor_id[u] < i - y) ans++;
			divisor_id[u] = i;
		}
		printf("%d\n", ans);
	}
	return 0;
}

法二:

  • O ( n ∗ l o g n ) O(n*logn) O(nlogn),但用了10M内存。也不多呀。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 100010;
int divisor_id[maxn];
int prime[maxn], cnt, st[maxn];
vector<int> divisor[maxn];
void divisor_sieve(int N) {
	for (int i = 1; i <= N; i++) {
		for (int j = i; j <= N; j += i) {
			divisor[j].push_back(i);
		}
	}
}
int main() {
	int N;
	scanf("%d", &N);
	divisor_sieve(100000);
	for (int i = 1; i <= N; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		vector<int>& v = divisor[x];
		int ans = 0;
		for (int u : v) {
			if (divisor_id[u] < i - y) ans++;
			divisor_id[u] = i;
		}
		printf("%d\n", ans);
	}
	return 0;
}

2000

1.D. Two Divisors

  • 对于一个数a,先把它做质因数分解,如果质因数只有一个,那么一定是无解的。否则,一定有解,且解的形式是先把所有质因数任意化分成两个非空集合。两个非空集合元素相乘,就是 d 1 d_1 d1 d 2 d_2 d2.
  • 那么,这个一定是有解的。首先,我们直到,对于a的任意一个质因数p,如果 ( d 1 + d 2 )   m o d   p ≠ 0 (d_1+d_2)\ mod\ p \ne 0 (d1+d2) mod p=0,那么显然 g c d ( d 1 + d 2 , a ) = 1 gcd(d_1 + d_2, a) = 1 gcd(d1+d2,a)=1。那么,对于任意一个p,一定是 d 1 d_1 d1 或者是 d 2 d_2 d2 有一个模p等于0,一个模p不等于0。因此 ( d 1 + d 2 )   m o d   p ≠ 0 (d_1+d_2)\ mod\ p \ne 0 (d1+d2) mod p=0.
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 10000010;
int prime[maxn], cnt, st[maxn];
int gcd(int a, int b) {
	if (b == 0) return a;
	return gcd(b, a % b);
}
void sieve(int N) {
	for (int i = 2; i <= N; i++) {
		if (!st[i]) prime[cnt++] = st[i] = i;
		for (int j = 0; prime[j] <= N / i; j++) {
			st[prime[j] * i] = prime[j];
			if (i % prime[j] == 0) break;
		}
	}
}
vector<int> divisor(int N) {
	vector<int> res;
	for (int i = N; i > 1; ) {
		//找到 j 的最小质因数
		int x = st[i];
		res.push_back(x);
		//把j一直除以最小质因数,直到不能被 x 整除为止。
		while (i % x == 0) {
			i /= x;
		}
	}
	return res;
}
int ans1[500010], ans2[500010];
int main() {
	sieve(maxn - 1);
	int N;
	scanf("%d", &N);
	for (int i = 0; i < N; i++) {
		int x;
		scanf("%d", &x);
		vector<int> v = divisor(x);
		if (v.size() == 1) {
			ans1[i] = -1, ans2[i] = -1;
			continue;
		}
		ans1[i] = v[0], ans2[i] = 1;
		for (int j = 1; j < v.size(); j++) {
			ans2[i] *= v[j];
		}
	}
	for (int i = 0; i < N; i++) printf("%d%c", ans1[i], i + 1 == N ? '\n' : ' ');
	for (int i = 0; i < N; i++) printf("%d%c", ans2[i], i + 1 == N ? '\n' : ' ');
	return 0;
}

2.D. Three Integers

  • 因为数据非常小,然后也找不到什么很好的规律,如果用bfs暴搜的话绝对不行,时间和空间都会爆掉。因此,我们用这种方式暴搜,就是从 1 到 2a 枚举 A 的取值,不可能超过2a,因为这样子还不如直接把a变成1。
  • 然后,从1到2b枚举B,但是这次是枚举 A 的倍数;最后一步,计算C的值,看看C离B的倍数的那个更近一些,很简单,就是 ⌊ c B ⌋ ∗ B , ( ⌊ c B ⌋ + 1 ) ∗ B \lfloor\frac{c}{B}\rfloor*B, (\lfloor\frac{c}{B}\rfloor+1)*B BcB,(Bc+1)B.
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
using namespace std;
int main() {
	int T;
	cin >> T;
	while (T--) {
		int a, b, c;
		cin >> a >> b >> c;
		int ans = 1e9;
		int A0, B0, C0;
		for (int A = 1; A <= 2 * a; A++) {
			for (int B = A; B <= 2 * b; B += A) {
				int C1 = c / B * B, C2 = (c / B + 1) * B;
				int res = abs(a - A) + abs(b - B) + min(abs(c - C1), abs(c - C2));
				if (ans > res) {
					ans = res;
					A0 = A, B0 = B;
					if (abs(c - C1) < abs(c - C2)) C0 = C1;
					else C0 = C2;
				}
			}
		}
		cout << ans << endl;
		printf("%d %d %d\n", A0, B0, C0);
	}
	return 0;
}

3.B. The least round way

  • 给一个矩阵,找到一条道路,使得沿途数字乘积的 trailing zero 最少。
  • 其实我想了一下,末尾零和因子2和5的数量有关,最终答案应该是 m i n ( c n t 2 , c n t 5 ) . min(cnt_2, cnt_5). min(cnt2,cnt5).
  • 如果矩阵元素有0的话,那么答案最大就是0。然后我们把这个数字换成10,再按照全是正数的矩阵进行计算就行。
  • 哎呀,突然明白了,因为2和5的加法是分开的呀,所以把2和5都分别走一遍,看看哪个之和是最小的。
  • 然后,代码其实有一点点难写。
#include<cstdio>
#include<algorithm>
#include<string>
using namespace std;
const int maxn = 1010;
typedef pair<int, int> P;
struct res {
	int cnt, px, py;
	char op;
	res(int c = 0, int x = 0, int y = 0, char o = 0) :cnt(c), px(x), py(y), op(o) {};
};
P m[maxn][maxn];
res f1[maxn][maxn], f2[maxn][maxn];
int N;
bool has_zero = false;
int x_zero, y_zero;
void dp() {
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			if (i == 1) {
				f1[i][j] = { f1[i][j - 1].cnt + m[i][j].first, i, j - 1, 'R' };
			}
			else if (j == 1) {
				f1[i][j] = { f1[i - 1][j].cnt + m[i][j].first, i - 1, j, 'D' };
			}
			else {
				if (f1[i - 1][j].cnt < f1[i][j - 1].cnt) {
					f1[i][j] = { f1[i - 1][j].cnt + m[i][j].first, i - 1, j, 'D' };
				}
				else {
					f1[i][j] = { f1[i][j - 1].cnt + m[i][j].first, i, j - 1, 'R' };
				}
			}
		}
	}

	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			if (i == 1) {
				f2[i][j] = { f2[i][j - 1].cnt + m[i][j].second, i, j - 1, 'R' };
			}
			else if (j == 1) {
				f2[i][j] = { f2[i - 1][j].cnt + m[i][j].second, i - 1, j, 'D' };
			}
			else {
				if (f2[i - 1][j].cnt < f2[i][j - 1].cnt) {
					f2[i][j] = { f2[i - 1][j].cnt + m[i][j].second, i - 1, j, 'D' };
				}
				else {
					f2[i][j] = { f2[i][j - 1].cnt + m[i][j].second, i, j - 1, 'R' };
				}
			}
		}
	}
}
void Print(res f[][maxn]) {
	printf("%d\n", f[N][N].cnt);
	string path("");
	for (res p = f[N][N];; p = f[p.px][p.py]) {
		path += p.op;
		if (p.px == 1 && p.py == 1) break;
	}
	reverse(path.begin(), path.end());
	printf("%s", path.c_str());
}
int main() {
	scanf("%d", &N);
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			int x;
			scanf("%d", &x);
			if (x == 0) {
				has_zero = true, x_zero = i, y_zero = j;
				x = 10;
			}
			while (x % 2 == 0) {
				m[i][j].first++;
				x /= 2;
			}
			while (x % 5 == 0) {
				m[i][j].second++;
				x /= 5;
			}
		}
	}
	
	dp();

	//for (int i = 1; i <= N; i++) {
	//	for (int j = 1; j <= N; j++) {
	//		printf("%d ", f2[i][j].cnt);
	//	}
	//	printf("\n");
	//}
	
	
	if (has_zero) {
		int ans = min(f1[N][N].cnt, f2[N][N].cnt);
		ans = min(1, ans);
		if (ans == 1) {
			printf("1\n");
			//别偷懒,画个图画个图,一画图就知道那里错了呀 :(
			for (int j = 1; j < y_zero; j++) printf("R");
			for (int i = 1; i < N; i++) printf("D");
			for (int j = y_zero; j < N; j++) printf("R");
		}
		else if (ans == f1[N][N].cnt) Print(f1);
		else Print(f2);
	}
	else {
		if (f1[N][N].cnt < f2[N][N].cnt) Print(f1);
		else Print(f2);
	}
	printf("\n");
	return 0;
}

/*
4
2 5 2 5
5 2 0 5
2 5 2 2
5 5 2 5
*/

4.A. Sticker Album

  • 这种较难的概率题,一般都是倒着推导的。我认为题解这个方法没有问题。
  • 可以设 E x E_x Ex 为填满剩余的 x 需要的卡包数的期望是多少。设 L = B − A + 1 L = B - A + 1 L=BA+1. 显然 E 0 = 0. E_0 = 0. E0=0.
    在这里插入图片描述
  • 当然,这个可以用滑动窗口去求,也可以用前缀和去求,不过前缀和小心浮点数误差。
  • 不过,当 A = 0 A = 0 A=0 时,我们发现 E x − A = E x E_{x-A} = E_x ExA=Ex,那么移项变化就好。
    在这里插入图片描述
  • 小心别化简前缀和的时候公式推错!
#include<cstdio>
#include<algorithm>
using namespace std;
int N, A, B;
const int maxn = 1000010;
double SumE[maxn], E[maxn];
int main() {
	scanf("%d%d%d", &N, &A, &B);
	double L = (double)B - A + 1;
	if (A == 0) {
		for (int i = 1; i <= N; i++) {
			if (i <= B) {
				E[i] = L / (L - 1) + 1.0 / (L - 1) * SumE[i - 1];
			}
			else {
				E[i] = L / (L - 1) + 1.0 / (L - 1) * (SumE[i - 1] - SumE[i - B - 1]);
			}
			SumE[i] = E[i] + SumE[i - 1];
		}
	}
	else {
		for (int i = 1; i <= N; i++) {
			if (i <= B) {
				E[i] = 1 + 1.0 / L * SumE[max(i - A, 0)];
			}
			else {
				E[i] = 1 + 1.0 / L * (SumE[i - A] - SumE[i - B - 1]);
			}
			SumE[i] = SumE[i - 1] + E[i];
		}
	}
	printf("%.10f\n", E[N]);
	return 0;
}

5.A. Easy Equation

  • 题意:You are given four positive integers x , y , z , k x, y, z, k x,y,z,k, please help little M calculate the number of equations x + y + z = k x + y + z = k x+y+z=k when 0 ≤ x ≤ a , 0 ≤ y ≤ b , 0 ≤ z ≤ c , 0 ≤ k ≤ d 0 ≤ x ≤ a, 0 ≤ y ≤ b, 0 ≤ z ≤ c, 0 ≤ k ≤ d 0xa,0yb,0zc,0kd .
  • 题解太巧妙了!我们先计算能够凑成 (x + y) 的有多少种。令 f ( i ) f(i) f(i) x + y = i x + y = i x+y=i 的方案数的差分数组,从0到a枚举 i,每次枚举一个 i,就把 f ( i ) f(i) f(i) ~ f ( i + b ) f(i + b) f(i+b) 都加1,这个用差分就可以。最开始 f 都是0嘛,所以这个差分数组也不需要初始化。
  • 同理,我们令 g ( i ) g(i) g(i) x + y + z = i x + y + z = i x+y+z=i 的方案数的差分数组,从 0 到 a + b a + b a+b 枚举 i 。此时,每次枚举到一个 i,我们就把 g ( i ) g(i) g(i) ~ g ( i + c ) g(i + c) g(i+c) 都加上 ∑ j = 0 i f ( j ) \sum\limits_{j = 0}^{i}f(j) j=0if(j). 最后,答案就是 ∑ i = 0 d ∑ j = 1 i g ( i ) \sum\limits_{i=0}^{d}\sum\limits_{j = 1}^{i}g(i) i=0dj=1ig(i).
  • 其实,仔细分析,我们不用考虑太多边界问题。
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 3000010;
ll f[maxn], g[maxn], A, B, C, D;
int main() {
	scanf("%lld%lld%lld%lld", &A, &B, &C, &D);
	//枚举第一个数字
	for (int i = 0; i <= A; i++) {
		f[i]++;
		f[i + B + 1]--;
	}
	for (int i = 1; i <= A + B; i++) f[i] += f[i - 1];
	//同样也是枚举第一个数字
	for (int i = 0; i <= A + B; i++) {
		g[i] += f[i];
		g[i + C + 1] -= f[i];
	}
	for (int i = 1; i <= A + B + C; i++) {
		g[i] += g[i - 1];
	}
	ll ans = 0;
	for (int i = 0; i <= D; i++) ans += g[i];
	printf("%lld\n", ans);
	return 0;
}
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值