牛客寒假训练营1
A-九小时九个人九扇门_2022牛客寒假算法基础集训营1 (nowcoder.com)
结论:一个数的数字根等于这个数对9取模的结果,特别地,取模得0则数字根为9
证明:
假设一个数为abcd, 那么:
abcd % 9 = (a * 1000 + b * 100 + c * 10 + d * 1) % 9
由取模的性质我们可以知道:
= ((a % 9) * (1000 % 9) + (b % 9) * (100 % 9) + (c % 9) * (10 % 9) + (d % 9) * (1 % 9)) % 9
= (a + b + c + d) % 9
第一眼没看出来是DP,以为是个组合数,没想到居然是一个01背包变种问题,太菜了太菜了
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 100010, mod = 998244353;
int t, n, m;
int dp[N][10]; // 状态表示f[i, j]: 考虑前i个人,数字根是j的方案数
int main(void)
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> m;
m %= 9;
dp[i][m]++;
for (int j = 0; j < 9; j++)
{
// 这一层数字根(j + m) % 9的方案数加上上一层数字根为j的方案数
dp[i][(j + m) % 9] = (dp[i][(j + m) % 9] + dp[i - 1][j]) % mod;
// 这一层的j加上上一层j的方案数
dp[i][j] = (dp[i][j] + dp[i - 1][j]) % mod;
}
}
for (int i = 1; i < 9; i++) cout << dp[n][i] << " ";
cout << dp[n][0] << endl ; // 9的情况单独输出
return 0;
}
B-炸鸡块君与FIFA22_2022牛客寒假算法基础集训营1 (nowcoder.com)
倍增预处理or线段树维护区间信息
先预处理出来初始分%3的各个情况下,从某局开始进行多少局的得分情况,然后利用二进制来修改答案即可
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 200010, M = 21; // 开21是因为2^21保证足够大
int t, n, m;
string c;
int f[3][N][M]; // f[k][i][j],初始分%3是k的情况下,从第i局开始,进行2^j局的得分
int main(void)
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m >> c;
// 预处理
for (int i = 0; i < n; i++)
{
if (c[i] == 'W') // 如果是赢,不管初始分多少,在第i局都是加分
f[0][i][0] = f[1][i][0] = f[2][i][0] = 1;
else if (c[i] == 'L') // 如果是输,初始分%3得0得情况是不扣分的,和平局情况一样都是0
f[1][i][0] = f[2][i][0] = -1;
}
for (int j = 1; (1 << j) <= n; j++) // 枚举从第i局开始进行2^j局
for (int i = 0; i + (1 << j) - 1 < n; i++) // 枚举i,注意不要越界
for (int k = 0; k < 3; k++) // 枚举初始分
// 状态转移可以这样理解,往后进行2^j可以看作先进行2^(j-1)局,再进行2^(j-1)局
// f[k][i][j - 1]表示前一半的得分
// f[(k + f[k][i][j - 1]) % 3][i + (1 << (j - 1))][j - 1]表示后一半的得分
f[k][i][j] = f[k][i][j - 1] + f[(k + f[k][i][j - 1]) % 3][i + (1 << (j - 1))][j - 1];
while (m--)
{
int l, r, s;
cin >> l >> r >> s;
l--, r--; // 因为我们下标是从0开始的,所以需要--
int len = r - l + 1, y = 0; // len表示区间长度,y是用来枚举二进制
while (l <= r)
{
if ((len >> y) & 1) // 寻找每一位上的1
{
s = s + f[s % 3][l][y]; // 修改分数
l = l + (1 << y); // 每次都进行2^y局
}
y++;
}
cout << s << endl ;
}
return 0;
}
C-Baby’s first attempt on CPU_2022牛客寒假算法基础集训营1 (nowcoder.com)
简单模拟
理解题意,模拟一下就可以了
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, x;
cin >> n;
vector<int> f(n); // f[i] 表示第i个语句的最终位置
f[0] = 1;
for (int i = 0; i < n; i++)
{
if (i) f[i] = f[i - 1] + 1;
for (int j = 1; j <= 3; j++)
{
cin >> x;
if (x) f[i] = max(f[i], f[i - j] + 4);
}
}
cout << f.back() - n << endl ; // 最后一条语句的位置减去初始的位置就是多插入的空语句数量
return 0;
}
D-牛牛做数论_2022牛客寒假算法基础集训营1 (nowcoder.com)
简单数论
结论:第一个问题的答案是最大连续质因子乘积,第二个问题的答案是最大质数
证明:
首先明确一个概念:任何数字都可以分解成质数的乘积,也就是质因子
所以第一个问题我们只需要找到一个数,它包含的质因子最多即可,也就是最大连续质因子乘积
第二个就比较容易了,因为质数的因子只有1和本身,所以它和其他数都肯定是互质的,所以答案是最大质数
#include<bits/stdc++.h>
using namespace std;
int t, n;
int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; // 这些质数乘积已经超过数据范围了
bool is_primes(int x)
{
if (x < 2) return false;
for (int i = 2; i <= x / i; i++)
if (x % i == 0) return false;
return true;
}
int main(void)
{
cin >> t;
while (t--)
{
cin >> n;
if (n == 1) {
cout << -1 << endl ;
continue;
}
long long x = 1;
for (int i = 0; x * primes[i] <= n; i++) // 找到最大连续质因子乘积
x *= primes[i];
cout << x << " ";
while (!is_primes(n)) n--; // 找到最大质数
cout << n << endl ;
}
return 0;
}
E-炸鸡块君的高中回忆_2022牛客寒假算法基础集训营1 (nowcoder.com)
推公式
结论: ( n + m − 3 ) / ( m − 1 ) ∗ 2 − 1 (n + m - 3) / (m - 1) * 2 - 1 (n+m−3)/(m−1)∗2−1
证明:
首先找规律:
进一次校门:m
进两次校门:m + m - 1
进三次校门:m + 2 * (m - 1)
···
进k次校门:m + (k - 1) * (m - 1)
依题意可得不等式:m + (k - 1) * (m - 1) >= n
两边同时减1:m - 1 + (k - 1) * (m - 1) >= n - 1
化简可得:k * (m - 1) >= n - 1
所以 k >= (n - 1) / (m - 1) 注意是上取整,保证全部都走完
最终公式就是: (n - 1 + (m - 1) - 1) / (m - 1) * 2 - 1
#include<bits/stdc++.h>
using namespace std;
int main(void)
{
int t;
cin >> t;
while (t--)
{
int n, m;
cin >> n >> m;
if (n == m) cout << 1 << endl ;
else if (m == 1) cout << -1 << endl ;
else cout << (n + m - 3) / (m - 1) * 2 - 1 << endl ;
}
return 0;
}
F-中位数切分_2022牛客寒假算法基础集训营1 (nowcoder.com)
思维题
结论:统计大于等于m的个数 c n t 1 cnt1 cnt1和小于m的个数 c n t 2 cnt2 cnt2,如果 c n t 1 − c n t 2 cnt1-cnt2 cnt1−cnt2小于等于0,则无解,反之则为答案
证明:
其实也很容易能理解:
最理想的情况肯定是一个区间的所有数字都大于等于m,这样中位数一定大于等于m,
但是这个情况我们肯定是把每个数字都单独分为一个区间,因为这样子可以分成最多个满足条件的区间
而最极限的情况呢,就是前半区间是小于m的,后半区间是大于等于m的,那么就可以看作是除了中位数外
左右两边的数字相互抵消了,因此也就是为什么答案是cnt1 - cnt2了
#include<bits/stdc++.h>
using namespace std;
int main(void)
{
int t;
cin >> t;
while (t--)
{
int n, m, cnt1 = 0, cnt2;
cin >> n >> m;
for (int i = 0; i < n; i++)
{
int x;
cin >> x;
if (x >= m) cnt1++;
}
cnt2 = n - cnt1;
if (cnt1 - cnt2 <= 0) cout << -1 << endl ;
else cout << cnt1 - cnt2 << endl ;
}
return 0;
}
G-ACM is all you need_2022牛客寒假算法基础集训营1 (nowcoder.com)
差分前缀和
做法:我们对于从 2 ≤ i ≤ n − 1 2 ≤i≤n-1 2≤i≤n−1的数求出满足条件的 b b b的范围 [ l , r ] [l, r] [l,r],然后将问题转换成给出 n − 2 n-2 n−2个区间,找出覆盖区域最多的最小点,很容易联想到差分
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int t, n, x;
int a[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> t;
while (t--)
{
cin >> n;
map<int, int> mp;
for(int i = 1; i <= n; i++) cin >> a[i];
int ans = 0;
for(int i = 2; i < n; i++)
{
if (a[i] == a[i - 1] || a[i] == a[i + 1]) continue;
if (a[i] < a[i - 1] && a[i] < a[i + 1])
{
ans++;
mp[(a[i] + min(a[i - 1], a[i + 1]) + 1) / 2] ++;
}
else if (a[i] > a[i - 1] && a[i] > a[i + 1])
{
mp[(a[i] + max(a[i - 1], a[i + 1])) / 2 + 1]--;
}
else {
mp[(a[i] + max(a[i - 1], a[i + 1]) + 1) / 2]++;
mp[(a[i] + min(a[i - 1], a[i + 1]))/ 2 + 1]--;
}
}
int res = 0, tot = 0;
for (auto i : mp)
{
tot += i.second;
res = max(res, tot);
}
cout << ans - res << endl ;
}
return 0;
}
H-牛牛看云_2022牛客寒假算法基础集训营1 (nowcoder.com)
思维题
看着 1 e 6 1e6 1e6数据范围很大,但是每个数都才 1 e 3 1e3 1e3而已,记录每个数字的个数,推出等价公式跑个双循环即可
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1010;
LL cnt[N];
int n, x;
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> x;
cnt[x]++;
}
LL res = 0;
for (int i = 0; i <= 1000; i++)
{
for (int j = i; j <= 1000; j++)
{
if (i == j) res += (cnt[i] + cnt[i] * (cnt[i] - 1) / 2) * abs(i + i - 1000);
else res += cnt[i] * cnt[j] * abs(i + j - 1000);
}
}
cout << res << endl ;
return 0;
}
I-B站与各唱各的_2022牛客寒假算法基础集训营1 (nowcoder.com)
期望+逆元
结论: m ∗ ( 2 n − 2 ) / 2 n m * (2^n - 2) / 2^n m∗(2n−2)/2n
因为句子和句子之间是没有办法影响的,所以只需要求出一个话的期望乘上数量即可
除法取模需要用到逆元,注意 1 e 9 + 7 1e9+7 1e9+7是一个质数,所以直接用快速幂求即可,不需要用拓展欧几里得
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
LL t, n, m;
LL qmi(LL a, LL b, LL p)
{
LL res = 1 % p;
while (b)
{
if (b & 1) res = res * a % p;
b >>= 1;
a = a * a % p;
}
return res;
}
int main(void)
{
cin >> t;
while (t--)
{
cin >> n >> m;
LL k = qmi(2, n, mod);
cout << (m * (k - 2 + mod) % mod * qmi(k, mod - 2, mod)) % mod << endl ;
}
return 0;
}
J-小朋友做游戏_2022牛客寒假算法基础集训营1 (nowcoder.com)
简单模拟
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
int main(void)
{
int t;
cin >> t;
while (t--)
{
int a, b, n, x;
vector<pair<int, int>> vec;
cin >> a >> b >> n;
for (int i = 0; i < a + b; i++)
{
cin >> x;
if (i < a) vec.push_back({x, 0});
else vec.push_back({x, 1});
}
if (a < (n + 1) / 2) cout << -1 << endl ;
else {
sort(vec.begin(), vec.end());
reverse(vec.begin(), vec.end());
int cnt = 0, res = 0, temp = n;
for (int i = 0; i < n; i++)
{
if (!vec[i].y) res += vec[i].x;
else if (cnt < temp / 2)
{
res += vec[i].x;
cnt++;
}
else n++;
}
cout << res << endl ;
}
}
return 0;
}
K-冒险公社_2022牛客寒假算法基础集训营1 (nowcoder.com)
DP问题
f [ i , j , k , l ] f[i, j,k,l] f[i,j,k,l] 表示考虑前 i i i个字符,分别放了 j , k , l ( j , k , l ∈ 0 , 1 , 2 ) j,k,l(j,k,l∈{0,1,2}) j,k,l(j,k,l∈0,1,2)时的最大绿岛数
优化成二维 f [ i , j ] f[i, j] f[i,j]就是用 j j j的三进制来表示 j , k , l j,k,l j,k,l
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = 27, INF = 1e6;
int dp[N][M], n;
char str[N];
int cnt(int x) {
int g = (x % 3 == 0) + ((x / 3) % 3 == 0) + ((x / 9) % 3 == 0);
int r = (x % 3 == 2) + ((x / 3) % 3 == 2) + ((x / 9) % 3 == 2);
return g - r;
}
int main()
{
cin >> n >> str + 1;
for(int i = 0; i < N; i++)
for(int j = 0; j < M; j++)
dp[i][j] = -INF;
for(int i = 0; i < M; i++) {
if(str[3] == 'G' && cnt(i) <= 0) continue;
if(str[3] == 'B' && cnt(i) != 0) continue;
if(str[3] == 'R' && cnt(i) >= 0) continue;
dp[3][i] = (i % 3 == 0) + ((i / 3) % 3 == 0) + ((i / 9) % 3 == 0);
}
for(int i = 4; i <= n; i++)
for(int j = 0; j < M; j++)
{
if(str[i] == 'G' && cnt(j) <= 0) continue;
if(str[i] == 'B' && cnt(j) != 0) continue;
if(str[i] == 'R' && cnt(j) >= 0) continue;
for(int k = 0; k < M; k++)
if(k % 3 == (j / 3) % 3 && (k / 3) % 3 == (j / 9) % 3)
dp[i][j] = max(dp[i][j], dp[i - 1][k] + (j % 3 == 0));
}
int ans = -INF;
for(int i = 0; i < M; i++)
ans = max(ans, dp[n][i]);
if (ans < 0) cout << -1 << endl ;
else cout << ans << endl ;
return 0;
}
L-牛牛学走路_2022牛客寒假算法基础集训营1 (nowcoder.com)
简单模拟
#include<bits/stdc++.h>
using namespace std;
int t, n, x, y;
double res;
string s;
int main(void)
{
cin >> t;
while (t--)
{
cin >> n >> s;
res = 0;
x = 0, y = 0;
for (int i = 0; i < s.size(); i++)
{
if (s[i] == 'U') y++;
else if (s[i] == 'D') y--;
else if (s[i] == 'R') x++;
else x--;
res = max(res, sqrt(x * x + y * y));
}
printf("%.12f\n", res);
}
return 0;
}