牛客小白月赛#26 题解
A-牛牛爱学习 #二分答案 #贪心
题目链接
题意
给定\(n\)(\(\leq 1e6\))本书,每本书知识值为a[i]
。如果同一天连续读k
本书,获得知识力为a[i]-k+1
。看书不需按顺序,且一本书只能看一次(即某一天看过这本书后,以后都不能再看了),一天之内不一定要将所有书全都看了。现要求最少多少天才能获得大于等于m
点知识点,若无法获得则输出-1
。
分析
假设最坏情况下,一天只看一本书,即需要n
天看完,由此获得的知识力一定是最大的,但我们试探发现,如果在某一天看两本书,由此再累计一下知识力,有可能仍能够大于m
,而此时天数为n-1
。因而观察到答案的单调性,试着二分可能的天数。
如何确实试探的天数x
能否满足题意?利用贪心思想,先将所有书降序排序,再将前x
本书分摊到x
天,比较下当前累计知识力是否大于等于m
,如果不满足,再从书堆中选择x
本书继续分摊.....只要当前累计知识力大于等于m
,即能说明x
天满足题意,可继续往下二分。
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <stack>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 5;
int n, m;
int a[MAXN];
bool Judge(int dd){
int k = 1, cnt = 1;
ll sum = 0; //注意long long,被卡了qwq
for (int i = 1; i <= n; i++){ //将1-dd天,对每天的第k本书排好
if(a[i] - k + 1 <= 0)
break; //注意,不能直接返回false,不一定要将所有书都看完!
sum += (ll)(a[i] - k + 1);
if(i % dd == 0)
k++;
}
if(sum >= (ll)m) return true;
else return false;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
sort(a + 1, a + 1 + n, greater<int>());
int lo = 1, hi = n + 1;
bool f = false;
while(lo < hi){
int mid = (lo + hi) >> 1;
if(Judge(mid)){
hi = mid;
f = true;//说明之前存在一个mid使得条件满足
}
else
lo = mid + 1;
}
printf("%d\n", f ? lo : -1);
}
C-牛牛种花 #离散化 #树状数组 #离线处理
题目链接
题意
给定\(n\)(\(\leq 1e5\))朵花,需要在无限大矩阵中,将第i
朵花种到(\(xi, yi\))(\(10^{-9} \leq xi,yi,ai,bi \leq 10^9\))位置上。然后有\(m\)(\(\leq 1e5\))次询问,每次给定(ai, bi)
,询问在\(x \leq ai\) && \(y\leq bi\)下种了多少朵花。同一个地方可以种多朵花。
分析
借鉴了官方题解的思路,离散化思路值得学习。将给定的种花位置以及询问的种花位置进行离散化。
如何用树状数组维护数量?我们利用树状数组维护一个维度的信息(代码中维护的是y轴信息)。我们先将所有位置进行排序(先排x坐标,再排y坐标)。由于题目要求的是\(“\leq”\),接下来利用循环(即x坐标逐渐递增,相当于时间线),每一次循环迭代即更新覆盖y轴的信息,其中的迭代既包含了种花插入,又包含查询。最后按询问编号,离线输出答案。
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <stack>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
const int MAXN = 2e5 + 5;
const int MOD = 1e4 + 7;
struct Node{
int x, y, id;
bool operator < (const struct Node& b) const {
if(x == b.x) return y < b.y;
else return x < b.x;
}
} a[MAXN];
int n, m, tree[MAXN], pos[MAXN], ans[MAXN];//树状数组维护花在y坐标下数量
int lowbit(int x){ return x & (-x); }
void addDot(int x){
for(; x <= n + m; x += lowbit(x)) tree[x] ++;
} //注意,是n+m啊!!!
int findLine(int x){
int ans = 0;
for (; x > 0; x -= lowbit(x)) ans += tree[x];
return ans;
}
int main(){
scanf("%d%d", &n, &m);
for (int i = 1; i <= n + m; i++){
scanf("%d%d", &a[i].x, &a[i].y);
pos[i] = a[i].y;
a[i].id = 0;
if(i > n) a[i].id = i - n; //i>n时用id记录该位置坐标所属的问题编号
}
sort(a + 1, a + n + m + 1); //对所有坐标升序排序
sort(pos + 1, pos + n + m + 1); //对所有坐标中的y坐标升序排序,用于去重离散化
int len = unique(pos + 1, pos + n + m + 1) - pos - 1;
for (int i = 1; i <= n + m; i++){
int idx = lower_bound(pos + 1, pos + 1 + len, a[i].y) - pos;
if(a[i].id == 0) addDot(idx); //id==0就种花
else ans[a[i].id] = findLine(idx); //查询
}
for (int i = 1; i <= m; i++) printf("%d\n", ans[i]);
return 0;
}
D-失忆药水 #二分图
题目链接
题意
图中,a存在一条有向边指向b,说明a知道b的秘密。初始情况下,每个人都知道n-1
个人的秘密。一瓶失忆药水可将任意两点间存在的所有边去除掉。现要求最少要多少瓶药水,能使得图中不存在奇数长度的圈。
分析
前置芝士:二分图性质
由题意可知,二分图不存在长度为奇数的圈。于是我们可将n个顶点任意划分为两个集合,每个集合中的所有顶点之间不存在任何一条边相连,而不同集合的顶点存在边相连。
既然初始情况为完全图(共有\(n*(n-1)/2\)条无向边),二分图最多边的情况即为,一个集合中所有顶点与另一集合所有顶点都有边相连(即\(n/2*(n-n/2)\)条边)
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
ll n;
int main(){
while(cin >> n){
cout << 1ll * n * (n - 1) / 2 - 1ll * (n / 2) * (n - n / 2) << '\n';
}
return 0;
}
E-牛牛走迷宫 #广搜 #贪心
题目链接
题意
01
迷宫中,0
代表该位置可走,1
表示存在障碍。现要从(1,1)
起点走到终点(n,m)
。若可以走到终点,优先走路径最短的,同时走字典序最小的路径(D
表示向下,L
表示向左,R
表示向右,U
表示向上)。现要你求出路径长度,并输出用字母表示方向的路径。
分析
使用宽搜即可,宽搜过程中第一个遇到终点的路径,其长度一定是最短的。行进时注意方向,显然决策方向时,应先考虑D
方向,再依次考虑L
方向,R
方向,U
方向。
参考了官方题解,每个顶点结构体中都存有一字符串,实时记录到达该顶点的方向顺序。
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
using namespace std;
typedef long long ll;
const int MAXN = 505;
int n, m, G[MAXN][MAXN];
int di[5] = { 0, 1, 0, 0, -1};
int dj[5] = { 0, 0, -1, 1, 0};
typedef struct Node {
int cx, cy; string str;
} Node;
char str[MAXN][MAXN];
bool vis[MAXN][MAXN];
void bfs(int cx, int cy) {
queue<Node> myque;
vis[1][1] = true;
myque.push({ cx, cy, ""});
while (!myque.empty()) {
Node cur = myque.front();
myque.pop();
if (cur.cx == n && cur.cy == m) {
cout << cur.str.length() << '\n';
cout << cur.str << '\n';
return;
} //终点(边界条件)要放在外面判断,不要在决策方向时才判断,否则会发生莫名其妙的超时
for (int t = 1; t <= 4; t++) {
Node v = cur;
v.cx += di[t]; v.cy += dj[t];
if (v.cx <= 0 || v.cx > n || v.cy <= 0 || v.cy > m || G[v.cx][v.cy] || vis[v.cx][v.cy]) continue;
if (t == 1) v.str.append("D");
else if (t == 2) v.str.append("L");
else if (t == 3) v.str.append("R");
else if (t == 4) v.str.append("U");
vis[v.cx][v.cy] = true;
myque.push(v);
}
}
printf("-1\n");
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%s", str[i]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
G[i][j] = str[i][j - 1] - '0';
bfs(1, 1);
return 0;
}
G-牛牛爱几何
题意
给出正方形边长,计算阴影面积
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
typedef long long ll;
typedef long double ldd;
const int MAXN = 1e5;
const double PI = acos(-1);
int n, q;
int main(){
while ((scanf("%d", &n)) != EOF){
double ans = 0.25 * 2 * PI * n * n - 1.0 * n * n; //n*n前面一定要加1.0,否则出锅!
printf("%.6f\n", ans);
}
return 0;
}
H-保卫家园 #STL #区间重复覆盖
题目链接
题意
已知法兰不死队最多能有k
人。有n
个人想加入,他们每人都有一个期望入伍时间s
和退役时间t
。入伍时间表示这个人如果要入伍只能在s
时刻入伍。退役时间代表这个人可以在t
时刻后退伍(退伍时间要严格大于t )。队伍必须一直保持达到最大编制的状态,即队伍达到最大编制时候,队伍里有人可以退役的话,若无人接替这个人的位置就不能退役。问最少有多少人没有进入法兰不死队的经历。 同一时刻,允许多个人一起入伍或者退伍,该人是否入伍是你来决定的,即就算没达到最多编制人数k,到了某人的入伍时间,你可以选择不让他入伍。
分析
摘至官方题解
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <set>
using namespace std;
typedef long long ll;
const int MAXN = 1e6+5;
struct Node{
int lo, hi;
bool operator < (const Node& b){
if(lo == b.lo) return hi < b.hi;
else return lo < b.lo;
}
} a[MAXN];
multiset<int> myset;
int main(){
int n, k; scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++) scanf("%d%d", &a[i].lo, &a[i].hi);
sort(a+1, a+1+n);
int ans = 0;
for (int i = 1; i <= n; i++){ //枚举所有以i为起点的线段
while(!myset.empty() && *myset.begin() < a[i].lo) //先将在集合中的终点<当前点i起点都弹出集合
myset.erase(myset.begin());
myset.insert(a[i].hi); //存入该区间的终点
while(myset.size() > k){
myset.erase(--myset.end()); //弹出终点久的,也许能够在未来填上更多的区间
ans++; //说明当前队列中终点久的区间不适合
}
}
printf("%d\n", ans);
}
I-恶魔果实 #深搜
题目链接
题意
每个恶魔果实给予改变数字的能力,可以把某个数字a变成某个数字b,现有一个数字x,现吃完这n个恶魔果实后,求出可由数字x变成的数字种类数量,需要取模\(1e4+7\)。每个恶魔果实的能力可重复使用,也可不用,存在相同能力的恶魔果实。
分析
从每一种数字出发进行深搜,最后根据给定数字进行统计,具体见代码注释吧
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <stack>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 5;
const int MOD = 1e4 + 7;
int n, m, x;
bool trans[11][11], vis[11];
ll sum[11];
void dfs(int cur, int fa){
vis[cur] = true;
sum[fa]++; //统计由fa变成的数字种类数量
for(int i = 0; i <= 9; i++)
if(trans[cur][i] && !vis[i])
dfs(i, fa);
}
int main(){
scanf("%d%d", &x, &m);
for (int i = 1, u, v; i <= m; i++){
scanf("%d%d", &u, &v);
trans[u][v] = true; //说明可以转化
}
for (int i = 0; i <= 9; i++){ //每一种数字出发
for (int j = 0; j <= 9; j++) vis[j] = false;
dfs(i, i);
}
ll ans = 1;
while(x != 0){
int cur = x % 10; //分解数字x的每一数位
ans = (ans * sum[cur]) % MOD;
x /= 10;
}
printf("%lld\n", ans);
}
J-牛牛喜欢字符串 #模拟 #贪心
题目链接
题意
现有一长度\(n\)的字符串(仅包含小写字母),现将该字符串,每隔k个就分出来一个子串,比如[1,k]为第一个子串,[k+1,2k]为第二个、[2k+1,3k]为第三个.....(保证\(n\) Mod \(k==0\)) 。现想要把这些子串都变成一样(即每一子串均相同)。可选任意一个子串的任意一个字符进行更改,求出最少要进行的操作。
分析
与LeetCode 1566题有点类似。分段统计,最后累加。
#include <string>
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 1e6 + 5;
int n, q, k;
string str;
int vis[MAXN][30];
int main(){
scanf("%d%d", &n, &k);
cin >> str;
int sum = 0, len = n / k;
for(int i = 0; i < k; i++){ //枚举第一个子串中所有字符
int mymax = -1;
for(int j = i; j < n; j += k){ //周期前进,到达其他子串的对应位置
char curchar = str[j];
int cur = str[j] - 'a' + 1;
vis[i][cur]++;
mymax = max(mymax, vis[i][cur]);
} //统计对应位置下出现频率最高的字符,即为不需要修改的字符
sum += (len - mymax);
}
printf("%d\n", sum);
return 0;
}