2022牛客寒假算法基础集训营4_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ
目录
A-R
解题思路:将字符串按 P 分隔成多个子字符串,每个子字符串单独计算,这样就不用考虑 P 的影响,提前预处理好每个子串的 R 的数量(可以用前缀和处理)和每个字符之后最近的 P 的位置,然后对于每个子串枚举左端点二分右端点,将方案数累加后即为答案,时间复杂度。
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;
char s[N];
int sum[N], posp[N];
int n, k;
void init(){
for(int i = 1; i <= n; i++){
sum[i] = sum[i - 1] + (s[i] == 'R');
}
posp[n + 1] = -1;
for(int i = n; i >= 1; i--){
posp[i] = s[i] == 'P' ? i : posp[i + 1];
}
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> n >> k;
cin >> s + 1;
init();
ll ans = 0;
for(int i = 1; i <= n; i++){
if(s[i] != 'P'){
int l = i;
int r = posp[i] == -1 ? n : posp[i] - 1;
if(sum[r] - sum[l - 1] >= k){
int pos = lower_bound(sum + 1, sum + n + 1, sum[l - 1] + k) - sum;
ans += r - pos + 1;
}
}
}
cout << ans << endl;
return 0;
}
B-进制
解题思路:单点修改,区间查询,自然可以使用线段树来解决。对于 k 进制来说,设一个数某位的左边的权值为 x,右边的权值为 y,那么组成的数的值为,其中
表示右边的位数。比如左边为123,右边为456,在8进制的情况下,组合成的数就为
。为了选择一种进制使组合成的数最小,那么就需要查找给定区间的最大数字,就是要用线段树查找区间的最大值。然后还需要一个不同进制的线段树来维护每段区间的值,这样得到要选的进制后,选择对应进制的线段树查找区间的值。注意修改某个值的时候需要将该值会影响的区间都进行处理。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <cctype>
#include <utility>
#define mid (l + r >> 1)
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 100005;
string s;
struct node{ // 线段树结构
int l, r;
ll val; // 存储区间最大值
}tree[N << 2];
struct jznode{ // 进制线段树结构
int l, r;
ll res; // 存储区间答案
}jztree[N << 2][11];
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;
}
void sgt_build(int i, int l, int r){ // 线段树建立
tree[i].l = l;
tree[i].r = r;
if(l == r){
tree[i].val = s[l - 1] - '0';
return;
}
sgt_build(i << 1, l, mid);
sgt_build(i << 1 | 1, mid + 1, r);
tree[i].val = max(tree[i << 1].val, tree[i << 1 | 1].val);
}
void jzsgt_build(int i, int l, int r, int k){ // 进制线段树建立
jztree[i][k].l = l;
jztree[i][k].r = r;
if(l == r){
jztree[i][k].res = s[l - 1] - '0';
return;
}
jzsgt_build(i << 1, l, mid, k);
jzsgt_build(i << 1 | 1, mid + 1, r, k);
int len = jztree[i << 1 | 1][k].r - jztree[i << 1 | 1][k].l + 1;
jztree[i][k].res = (jztree[i << 1][k].res * quick_pow(k, len) % mod + jztree[i << 1 | 1][k].res) % mod;
}
void sgt_modify(int i, int x, int y){ // 线段树单点修改
int l = tree[i].l, r = tree[i].r;
if(l == x && r == x){
tree[i].val = y;
return;
}
if(x <= mid) sgt_modify(i << 1, x, y);
else sgt_modify(i << 1 | 1, x, y);
tree[i].val = max(tree[i << 1].val, tree[i << 1 | 1].val);
}
ll sgt_query(int i, int x, int y){ // 线段树区间查询
ll ans = 0;
int l = tree[i].l, r = tree[i].r;
if(l >= x && r <= y) return tree[i].val;
if(x <= mid) ans = max(ans, sgt_query(i << 1, x, y));
if(y > mid) ans = max(ans, sgt_query(i << 1 | 1, x, y));
return ans;
}
void jzsgt_modify(int i, int x, int y, int k){ // 进制线段树单点修改
int l = jztree[i][k].l, r = jztree[i][k].r;
if(l == x && r == x){
jztree[i][k].res = y;
return;
}
if(x <= mid) jzsgt_modify(i << 1, x, y, k);
else jzsgt_modify(i << 1 | 1, x, y, k);
int len = jztree[i << 1 | 1][k].r - jztree[i << 1 | 1][k].l + 1;
jztree[i][k].res = (jztree[i << 1][k].res * quick_pow(k, len) % mod + jztree[i << 1 | 1][k].res) % mod;
}
ll jzsgt_query(int i, int x, int y, int k){ // 进制线段树区间查询
ll res1 = 0, res2 = 0;
int l = jztree[i][k].l, r = jztree[i][k].r;
if(l >= x && r <= y) return jztree[i][k].res % mod;
l = tree[i].l, r = tree[i].r;
if(x <= mid) res1 = jzsgt_query(i << 1, x, y, k);
if(y > mid){
res2 = jzsgt_query(i << 1 | 1, x, y, k);
ll len = min(y, jztree[i << 1 | 1][k].r) - mid;
return (res1 * quick_pow(k, len) + res2) % mod;
}
else return res1;
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n, m;
cin >> n >> m;
cin >> s;
sgt_build(1, 1, n);
for(int i = 2; i <= 10; i++){
jzsgt_build(1, 1, n, i);
}
for(int i = 1; i <= m; i++){
int op, x, y;
cin >> op >> x >> y;
if(op == 1){
sgt_modify(1, x, y);
for(int i = 2; i <= 10; i++) jzsgt_modify(1, x, y, i);
}
if(op == 2){
int val = sgt_query(1, x, y) + 1;
if(val == 1) val++;
cout << jzsgt_query(1, x, y, val) % mod << endl;
}
}
return 0;
}
C-蓝彗星
解题思路:将蓝慧星和红彗星存在的时间用两个差分数组存储起来,如果某个时刻蓝彗星存在而红彗星不存在,那么答案就加上1,差分数组的累加到某一点就是某一时间点是否出现对应的彗星(大于0则说明有出现,反之则说明没出现)。注意由于存在时间和彗星开始时刻最大均为100000,因此差分数组需要开到 2 * N。总时间复杂度为。
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 bt[2 * N], rt[2 * N];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n, t;
cin >> n >> t;
string s;
cin >> s;
int maxa = -1;
for(int i = 0; i < n; i++){
cin >> a[i];
if(a[i] > maxa) maxa = a[i];
}
for(int i = 0; i < n; i++){
if(s[i] == 'B'){
bt[a[i]]++;
bt[a[i] + t]--;
}
if(s[i] == 'R'){
rt[a[i]]++;
rt[a[i] + t]--;
}
}
int b = 0, r = 0, ans = 0;
for(int i = 1; i <= maxa + t; i++){
b += bt[i];
r += rt[i];
if(b > 0 && r <= 0) ans++;
}
cout << ans << endl;
return 0;
}
D-雪色光晕
解题思路:计算几何问题,求点到折线的最短距离。可以转换成点到折线的每一段线段的最小值的总的最小值。那么点到线段的距离最小值有两种情况,一种是过点作线段的垂线,垂足在线段上,那么最短距离就是垂线段距离;另一种是垂足不在线段上,最短距离是到线段两端点距离的最小值。垂足是否在线段上可以用点和线段两端点构成的两个角是否为钝角判断,垂线段长度可以用三角形面积公式求得,S 可以用海伦公式
,其中
。
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 double eps = 1e-8;
const double pi = 3.141592653589793;
using namespace std;
//二维点类
struct Point{
double x, y;
Point(double x = 0,double y = 0):x(x), y(y){}
};
//求两点之间的直线距离
double dis(Point a, Point b){
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
//三点三角形面积
double area(Point A, Point B, Point C){
double a = dis(B, C), b = dis(A, C), c = dis(A, B);
double p = (a + b + c) / 2;
return sqrt(p * (p - a) * (p - b) * (p - c));
}
//判断角ABC是否为钝角
bool judge(Point A, Point B, Point C){
double a = dis(B, C), b = dis(A, C), c = dis(A, B);
return b * b > a * a + c * c;
}
//求点C到线段AB的距离
double PointToSegDist(Point A, Point B, Point C){
double d1 = dis(A, C), d2 = dis(B, C);
if(judge(C, A, B) || judge(C, B, A)) return min(d1, d2);
double s = area(A, B, C);
double d3 = 2 * s / dis(A, B);
return min(min(d1, d2), d3);
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n;
cin >> n;
double x0, y0, x, y;
cin >> x0 >> y0 >> x >> y;
Point red(x0, y0);
Point guo(x, y);
double ans = 1e16;
for(int i = 0; i < n; i++){
double xi, yi;
cin >> xi >> yi;
Point endred(red.x + xi, red.y + yi);
ans = min(ans, PointToSegDist(red, endred, guo));
red = endred;
}
printf("%.8lf\n", ans);
return 0;
}
E-真假签到题
解题思路:通过举例可以发现对于任意一个数 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 main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
ll x;
cin >> x;
cout << x << endl;
return 0;
}
F-小红的记谱法
解题思路:根据题意进行模拟,用一个变量判断当前位是高 n 个 8 度还是低 n 个 8 度,然后对应输出即可。
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);
string s;
cin >> s;
int len = s.size();
int cnt = 0;
for(int i = 0; i < len; i++){
if(s[i] == '>') cnt++;
else if(s[i] == '<') cnt--;
else{
if(s[i] == 'C') cout << 1;
if(s[i] == 'D') cout << 2;
if(s[i] == 'E') cout << 3;
if(s[i] == 'F') cout << 4;
if(s[i] == 'G') cout << 5;
if(s[i] == 'A') cout << 6;
if(s[i] == 'B') cout << 7;
if(cnt > 0){
for(int i = 0; i < cnt; i++){
cout << '*';
}
}
if(cnt < 0){
for(int i = cnt; i < 0; i++){
cout << '.';
}
}
}
}
return 0;
}
G-子序列权值乘积
解题思路:考虑数组中每个数作为最小值和最大值对答案的贡献,先对数组进行排序,假设子序列中
为最小值,那么凡是在
之前的元素均不能选,在
之后的每个元素都有选和不选两种可能,即可能情况为
种,那么答案就得乘上
,作为最大值也同理。因此答案就为
。但由于指数
和
是很大的一个数,无法得到值后再与
做快速幂,由于
一定和
互质,因此可以用欧拉降幂的方法,即
。
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;
const int N = 200005;
ll a[N];
ll quick_pow(ll a, ll b, ll p){
ll res = 1;
while(b){
if(b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
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++) cin >> a[i];
sort(a + 1, a + n + 1);
ll x = 1, y = 1;
for(int i = 1; i <= n; i++){
x = x * quick_pow(a[i], quick_pow(2, i - 1, mod - 1), mod) % mod;
y = y * quick_pow(a[i], quick_pow(2, n - i, mod - 1), mod) % mod;
}
cout << x * y % mod << endl;
return 0;
}
H-真真真真真签到题
解题思路:小紫要尽可能离小红近,小红要尽可能里小紫远,那么小紫只能取正方体中心,因为只要小紫不取中心,那么小红总能取到一个顶点使得二者距离大于体对角线的一半。因此最终小紫一定站在正方体中心,小红一定站在正方体顶点上,二者的距离为体对角线的一半。
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);
double x;
cin >> x;
double a = sqrt((double)4 / 3) * x;
printf("%.4lf\n", a * a * a);
return 0;
}
I-爆炸的符卡洋洋洒洒
解题思路:简单背包问题。设表示考虑到第
张符卡消耗
为
的最大威力。那么当前状态是由两种状态转移而来——用当前符卡和不用当前符卡,即
和
,得到状态转移方程
。可以先预处理单独取每一张符卡的消耗存入 dp 数组中,然后只要前一个状态不为 0,说明是有方案可以达到前一个状态,那么就可以更新当前状态,反之说明没有方案能达到前一个状态,那么当前状态就也无法更新。最后根据
是否为 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 = 1005, K = 1005;
ll a[N], b[N], dp[N][K];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n, k;
cin >> n >> k;
for(int i = 1; i <= n; i++){
cin >> a[i] >> b[i];
}
for(int i = 1; i <= n; i++){
dp[i][a[i] % k] = b[i];
}
for(int i = 1; i <= n; i++){
for(int j = 0; j < k; j++){
if(dp[i - 1][(j - a[i] % k + k) % k] != 0){
dp[i][j] = max(dp[i][j], dp[i - 1][(j - a[i] % k + k) % k] + b[i]);
}
if(dp[i - 1][j] != 0){
dp[i][j] = max(dp[i][j], dp[i - 1][j]);
}
}
}
if(dp[n][0] == 0) cout << -1 << endl;
else cout << dp[n][0] << endl;
return 0;
}
J-区间合数的最小公倍数
解题思路:由于每个数可以分解为若干个素因子相乘,即,那么两个数的最小公倍数为两个数中每个素因子的最高次幂,同理可以推广到多个数,那么只需要将
中每个合数分解,然后记录每个素因子出现的最高次幂,最后用快速幂累乘即可。
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;
const int N = 30005;
int p[N];
bool isprime[N];
void init(){
for(int i = 2; i <= N; i++){
isprime[i] = true;
for(int j = 2; j <= i / j; j++){
if(i % j == 0){
isprime[i] = false;
break;
}
}
}
}
void divide(int i){
for(int j = 2; j <= i / 2; j++){
if(!isprime[j]) continue;
int x = i;
if(x % j == 0){
int cnt = 0;
while(x % j == 0){
x /= j;
cnt++;
}
p[j] = max(p[j], cnt);
}
}
}
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;
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int l, r;
cin >> l >> r;
init();
for(int i = l; i <= r; i++){
if(!isprime[i]) divide(i);
}
ll ans = 1;
for(int i = 2; i <= r; i++){
if(p[i]) ans = ans * quick_pow(i, p[i]) % mod;
}
cout << (ans == 1 ? -1 : ans) << endl;
return 0;
}
K-小红的真真假假签到题题
解题思路:由于 x 为 y 的二进制下的子串且 y 为 x 的倍数,考虑二进制思想,一个数每左移一位,这个数就为原来的两倍,那么可以将 x 左移若干位后得到 y,考虑到还有个条件为 y 的二进制表示中 1 的个数要大于 x,对于任意一个数,其加上本身仍为该数的倍数,因此为了使 1 的个数增加,最后加上 x 即可。综合以上分析,可以发现最小的符合条件的 y 是将 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 main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int x;
cin >> x;
int t = x, cnt = 0;
while(t){
cnt++;
t >>= 1;
}
cout << ((ll)x << cnt) + x << endl;
return 0;
}
L-在这冷漠的世界里光光哭哭
解题思路:待补
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 = 80005;
char s[N];
ll dp1[N][27], dp2[N][27 * 27], dp3[N][27 * 27];
vector<int> p[1005];
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n, q;
cin >> n >> q >> s + 1;
for(int i = 0; i < 26; i++) p[i].push_back(0);
for(int i = 1; i <= n; i++){
int u = p[s[i] - 'a'].back();
p[s[i] - 'a'].push_back(i);
for(int j = 0; j < 26; j++){
dp1[i][j] = dp1[i - 1][j] + (s[i] == j + 'a');
for(int k = 0; k < 26; k++){
dp2[i][j * 26 + k] = dp2[i - 1][j * 26 + k] + (k == s[i] - 'a') * dp1[i - 1][j];
dp3[i][j * 26 + k] = dp3[u][j * 26 + k] + dp2[i - 1][j * 26 + k];
}
}
}
while(q--){
int l, r;
cin >> l >> r >> s;
int a = (s[1] - 'a') * 26 + s[2] - 'a';
int b = (s[0] - 'a') * 26 + s[1] - 'a';
int c = s[2] - 'a';
int num = dp1[r][c] - dp1[l - 1][c];
int u = upper_bound(p[c].begin(), p[c].end(), r) - p[c].begin() - 1;
int v = lower_bound(p[c].begin(), p[c].end(), l) - p[c].begin() - 1;
ll ans = dp3[p[c][u]][b] - dp3[p[c][v]][b] - num * dp2[l - 1][b] - dp1[l - 1][s[0] - 'a'] * (dp2[r][a] - dp2[l - 1][a] - num * dp1[l - 1][s[1] - 'a']);
cout << ans << endl;
}
return 0;
}