牛客小白月賽#27
A-牛牛愛學習 #二分答案 #貪心
題目連結
題意
給定$n (\leq1e6) $本書,每本書知識值為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”\)
#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%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;
}