2022牛客寒假算法基础集训营1_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ
目录
A-九小时九个人九扇门
解题思路:注意数论中原根的性质:一个数的原根是该数对 9 取模的余数,余数为 0 时原根为 9。然后问题就转化成求不同数的组合之和对 9 取模分别为 0~9 的方案数。可以发现这是0/1背包的变形,可以将就可以表示成考虑前 i 个人的原根为 j 的方案数(要注意取模之后为 0 的表示原根为 9,其它的值与原根相同)。我们的状态转移方程就可以表示为
这样要求的答案就是 到
以及
(这里减1是因为
中包括所有人都不选上的情况)
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int mod = 998244353;
const int N = 100005;
int a[N];
int dp[N][9];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n;
cin >> n;
dp[0][0] = 1;
for(int i = 1; i <= n; i++){
cin >> a[i];
a[i] %= 9;
}
for(int i = 1; i <= n; i++){
for(int j = 0; j < 9; j++){
dp[i][(j + a[i]) % 9] = (dp[i][(j + a[i]) % 9] + dp[i - 1][j]) % mod;
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] - 1 << endl;
return 0;
}
B-炸鸡块君与FIFA22
解题思路:根据数据量可以发现暴力模拟一定会T,而又要求多个询问下区间内符合条件的值,这就可以用到倍增的思想。题中分数是 3 的整数倍时失败不掉分的另一种表述为起始分数在模 3 的意义下相等。定义表示初始分数为 k 的情况下经历
一段游戏后分数的变化量。根据倍增的思想进行预处理 st 表:先根据题目的要求预处理出 j = 0 的情况,然后按照转移方程
进行预处理。然后对于每次询问,从
跳 2 的若干次幂到
,跳的过程中根据分数模 3 的结果来访问 st 表中的值。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 200005;
int st[3][N][21];
char str[N];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n, q;
scanf("%d%d", &n, &q);
scanf("%s", str);
for(int i = 0; i < n; i++){
if(str[i] == 'W') st[0][i][0] = st[1][i][0] = st[2][i][0] = 1;
if(str[i] == 'L') st[1][i][0] = st[2][i][0] = -1;
}
for(int j = 1; (1 << j) <= n; j++){
for(int i = 0; i + (1 << j) - 1 < n; i++){
for(int k = 0; k < 3; k++){
st[k][i][j] = st[k][i][j - 1] + st[(k + st[k][i][j - 1]) % 3][i + (1 << (j - 1))][j - 1];
}
}
}
while(q--){
int l, r, s;
scanf("%d%d%d", &l, &r, &s);
int len = r - l + 1, i = 0;
while(l <= r){
if((len >> i) & 1){
s += st[s % 3][l - 1][i];
l += (1 << i);
}
i++;
}
printf("%d\n", s);
}
return 0;
}
C-Baby's first attempt on CPU
解题思路:题目大意是每两条先写后读相关的语句之间要有 3 行空语句,求要插入的最少空语句数。注意题目的定义,它表示第 i 行和第 i - j 行是否是相关的(为 1 表示相关,为 0 表示不相关)。由于不能直接改变原来的语句序号,有一种方法是将修改后的总语句数求出来减去原语句数即可。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 105;
int a[N];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n;
cin >> n;
for(int i = 1; i <= n; i++){
a[i] = a[i - 1] + 1;
for(int j = 0; j < 3; j++){
int x;
cin >> x;
if(x) a[i] = max(a[i], a[i - j - 1] + 4);
}
}
cout << a[n] - n << endl;
return 0;
}
D-牛牛做数论
解题思路:首先需要熟悉下面两个欧拉函数的定理:
(1)如果 p 是素数,那么。反之,如果 p 是正整数且满足
,那么 p 是素数;
(2)设为正整数 n 的素幂因子分解,那么
。
因此,
对于问题1,要想 H(x) 最小,那么根据,则素数的种数越多越好;
对于问题2,要想 H(x) 最大,那么根据,则这个数为素数且越大越好。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int p[] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
bool is_prime(int x){
for(int i = 2; i * i <= x; i++){
if(x % i == 0) return false;
}
return true;
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
if(n == 1) cout << -1 << endl;
else{
ll res = 1;
for(int i = 0; i < 9; i++){
if(res * p[i] > n) break;
res *= p[i];
}
while(!is_prime(n)) n--;
cout << res << ' ' << n << endl;
}
}
return 0;
}
E-炸鸡块君的高中回忆
解题思路:看上去就是找规律推公式的题目(很明显模拟会超时),公式为(注意特判 m = 1 时:n = 1 或 n > 1)
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
ll n, m;
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin >> t;
while(t--){
cin >> n >> m;
if(m == 1){
if(n == 1) cout << 1 << endl;
else cout << -1 << endl;
}
else{
cout << ((n - 1) / (m - 1) + ((n - 1) % (m - 1) != 0)) * 2 - 1 << endl;
}
}
return 0;
}
推不出公式怎么办?那就可以放弃了。
还有一种方法是二分答案,设进入的总次数为 t ,最后一次进入时的人数为 x ,那么一定有( x > m 说明第 t 次不是最后一次进入),且总用时为 2 * t - 1。这样就可以二分寻找合适的 t ,时间复杂度
就不会T了。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
ll n, m;
bool check(ll x){
if(n - (x - 1) * (m - 1) <= m) return true;
else return false;
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin >> t;
while(t--){
cin >> n >> m;
if(m == 1){
if(n == 1) cout << 1 << endl;
else cout << -1 << endl;
}
else{
ll l = 1, r = 1000000000;
while(l < r){
ll mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << 2 * r - 1 << endl;
}
}
return 0;
}
F-中位数切分
解题思路:多试几个例子就会发现,切分的段数与所给的序列顺序是无关的,仅与序列中大于等于所给定的数的个数有关(证明过程在官方题解中有)。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 100005;
int a[N];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
scanf("%d", &t);
while(t--){
int n, m, ans = 0;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
if(a[i] >= m) ans++;
else ans--;
}
if(ans <= 0) printf("-1\n");
else printf("%d\n", ans);
}
return 0;
}
G-ACM is all you need
解题思路:由于对整体作的变换,因此 +b 的操作可以忽略,即简化为
的变换,对于三个数出现的大小情况,我们可以分为三种:第一种是两边大中间小,即符合题目要求的样子;第二种是两边小中间大;第三种是两边的有大有小,中间的比小的大,比大的小。分别求出满足情况的 b 的区间,排序后寻找区间覆盖最多的一段即可。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
#include <vector>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 100005;
int f[N];
vector<pair<int, int> > vec;
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin >> t;
while(t--){
int n, now = 0;
cin >> n;
for(int i = 1; i <= n; i++) cin >> f[i];
for(int i = 2; i < n; i++){
if(f[i] < f[i - 1] && f[i] < f[i + 1]){
now++;
int x = (f[i] + min(f[i - 1], f[i + 1]) + 1) / 2;
vec.push_back({x, -1});
}
else if(f[i] > f[i - 1] && f[i] > f[i + 1]){
int x = (f[i] + max(f[i - 1], f[i + 1])) / 2 + 1;
vec.push_back({x, 1});
}
else if(f[i] > min(f[i - 1], f[i + 1]) && f[i] < max(f[i - 1], f[i + 1])){
int x = (f[i] + min(f[i - 1], f[i + 1])) / 2 + 1;
vec.push_back({x, 1});
x = (f[i] + max(f[i - 1], f[i + 1]) + 1) / 2;
vec.push_back({x, -1});
}
}
sort(vec.begin(), vec.end());
int pos = 0, i, j, ans = now, len = vec.size();
while(pos < len){
for(i = pos; i < len; i++){
if(vec[i].first != vec[pos].first) break;
now += vec[i].second;
}
ans = min(ans, now);
pos = i;
}
cout << ans << endl;
vec.clear();
}
return 0;
}
H-牛牛看云
解题思路:在输入数组时将每个数-500可以将题目的式子中的-1000给去掉,原题就变成求,然后将数组进行排序,每个数二分找它的相反数的位置,在该位置之前的数和该数相加一定小于(等于)0,在该位置之后的数和该数相加一定大于0,这样分两段分别取绝对值再相加就可以算出结果。这里二分可以用lower_bound()或者upper_bound(),总时间复杂度为
。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 1000005;
ll a[N], s[N];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
ll n, sum = 0;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
a[i] -= 500;
}
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; i++){
s[i] = s[i - 1] + a[i];
}
for(int i = 1; i <= n; i++){
if(a[i] < 0){
int pos = upper_bound(a + i, a + n + 1, -a[i]) - a - 1;
sum += abs(a[i] * (pos - i + 1) + (s[pos] - s[i - 1])) + abs(a[i] * (n - pos) + (s[n] - s[pos]));
}
else{
sum += a[i] * (n - i + 1) + s[n] - s[i - 1];
}
}
cout << sum << endl;
return 0;
}
I-B站与各唱各的
解题思路:可以发现每个句子之间是相互独立的,因此有,设每个人每句以
的概率决定唱或不唱,总的失败概率为
,由于 n 位up主足够聪明,因此要失败概率最小,即当
时,最小失败概率为
。那么成功概率就为
,期望为
。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
ll quick_pow(ll a, ll b){
ll res = 1;
while(b){
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll inv(ll x){
return quick_pow(x, mod - 2);
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
scanf("%d", &t);
while(t--){
ll n, m;
scanf("%lld%lld", &n, &m);
ll pn = quick_pow(2, n - 1);
printf("%lld\n", (pn - 1) % mod * inv(pn) % mod * m % mod);
}
return 0;
}
J-小朋友做游戏
解题思路:要求仅为两个闹腾的小朋友不能相邻,因此闹腾的小朋友的总数是不受 n 的约束的,只有安静的小朋友总数受约束,即时才能安排进行游戏。而又要求总幸福度最大,因此贪心从幸福度最大的小朋友开始选。(注意闹腾的小朋友最多只能选 n / 2 个)
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 10005;
int an[N], nao[N];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin >> t;
while(t--){
int a, b, n;
cin >> a >> b >> n;
for(int i = 0; i < a; i++) cin >> an[i];
for(int i = 0; i < b; i++) cin >> nao[i];
if(a < n - n / 2){
cout << -1 << endl;
continue;
}
sort(an, an + a);
sort(nao, nao + b);
int i = a - 1, j = b - 1, cnt = 0, cntj = 0;
ll sum = 0;
while(cnt < n){
if(cntj + 1 <= n / 2){
if(an[i] >= nao[j]) sum += an[i--];
else{
sum += nao[j--];
cntj++;
}
}
else sum += an[i--];
cnt++;
}
cout << sum << endl;
}
return 0;
}
K-冒险公社
解题思路:如果注意到这题是dp题的话就很简单了,设表示考虑到前
个岛,第
,
,
三个岛中分别放
,
,
岛时的最大绿岛数(
,其中0表示绿岛,1表示红岛,2表示黑岛),状态转移为
,其中
为第 i - 3 个岛的颜色。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
#include <set>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 100005;
int dp[N][3][3][3]; // 0表示绿岛,1表示红岛,2表示黑岛,后三维分别表示i,i - 1, i - 2岛的颜色
char s[N];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n;
cin >> n;
cin >> s + 1;
memset(dp, -0x3f, sizeof dp);
int ans = -1;
for(int i = 3; i <= n; i++){
for(int j = 0; j < 3; j++){
for(int k = 0; k < 3; k++){
for(int l = 0; l < 3; l++){
int c[3] = {0}; // c[0]表示绿岛数,c[1]表示红岛数,c[2]表示黑岛数
c[j]++, c[k]++, c[l]++;
if(s[i] == 'G' && c[0] <= c[1]) continue;
if(s[i] == 'R' && c[1] <= c[0]) continue;
if(s[i] == 'B' && c[0] != c[1]) continue;
if(i == 3) dp[i][j][k][l] = c[0];
if(i >= 4){
for(int t = 0; t < 3; t++){
dp[i][j][k][l] = max(dp[i][j][k][l], dp[i - 1][t][j][k] + (l == 0));
}
}
if(i == n) ans = max(ans, dp[i][j][k][l]);
}
}
}
}
cout << ans << endl;
return 0;
}
L-牛牛学走路
解题思路:签到题,取每走一步后与原点距离的最大值即可。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
string s;
cin >> s;
int len = s.size();
double x = 0, y = 0, dis = 0;
for(int i = 0; i < len; i++){
if(s[i] == 'U') y++;
if(s[i] == 'D') y--;
if(s[i] == 'L') x--;
if(s[i] == 'R') x++;
dis = max(dis, sqrt(x * x + y * y));
}
printf("%.12lf\n", dis);
}
return 0;
}