竞赛讨论区 > 牛客小白月赛119题解
头像
HoshizoraZ
编辑于 07-04 21:33 福建
+ 关注

牛客小白月赛119题解

A

可以暴力枚举;或者分析一下,把 A 中最大的当作队长,B 中最小的当作队长判断是否可行。

int a[10], b[10], s1 = 0, s2 = 0;
for(int i = 1; i <= 5; i++) cin >> a[i], s1 += a[i];
for(int i = 1; i <= 5; i++) cin >> b[i], s2 += b[i];
s1 += a[1], s2 += b[5];
if(s1 > s2) cout << "YES\n";
else cout << "NO\n";

B

  • 的情况,输出
  • 对于剩下的情况,设 的非零数个数是 ,等于 的数个数是 ,那么答案是 。注意约分。
  • 约分不用 __gcd 也很简单,因为真分数里只有 需要约分。
void solve(){
	cin >> a >> b >> c >> d >> e >> f;
	int sk = 0, sn = 0;
	if(a == 0 && b == 0 && c == 0 && d == 0 && e == 0) cout << "1/1000" << endl;
	else{
		if(a == f) sk++;
		if(b == f) sk++;
		if(c == f) sk++;
		if(d == f) sk++;
		if(e == f) sk++;
		
		if(a != 0) sn++;
		if(b != 0) sn++;
		if(c != 0) sn++;
		if(d != 0) sn++;
		if(e != 0) sn++;
		
		if(sk == 0) sn = 1;
		if(sk == sn) sk = sn = 1;
		if(sk == 2 && sn == 4) sk = 1, sn = 2;
		cout << sk << "/" << sn << endl;
	}
}

C

假设有 个人。那么输出 Other 的情况分为以下两种:

  • 个人的答案是 ,其余人都回答了
    • 这说明此时恰好有一种颜色的帽子是最多的,而且它在场上有 顶。
  • 所有人的答案都是相同的数
    • 此时至少有两种颜色的帽子是最多的,而且它们在场上均恰好有 顶。
    • 所以在以上条件基础上,还需要保证 才是合理的情况。

请注意所有人回答 的情况是有可能的,一定要放到第一类去判断(此时 应当成立)。

对于其它情况,答案都是 Lie。

int t, n, a[200010], c, pd, num, k;
int solve(){
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i];
	sort(a + 1, a + n + 1);
	if(a[1] == a[n] - 1){
		num = 0, k = a[n];
		for(int i = 1; i <= n; i++) 
			if(a[1] == a[i]) num++;
		if(k == num) return 1;
		else return 0;
	}
	else if(a[1] == a[n]){
		if(a[1] == n - 1 || a[1] <= n / 2) return 1; 
		return 0;
	}
	else return 0;
}
int main(){
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	cin >> t;
	while(t--) cout << (solve() ? "Other\n" : "Lie\n");
	return 0;
}

D

BFS,一开始把所有叶子存入队列,每次向内延伸。对于每个节点记录下延伸的轮数(它等于最近叶子节点的距离)

最终找出记录值最远的一些点即可。

const int N = 200010;
int t, n, a[N], vis[N], d[N], sum, mx, res[N];
queue <pair<int, int> > q;
vector <int> e[N];

void solve(){
	cin >> n;
	for(int i = 1; i <= n; i++){
		vis[i] = res[i] = d[i] = 0;
		e[i].clear();
	}
	for(int i = 1, u, v; i < n; i++) {
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
		d[u]++, d[v]++;
	}
	for(int i = 1; i <= n; i++)
		if(d[i] == 1){
			q.push(mp(i, 0));
			vis[i] = 1;
		}
	while(!q.empty()){
		int x = q.front().first, v = q.front().second;
		q.pop();
		for(int y : e[x]){
			if(vis[y]) continue;
			vis[y] = 1;
			res[y] = v + 1;
			q.push(mp(y, v + 1));
		}
	}
	sum = mx = 0;
	for(int i = 1; i <= n; i++){
		if(res[i] > mx)
			mx = res[i], sum = 1;
		else if(res[i] == mx)
			sum++;
	}
	cout << sum << endl;
	for(int i = 1; i <= n; i++)
		if(res[i] == mx)
			cout << i << " ";
	cout << endl;
}

E

先只考虑子序列后一个是前一个数的倍数情况

考虑 DP。假设 表示扫描到位置 之前,最长符合条件的子序列的长度。

那么对于每个位置,有 。这里 表示 的约数。

但是这样做是 的,会超时,我们可以重新定义函数 。假设 表示,从左到右扫描到当前位置 时,选取的子序列最后一个数是 的最长子序列的长度。

然后对于当前的数 ,有 。由于一个数的约数个数最多是 级别的,所以对于每一个位置时间复杂度是 ,总时间复杂度

再加上子序列后一个是前一个数的约数情况

该怎么往前找自己的倍数呢?如果逐个枚举,那么在 较小的时候枚举量会过大,导致 TLE。

