1. 程式人生 > 實用技巧 >牛客小白月賽27題解(部分)

牛客小白月賽27題解(部分)

連結:https://ac.nowcoder.com/acm/contest/6874
來源:nowcoder

A 巨木之森(樹的直徑)

  思路:\(n\) 塊區域共有 \(n - 1\) 條道路,保證 \(n\) 塊區域聯通,我們可以想到如果我們從某一塊區域 \(x\) 出發,現在我們走到了最後一塊區域 \(y\),那麼從 \(y\) 一定能夠到達 \(x\),如果我們能夠保證 \(y\)\(x\) 的距離是最遠的,那麼我們就可以這個分隊走過所有的區域的最短的路徑就是 \(所有的邊 - dis[x][y]\),我們走過了所有的區域在返回起點的時候相當於我們把所有的邊都走了一遍,但是我們的目的是遍歷所有的區域,如果我們從 \(x\)

倒退到某一個最遠的點 \(y\),那麼 \(y\) 就是分隊所到達的最後一個點。當我們求出樹的直徑上的兩個端點到各個點的距離,就可以反向求出從每一個點出發需要的走過的最短的路徑。

# include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e5 + 10;
ll dis[maxn][2], ans[maxn], len;
int head[maxn], cnt, maxdot;

int n;

struct Edge {
    int u, v,  w, net;
}edge[maxn << 1];

void addedge(int u, int v, int w) {
    edge[cnt].u = u; edge[cnt].v = v;
    edge[cnt].w = w; edge[cnt].net = head[u];
    head[u] = cnt ++;
}

void dfs(int u, int fa, int num) {
	if(len <= dis[u][num]) {
		len = dis[u][num];
		maxdot = u;
	}
    for(int i = head[u]; i != -1; i = edge[i].net) {
        int v = edge[i].v;
        if(v == fa) continue;
        dis[v][num] = dis[u][num] + (ll)edge[i].w;
        dfs(v, u, num);
    }
}

int main() {
    ll m; scanf("%d%lld", &n, &m);
    for(int i = 1; i <= n; ++ i) head[i] = -1;
    ll edgeSum = 0;
    for(int i = 1; i < n; ++ i) {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        addedge(u, v, w); addedge(v, u, w);
        edgeSum += (ll)w * 2;
    }
    //cout << edgeSum << endl;
    dfs(1, -1, 0); len = 0; dis[maxdot][0] = 0;
    dfs(maxdot, -1, 0); len = 0;
    dfs(maxdot, -1, 1);
    for(int i = 1; i <= n; ++ i) ans[i] = edgeSum - max(dis[i][0], dis[i][1]);
    sort(ans + 1, ans + n + 1);
    ll sum = 0;
	int cnt_ans = 0;
    for(int i = 1; i <= n; ++ i) {
    	sum += ans[i];
    	if(sum <= m) ++ cnt_ans;
    }
    printf("%d\n", cnt_ans);
    return 0;
}

B 樂團派對(貪心)

  思路:首先如果 \(a[i] > n\) 成立,那麼這位樂手必定無法進入任何一支樂隊,此時輸出 "-1",否則,我們對 \(a[i]\) 進行從小到大排序,可以貪心的讓最大的 \(a[i]\) 值的樂手先組成一支樂隊, 將已經進入樂隊的人都進行標記,然後我們從前往後遍歷,同時記錄當前樂隊已經進入了多少人是否滿足題意,如果最後又剩餘就自動進入最後一支樂隊。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 2e5 + 10;
int a[maxn];

