1. 程式人生 > 實用技巧 >牛客小白月賽#27

牛客小白月賽#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”\)

,接下來利用迴圈(即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%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;
}