CF刷题——难度1700~2000
程序员文章站
2022-07-04 21:07:04
目前暂定,1600~2000,每个难度台阶写25道题,当然仍是math为主要训练方向。这篇文章记录1700的刷题。争取一周25道题,每天4道左右。也就是一周一个难度阶梯。加油(ง •_•)ง ????...
有种方式叫一下子拔高思维难度,做一些比你思维量极限还要略高一筹的题目,可以迅速上升。虽然我还是想有一个比较陡的缓冲区。于是,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} Cn−1k−2,就是 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} Cn−10+Cn−11+...+Cn−3n−3=2n−3.
- 注意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
- 涂色,相邻的格子最多一个颜色和它不一样。求方案数。
- 如果第一列确定,后面的全部可以确定。
- 第一列有两个相邻颜色一样的方块儿,这四行就可以确定颜色。
- 因此,只需查看第一列涂色的方案数有多少,记为 g n g_n gn.
- 当然,第一行有连续颜色的时候,计算方法是一样的。而且,3中所说的只要有两个竖着的相邻的颜色一样,那么整幅 N ∗ M N * M N∗M 的图中都不会出现横着两个相邻的颜色相同的情况(画一下就知道)。所以,记这个为 g m g_m gm.
- 那么答案就是 g m + g n − 2 g_m + g_n - 2 gm+gn−2,减去的是正好没有任何相邻的点颜色相同的情况。会重复计算两次(想一下就明白)。
- 最后,如何计算 g n g_n gn 呢?假设现在排到第 i 位。首先,我们假设第 i 位和第 i - 1 位颜色相同,那么,如果第 i - 2 位为0,那么后面跟的是11;如果第 i - 2 位为1,那么后面跟的00,显然,这个答案为 g i − 2 g_{i-2} gi−2,因为对于 i - 2 长度的序列,最后一位也就0和1两种情况嘛。接着,对于第 i 位和第 i - 1 位颜色不同的情况,我们发现,这个和 g n − 1 g_{n-1} gn−1 是一样的,因为此时只要确定第 i - 1 位,第 i 位就跟着确定了。因此 g n = g n − 1 + g n − 2 . g_n = g_{n - 1} + g_{n - 2}. gn=gn−1+gn−2.
- 然后,手算一下, 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)!} Cni∗i!=(n−i)!n! 种排列方式。
- 最后,对于每一种排列方式,有多少种合并呢?由于新序列前 i 个数字是确定的,而前 ( n − i ) ! (n-i)! (n−i)! 项正好是剩余的 ( n − i ) ! (n-i)! (n−i)! 的所有排列,并且每一个排列都是一个合法解。但是,要减去第一个。因为第一个数的前 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=1∑n−1(n−i)!n!((n−i)!−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 0≤x≤m−1 且 g c d ( x , m ) = g c d ( a , m ) gcd(x, m) = gcd(a, m) gcd(x,m)=gcd(a,m) 的数量。
- 首先,我们知道, 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).
- 当 x ∈ [ 0 , m − 1 ] x \in [0, m - 1] x∈[0,m−1]时, x % m x \% m x%m 会得到 [ 0 , m − 1 ] [0, m-1] [0,m−1] 这 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,m−1] 时,求 g c d ( x ′ , m ) = d gcd(x', m) = d gcd(x′,m)=d 的个数。
- 可以进一步等价。令 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,(m−1)/d],即 [ 0 , m ′ − 1 ] [0, m' - 1] [0,m′−1] 时, g c d ( x ′ ′ , m ′ ) = 1 gcd(x'', m') = 1 gcd(x′′,m′)=1
- 再稍微转化一下,因为 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} ax−1和 b 0 b_0 b0 ~ b y − 1 b_{y - 1} by−1 都是可以被 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) (a0∗bx+y+a1∗bx+y−1+...+ax−1∗by+1)+ax∗by+(ax+1∗by−1...+ax+y−1∗b1+ax+y∗b0),可以发现,上面的式子由 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.
- 我们先把每个数字都除以 gcd,然后开始枚举所有素数p,看看有哪些数可以整除 p,这些数组成的集合大小的最大值就是所求值。但是枚举素数 p 又会超时。因为很多并不能被 p 整除,但会被判断一次。
- 这里有一个很重要的地方。我平时用的素数线性筛叫欧拉线性筛法。这个筛法的核心是每次筛掉的都是这个数的最小素因数。我们将这个最小素因数记下来,之前的 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 n−i. 判断一下合法方案就可以了, 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=0∑nCni,μ(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} Ccnt−1k−1 即可。
- 为什么要强制选择最后一个呢?这是因为防止重复。这样子,就是选择当前开始的一个灯,以及其他通过此时间的 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) (a⊕b) 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=(2∗cnt−1)v1+v22∗v1∗l∗(cnt−1)
- a n s = x v 1 + l − x v 2 ans = \frac{x}{v_1} + \frac{l - x}{v_2} ans=v1x+v2l−x.
#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=a∗b,那么,如果不相等的话,说明是无解的。
- 若 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 a≥1,b≥1.
- 假设一开始的序列从左往右是b个1,a个0,则设 x 1 = a ∗ b − a 10 b x_1 = \frac{a*b - a_{10}}{b} x1=ba∗b−a10, x 2 = ( a ∗ b − a 10 ) m o d b x_2 = (a*b - a_{10})\ mod\ b x2=(a∗b−a10) mod b。则序列是: x 1 x_1 x1个0, b − x 2 b - x_2 b−x2个1,1个0, x 2 x_2 x2个1,以及 a − x 1 − 1 a - x_1 - 1 a−x1−1 个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(n∗logn),但用了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 ⌊Bc⌋∗B,(⌊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=B−A+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
Ex−A=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 0≤x≤a,0≤y≤b,0≤z≤c,0≤k≤d .
- 题解太巧妙了!我们先计算能够凑成 (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=0∑if(j). 最后,答案就是 ∑ i = 0 d ∑ j = 1 i g ( i ) \sum\limits_{i=0}^{d}\sum\limits_{j = 1}^{i}g(i) i=0∑dj=1∑ig(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;
}
本文地址:https://blog.csdn.net/qq_45812711/article/details/109551343