int main() {
    int n; scanf("%d\n", &n);
    for(int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
    int cnt = 0, ans = 0;
    sort(a + 1, a + n + 1);
    //for(int i = 1; i <= n; ++ i) cout << a[i] << " "; cout << endl;
    int inx = -1;
    for(int i = n; i >= 1; -- i) {
        ++ cnt;
        if(cnt == a[n]) {
            ++ ans;
            inx = i;
        }
    }
    if(inx != -1) {
        cnt = 0;
        for(int i = 1; i < inx; ++ i) {
            ++ cnt;
            if(cnt >= a[i]) {
                ++ ans;
                cnt = 0;
            }
        }
        printf("%d\n", ans);
    } else puts("-1");
    return 0;
}

D 巔峰對決(線段樹)

  思路:由於在查詢的時候區間內的數字都互相不相同,所以我們通過查詢區間最大值和區間最小值,通過二者的差值就可以判斷這個區間是否可以形成一段連續的數字。線段樹:單點修改 + 區間查詢

#include<bits/stdc++.h>
using namespace std;
#define LNode x << 1
#define RNode x << 1 | 1

typedef long long ll;
const int maxn = 1e5 + 10;
int a[maxn], Max[maxn << 2], Min[maxn << 2];

void pushUp(int x) {
    Max[x] = max(Max[LNode], Max[RNode]);
    Min[x] = min(Min[LNode], Min[RNode]);
}

void build(int l, int r, int x) {
    Max[x] = Min[x] = 0;
    if(l == r) {
        Min[x] = Max[x] = a[l];
        return ;
    }
    int mid = (l + r) >> 1;
    build(l, mid, LNode);
    build(mid + 1, r, RNode);
    pushUp(x);
}

void update(int l, int r, int x, int pos, int val) {
    if(l == r) {
        Min[x] = Max[x] = val;
        return ;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid) update(l, mid, LNode, pos, val);
    else update(mid + 1, r, RNode, pos, val);
    pushUp(x);
}

int query_max(int l, int r, int L, int R, int x) {
    if(L <= l && R >= r) return Max[x];
    int mid = (l + r) >> 1, ans = 0;
    if(L <= mid) ans = max(ans, query_max(l, mid, L, R, LNode));
    if(R > mid) ans = max(ans, query_max(mid + 1, r, L, R, RNode));
    return ans;
}

int query_min(int l, int r, int L, int R, int x) {
    if(L <= l && R >= r) return Min[x];
    int mid = (l + r) >> 1, ans = 1e9 + 10;
    if(L <= mid) ans = min(ans, query_min(l, mid, L, R, LNode));
    if(R > mid) ans = min(ans, query_min(mid + 1, r, L, R, RNode));
    return ans;
}

int main() {
    int n, q; scanf("%d%d", &n, &q);
    for(int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
    build(1, n, 1);
    //for(int i = 1; i <= 4 * n; ++ i) cout << Max[i] << " "; cout << endl;
    while(q --) {
        int op; scanf("%d", &op);
        if(op == 1) {
            int pos, val; scanf("%d%d", &pos, &val);
            update(1, n, 1, pos, val);
        } else {
            int l, r; scanf("%d%d", &l, &r);
            int mmax = query_max(1, n, l, r, 1);
            int mmin = query_min(1, n, l, r, 1);
            //cout << mmax << " " << mmin << endl;
            if(mmax - mmin == r - l) puts("YES");
            else puts("NO");
        }
    }
    return 0;
}

E 使徒襲來(公式題)

  思路:\(\frac{x + y + z}{3} >= \sqrt[\frac{1}{3}]{x·y·z}\)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

int main() {
    int n; scanf("%d", &n);
    double ans = 3.0 * pow(n, 1.0/3);
    printf("%.3f\n", ans);
    return 0;
}

F 核彈劍仙(dfs bfs)

  思路:根據題意可以建一張圖出來,建圖完畢後,所問的就是 \(i\) 點的可以到達的點有多少個。直接 \(dfs\) 或者 \(bfs\) 可以計算出每一個點的可達點有多少個。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e3 + 10;
vector<int> G[maxn];
bool vis[maxn];


int dfs(int x) {
	vis[x] = true;
	int ans = 0;
	for(int i = 0; i < (int)G[x].size(); ++ i) {
		int v = G[x][i];
		if(!vis[v]) {
			ans += dfs(v);
			vis[v] = true;
			++ ans;
		}
	}
	return ans;
}

int main() {
	int n, m; scanf("%d%d", &n, &m);

	while(m --) {
		int a, b; scanf("%d%d", &a, &b);
		G[b].push_back(a);
	}
	for(int i = 1; i <= n; ++ i) {
		for(int j = 1; j <= n; ++ j) vis[j] = false;
		int ans = dfs(i);	
		printf("%d\n", ans);
	}
	return 0;
} 
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e3 + 10;
vector<int> G[maxn];

int main() {
	int n, m; scanf("%d%d", &n, &m);

	while(m --) {
		int a, b; scanf("%d%d", &a, &b);
		G[b].push_back(a);
	}
	for(int i = 1; i <= n; ++ i) {
		int ans = 0;
		queue<int> q; 
		bool vis[maxn];
		for(int i = 1; i <= n; ++ i) vis[i] = false;
		q.push(i); vis[i] = true;
		while(!q.empty()) {
			int now = q.front(); q.pop();
			for(int i = 0; i < (int)G[now].size(); ++ i) {
				int v = G[now][i];
				if(!vis[v]) {
					vis[v] = true;
					q.push(v);
					++ ans;
				}
			}
		}
		printf("%d\n", ans);
	}
	return 0;
} 

G 虛空之力(貪心)

  思路:直接求出 \(min(i, j, k)\) 的個數,然後判斷最多可以抵消多少個 \(k\) 即可求出答案。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e7 + 10;
char a[maxn];

int main() {
    int n; scanf("%d", &n);
    scanf("%s", a);
    n = strlen(a);
    int cntk = 0, cnti = 0, cntn = 0, cntg = 0;
    for(int i = 0; i < n; ++ i) {
        if(a[i] == 'k') ++ cntk;
        else if (a[i] == 'i') ++ cnti;
        else if(a[i] == 'n') ++ cntn;
        else if(a[i] == 'g') ++ cntg;
    }
    int Min = min(cnti, min(cntn, cntg));
    if(cntk >= Min) printf("%d\n", Min);
    else {
        if(2 * cntk >= Min) printf("%d\n", Min);
        else printf("%d\n", 2 * cntk);
    }
    return 0;
}

H 社團遊戲(二分 + dp)

  思路:\(dp[i][j][k]:(0, 0)\)\((i, j)\) 這個長方形內字母 \(k\) 的個數是多少個,狀態轉移方程:\(dp[i][j][k] = dp[i - 1][j][k] + dp[i][j - 1][k] - dp[i - 1][j - 1][k] + (str[i][j] - 'a' == k);\),然後對於每一個位置,我們可以二分一個邊長,判斷其中的字母的數量是否滿足題意即可。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 510 + 10;
int dp[maxn][maxn][26];
char str[maxn][maxn];
int x;

bool check(int i, int j, int len) {
	int i1 = i + len - 1, j1 = j + len - 1;
	for(int k = 0; k < 26; ++ k) {
		int res = dp[i1][j1][k] - dp[i - 1][j1][k] - dp[i1][j - 1][k] + dp[i - 1][j - 1][k]; 
		if(res > x) return false;
	}
	return true;
}

int main() {
	int n, m; scanf("%d%d%d", &n, &m, &x);
	for(int i = 1; i <= n; ++ i) scanf("%s", str[i] + 1);
	for(int i = 1; i <= n; ++ i) {
		for(int j = 1; j <= m; ++ j) {
			for(int k = 0; k < 26; ++ k) {
				dp[i][j][k] = dp[i - 1][j][k] + dp[i][j - 1][k] 
					- dp[i - 1][j - 1][k] + (str[i][j] - 'a' == k);
			}
		}
	}
	/*for(int i = 1; i <= n; ++ i) {
		for(int j = 1; j <= m; ++ j) {
			cout << dp[i][j][0] << " ";
		}
		cout << endl;
	}*/
	//cout << "-----------------------------" << endl;
	for(int i = 1; i <= n; ++ i) {
		for(int j = 1; j <= m; ++ j) {
			int l = 1, r = min(m - j + 1, n - i + 1), ans = 0;
			while(l <= r) {
				int mid = (l + r) >> 1;
				if(check(i, j, mid)) {
					l = mid + 1;
					ans = mid; 
				} else r = mid - 1;
			}
			printf("%d%c", ans, j == m ? '\n' : ' ');
		}
	}
	return 0;
} 

I 名作之壁(雙指標 + 單調佇列)

  思路:如果現在指標 \(l\) 指向 \(1\) 號位置,現在我們在右邊找到一個 \(r\) 滿足最大值和最小值之差大於 \(k\),那麼之後的每一位置都可以滿足最大值和最小值之差大於 \(k\),之後的每一個位置都包含滿足題意的區間,所以都滿足題意,此時就增加了 \(n - r + 1\) 個區間,我們就可以將 \(l\) 右移,判斷最大值和最小值的差,反覆的做這樣的操作,直到 \(r\) 到達了最後一個位置為止。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e7 + 10;
const int mod = 1e9;
int a[maxn];

int main() {
	int n, k; scanf("%d %d", &n, &k);
	int b, c; scanf("%d%d%d", &a[0], &b, &c);
	for(int i = 1; i <= n; ++ i) a[i] = (1ll * a[i - 1] * b % mod + 1ll * c) % mod;
	//for(int i = 1; i <= n; ++ i) cout << a[i] << " "; cout << endl;
	deque<int> qMax, qMin;
	ll ans = 0;
	for(int l = 1, r = 1; r <= n; ++ r) {
		while(qMax.size() && a[qMax.back()] < a[r]) qMax.pop_back();
		qMax.push_back(r);
		while(qMin.size() && a[qMin.back()] > a[r]) qMin.pop_back();
		qMin.push_back(r);
		//cout << a[qMax.front()] << " " << a[qMin.front()] << endl;
		while(a[qMax.front()] - a[qMin.front()] > k) {
			ans += n - r + 1; l ++;
			if(qMax.front() < l) qMax.pop_front();
			if(qMin.front() < l) qMin.pop_front();
		}
	}
	printf("%lld\n", ans);
	return 0;
}

J 逃跑路線(思維)

  思路:我們知道\((2^{1} - 1)\&(2^{2} - 1)\&(2^{3} - 1)\&···\&(2^{n} - 1) = 1\),所以問題就轉化為了 \(橫座標 \& 1\),如果橫座標的最後一位為奇數,那麼答案則為 \(1\),否則答案則為 \(0\)。我們只需要判斷輸入的所有的字串的最後一位的和,然後判斷奇偶即可。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e4 + 10;
char a[maxn];

int main() {
    int n; scanf("%d", &n);
    int ans = 0;
    for(int i = 1; i <= n; ++ i) {
        scanf("%s", a);
        int len = strlen(a);
        //printf("%d\n", len);
        ans = (ans + a[len - 1]) % 10;
    }
    //printf("%d\n", ans);
    printf("%d\n", ans & 1);
    return 0;
}