牛客小白月赛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
", 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);
    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
", 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
", 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
", 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
", 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
", Min);
    else {
        if(2 * cntk >= Min) printf("%d
", Min);
        else printf("%d
", 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 ? '
' : ' ');
		}
	}
	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
", 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
", len);
        ans = (ans + a[len - 1]) % 10;
    }
    //printf("%d
", ans);
    printf("%d
", ans & 1);
    return 0;
}

原文地址:https://www.cnblogs.com/zut-syp/p/13570760.html