那我们就应该提前为后面可能出现的数字进行铺垫。对于 ,若 的约数,那么 的倍数,我们在算完第 个位置的答案时,直接把这个答案下放到所有 约数对应的 值里,预判下一个数。

当然为了防止新下放的答案和原有的数字发生冲突,我们重新定义一个函数 专门储存这种下放情况的答案。

简单来说,对于每个新扫描到的位置 ,我们按顺序执行如下操作:

即先算一遍 再算一遍 。最后在所有 里找到最大的一个即可。时间复杂度 。std 预处理了值域内的所有约数,但是不写这个应该也能轻松通过。

const int N = 200010;
int n, a[N], f[N], g[N], t, ans;
vector <int> d[N]; 

void init(){
	for(int i = 1; i < N; i++)
		for(int j = i; j < N; j += i)
			d[j].push_back(i);
}

void solve(){
	cin >> n;
	for(int i = 1; i <= n; i++)
		cin >> a[i];
	ans = 0;
	for(int i = 1; i <= n; i++) 
		f[i] = g[i] = 0;
	for(int i = 1, mx; i <= n; i++){
		mx = 0;
		for(int j : d[a[i]]) 
			mx = max(mx, f[j] + 1);
		f[a[i]] = max(g[a[i]], mx);
		for(int j : d[a[i]])
			g[j] = max(g[j], f[a[i]] + 1); 
	}
	for(int i = 1; i <= n; i++) 
		ans = max(ans, f[i]);
	cout << ans << endl;
}

F

我们需要知道一个结论:

  • 对于一个极长连续 o 段,我们想通过修改 处让分数最小,那么分割出来的若干段长度要尽可能平均。
  • 证明:
    • 假设切出的两个连续 o 段,长度均为 ;此时把修改的地方往相邻处移动一格,切出的两段长度就变为
    • 那么两种情况的分数分别为 ,显然前者更小。
    • 相似的情况同样可以计算得知,长度尽量靠近时分数更小,这里就不写了。
  • 所以我们可以定义一个函数 表示把一个长度为 的连续 o 段修改 处得到的最小分数。
    • 定义 ,即一个长度为 的连续 o 段的分数。
    • 那么 相当于有 处被抹掉了,要把剩余的 处切成 段,且尽可能切得均匀。
    • 。三个量 分别代表切割出的最小段的长度、切出长度为 的段数量、切出长度为 的段数量。
    • 那么

对于一般的情况,我们会出现多个连续 o 段。

我们可以设计一个贪心的策略,希望每多切一刀,答案就会减少得尽可能多。那我们要怎么切呢?

一个很常见的误区就是每次选一段,切成均等的两半。这是不对的,原因可以看上面的结论。假设你可以切两刀,最优的分割方案是三等分,而不是对半后再在某一段对半。

所以对于初始情况的每个极长连续 o 段,我们假设记录它初始长度 ,当前已经被切的次数 。那么对于这被切了 次的段,多给你一次切一刀的机会,带来的减少量是 。这是一个反悔贪心式的操作,将补偿量作为更换方案的指标。

如果让你选择多个原始连续段的其中一个切,你需要挑出减少量最大的一个原始段。切完以后,把当前的减少量更新为 ,然后在这些原始段内重新选减少量最大的段,进行下一次切割。

这可以用堆维护。可以分析得知时间复杂度低于 ,可以轻松通过。

#define int long long
const int N = 1000010;
struct Cut{
	int id, now, val;
}cut[N];
bool operator < (Cut x, Cut y){
	return x.val < y.val;
}

int n, k, T, segs, a[N], state, sum;
char s[N];
priority_queue <Cut> q; 

int S(int x){
	return x * (x + 1) / 2;
}
int F(int i, int j){
	if(i <= j) return 0;
	int k = (i - j) / (j + 1);
	int r = (i - j) - (j + 1) * k;
	int p = (j + 1) - r;
	return p * S(k) + r * S(k + 1); 
}
int solve(){
	int Sum = 0;
	while(!q.empty()) q.pop();
	for(int i = 1; i <= segs; i++){
		Sum += S(a[i]);
		q.push((Cut){i, 0, F(a[i], 0) - F(a[i], 1)});
	}
	for(int i = 1; i <= k && !q.empty(); i++){
		int id = q.top().id, now = q.top().now, val = q.top().val;
		q.pop();
		Sum -= val;
		if(now < a[id]) q.push((Cut){id, now + 1, F(a[id], now + 1) - F(a[id], now + 2)});
	}
	return Sum;
}

void Main(){
    cin >> n >> k >> s;
	segs = state = sum = 0;
	for(int i = 0; i < n; i++){
		if(s[i] == 'o'){
			sum++;
			if(!state) state = 1, a[++segs] = 1;
			else a[segs]++;
		}
		else state = 0;
	}
	cout << solve() << endl;
}

全部评论

(2) 回帖
加载中...
话题 回帖

等你来战

查看全部

热门推荐