ABC round 469

A - Train Car

题目思路:基础数学

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pll = pair<ll ,ll>;

void solve()
{
	ll n, k;cin >> n >> k;
	cout << n - k + 1 << '\n';
}
int main()
{
	cin.tie(0)->ios::sync_with_stdio(false);
	int _ = 1;
	while(_ -- ) solve();
	return 0;
}

B - Isolated Seats

题目思路:基础字符串操作

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pll = pair<ll ,ll>;

void solve()
{
	int n;cin >> n;
	string s;cin >> s;
	s = 'x' + s;
	s = s + 'x';
	int res{};
	for(int i = 1 ; i<= n ;i ++ ) res += s[i] == 'x' && s[i - 1] == 'x' && s[i +  1] == 'x';
	cout << res << '\n';
}
int main()
{
	cin.tie(0)->ios::sync_with_stdio(false);
	int _ = 1;
	while(_ -- ) solve();
	return 0;
}

C - Cantrip

题目思路:双指针
题目有点难调,wa了好几发QAQ
首先发现这里从右往左看答案是单增的,所以显然我们可以直接用指针i扫一遍然后指针j在i的前面尽可能的走(显然这里j不可能往后走)
题目说往前走就要消耗储存的o,所以走的时候遇到o的话o储量不变x储量减1
所以也就是说对于\(i\)而言只要满足\([1, i]\)的o的数量大于等于\([i + 1, j]\)的x的数量的话\(j\)就可以往前走
注意两点边界

  • 这里j要求为0的时候立刻停下,因为要先消耗再拿,所以如果遇到等于的话就直接break
  • 对于形如(xxx)这样的j会一直留在原地不动,这个时候记得保证\(i \le j\)
    检查条件就是看一下,大于不用管,等于的话
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pll = pair<ll ,ll>;
		
void solve()
{
	int n;cin >> n;
	string s;cin >> s;
	s = '&' + s;
	vector<int>cnto(n + 1, 0),cntx(n + 1, 0);
	for(int i = 1 ;i <= n ;i ++) 
	{
		cnto[i] += s[i] == 'o';
		cntx[i] += s[i] == 'x';
	}
	for(int i = 1; i <= n ;i ++ )
	{
		cnto[i] += cnto[i - 1];
		cntx[i] += cntx[i - 1];
	}
	//oxoxo
	for(int i = 1, j = 1;i <= n ; i ++ )
	{
		int co = cnto[i];
		if(i > j) j = i;
		while(j <= n && cntx[j] - cntx[i] <= co)
		{
			if(cntx[j] - cntx[i] == co)
			{
				j ++;
				break;
			}
			j ++;
		}
//		cout << "TESt : " << cntx[j] - cntx[i] << " " <<  co << '\n';
		cout << j - 1<< '\n';
	}
}
//discards
int main()
{
	cin.tie(0)->ios::sync_with_stdio(false);
	int _ = 1;
	while(_ -- ) solve();
	return 0;
}

D - The Big Two

题目思路:暴力
emmm题目有点不说人话,读懂以后还是蛮好想的
题意翻译一下即为,给A和B两个数组,给出\({x, y}\)要求对于\(1 \le i \le m\)都有\(A_i == x || A_i == y || B_i == x || B_i == y\)
所以不难想到从第一个开始我们一定要选一个由此我们可以先确定x(要么是\(A[1]\)要么是\(B[1]\)),然后去找y
然后想到如果有一条全为x的路径从1到m那么y我们可以直接取\([1, n]{不含x}\),如果出现了一个地方\(A[i] != x 并且 B[i] != x\)
那么这个时候y也确定了(要么是\(A[i]\)要么是\(B[i]\)),直接检测一下此时的x和y是否可行即可

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pll = pair<ll ,ll>;
vector<pll>a;
const ll INF = 1e18;
int n, m;
void solve()
{
	cin >> n >> m;
	a.resize(m + 1);
	for(int i = 1;  i<= m; i ++ ) cin >> a[i].first >> a[i].second;
	vector<pll>res;
	int x = a[1].first;int cnt = 0;
	auto isok = [&](int loc, int y)->bool
	{
		for(int i = loc ;i <= m ;i ++ )
		{
			if(a[i].first == x || a[i].second == x) continue;
			if(a[i].first == y || a[i].second == y) continue;
			return false;
		}
//		cout <<"TEST : " << loc << " " << x << " " << y << '\n';
		return true;
	};
	auto add = [&](int xx, int yy)->void
	{
		if(xx > yy) res.emplace_back(xx ,yy);
		else res.emplace_back(yy, xx);
	};
	for(int i = 1 ;i <= m ;i ++ )
	{
		cnt += !(a[i].first == x || a[i].second == x);
		if(cnt)
		{
			if(isok(i, a[i].first)) add(x, a[i].first);
			if(isok(i, a[i].second)) add(x, a[i].second);
			break;
		}
	}
	
	if(!cnt)
	{
		for(int i = 1 ; i<= n ;i ++ )
		{
			if(x == i) continue;
			add(x, i);
		}
	}
	x = a[1].second;
	cnt = 0;
	for(int i = 1 ;i <= m ;i ++ )
	{
		cnt += !(a[i].first == x || a[i].second == x);
		if(cnt)
		{
			if(isok(i, a[i].first)) add(x, a[i].first);
			if(isok(i, a[i].second)) add(x, a[i].second);
			break;
		}
	}
	if(!cnt)
	{
		for(int i = 1 ; i<= n ;i ++ )
		{
			if(x == i) continue;
			add(x, i);
		}
	}
	sort(res.begin(), res.end());
	res.erase(unique(res.begin(),res.end()),res.end());
	
	cout << res.size() << '\n';
}
//discards
int main()
{
	cin.tie(0)->ios::sync_with_stdio(false);
	int _ = 1;
	while(_ -- ) solve();
	return 0;
}

E - Pro Exam Eligibility

题目思路:求区间最大平均值
对于区间最大平均值一般有三种常用方式

  • 情况A:固定长度k。滑动窗口。简单。O(n)。
  • 情况B:任意长度(至少k)。这是经典的“最大平均值子数组 II”(LeetCode 644)。采用二分查找答案 + 前缀和检查\(O(n log range)\)
  • 情况C:有长度约束的通用最大密度段。通常采用凸包技巧或分数规划(Dinkelbach 算法)。
    我们采取方法B,用二分 + 前缀和 + 前缀min的方式求解
    首先观察到这里的ave具备单调性,如果,对于离散值域中的所有ave\(ave_1 >ave_2且ave_1存在,那么ave_2也一定可行\),所以可以用二分来计算答案
    再想一下二分检查
    那么无非就是只要存在\([i, j]\)满足以下条件
  • \((pre[j] - pre[i - 1]) / (j - i + 1) \geq ave\)
  • \(pre[j] - pre[i - 1] \geq k\)
    我们可以对第一个式子进行i与j的分离尝试构造函数(常规化简操作)
    \(pre[j] - j * ave \geq pre[i - 1] - (i - 1) * ave\)
    所以构造函数\(B[j] = pre[j] - j* ave \geq B[i - 1]\)
    至于第二个式子我们可以用双指针保证实现,因为观察到当j向右移动的时候i的可存在区域大小也是单调递增的
    (反一下,我习惯用j当第二指针)
    所以i移动的时候同时移动j并且做下\(B[j - 1]\)的min,当存在\(B[i] \geq min\)的时候就更新
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pll = pair<ll ,ll>;

const double eps = 1e-15;
void solve()
{
	int n, k;cin >> n >> k;
	string s;cin >> s;
	s = '&' + s;
	vector<int>pre(n + 1 , 0);
	for(int i = 1; i <= n ;i ++ ) pre[i] += s[i] == 'o';
	for(int i = 1; i <= n ;i ++ ) pre[i] += pre[i - 1];
	//存在(i, j)满足pre[j] - pre[i - 1] >= k 使得
	//pre[j] - pre[i - 1] / (j - i + 1) >= mid
	//pre[j] - pre[i - 1] >= j * mid - (i - 1) * mid
	//pre[j] - j * mid >= pre[i - 1] - (i - 1) * mid
	//B[j] = pre[j] - j * mid >= B[i - 1]
	//pre[j] >= pre[i - 1] + k
	
	double l = 0, r = 1;
	auto isok = [&](double x)->bool
	{
		vector<double>B(n + 1);
//		for(int i = 1 ;i <= n ;i ++ ) B[i] = pre[i] - i * x;
//		double mn = 1e18;
//		for(int i = k ;i <= n ;i ++ )
//		{
//			mn = min(mn, B[i - k]);
//			if(B[i] >= mn) return true;
//		}
		for(int i = 1 ;i <= n ;i ++ ) B[i] = pre[i] - i * x;
//		int lc = 1;
//		while(pre[lc] < k) lc ++;
		double mn = 1e18;
		for(int i = 1, j = 1; i <= n ; i ++ )
		{
			while(j <= n && pre[i] - pre[j - 1] >= k)
			{
				mn = min(mn , B[j - 1]);
				j ++;
			}//B[j] >= B[i - 1]
			if(B[i] >= mn) return true;
		}
		return false;
	};
	while(fabs(l - r) >= eps)
	{
		double mid = (l + r) / 2.0;
		if(isok(mid)) l = mid;
		else r = mid;
	}
	cout << fixed << setprecision(12) << l  << '\n';
}
//discards
int main()
{
	cin.tie(0)->ios::sync_with_stdio(false);
	int _ = 1;
	while(_ -- ) solve();
	return 0;
}

F - GCD Maximum Spanning Tree

题目思路:kruskal算法+调和级数
观察对于形如

	for(int i = 1; i <= n ;i ++ ) 
	{
		for(int j = i ;j <= n; j += i)
		{
		
		}
	}

这种样子的复杂度是\(O(nlogn)\)
这里要求给出一个定义边为\(gcd(a, b)\)的最大生成树
那么不难想到可以用kruskal实现
我们可以去遍历所有边的值d,然后对应的点一定是d的倍数
所以我们可以在\(O(nlogn)\)内实现遍历所有\(gcd(a, b) = d\)的点
然后我们直接选其中一个然后剩下的插在这个点上即可
实现的时候稍微得注意一下,因为存在a = b的情况,这个时候我们直接合并即可这样收益最大

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pll = pair<ll ,ll>;
const int N = 1e6;
struct DSU
{
	vector<int>a,sz;
	int n;
	DSU(int x) : n(x), a(x + 10, 0),sz(x + 10, 1)
	{
		iota(a.begin(), a.end(), 0);
	}
	int find(int x)
	{
		while(a[x] != a[a[x]]) a[x] = a[a[x]];
		return a[x]; 
	}
	void merge(int x, int y)
	{
		x = find(x), y = find(y);
		if(sz[x] > sz[y]) swap(x, y);
		sz[y] += sz[x];
		a[x] = y;
	}
	bool same(int x, int y){return find(x) == find(y);}
	int size(int x){return sz[find(x)];}
};
void solve()
{
	int n;cin >> n;
	vector<ll>a(n + 1);
	for(int i = 1; i <= n ;i ++ ) cin >> a[i];
	//x - gcd(x, y) -> y
	ll res{};
	vector<int>cnt(N + 10, 0);
	for(int i = 1; i <= n ;i ++ )
	{
		cnt[a[i]] ++ ;
		if(cnt[a[i]] > 1)
		{
			res += a[i];//观察到相同点链接一定收益最高直接在这里连起来
		}
	}
	
	DSU dsu(N);
	for(int i = N ;i >= 1; i -- )
	{
		int first = -1;
		for(int d = i; d <= N ; d += i)
		{
//			for(int k = 0 ; k < cnt[d]; k ++ )
//			{
//				if(first == -1) first = d;
//				else if(!dsu.same(first, d))
//				{
//	//				cout << "TESt : " << first << " " << d << '\n';
//					dsu.merge(first , d);
//					res += i;
//				}
//			}
			//比如16,i = 2,i = 4, i = 8的时候都会经过导致重复检查
			//复杂度过高(比如全是2^20的时候复杂度大概是2^20 * 2 ^20 *log(2^20)会T)
			if(!cnt[d]) continue;
			if(first == -1) first = d;
			else if(!dsu.same(first, d))
			{
				dsu.merge(first, d);
				res += i;
			}
		}
		if(dsu.size(a[1]) == n) break;
	}
	cout << res << '\n';
}
int main()
{
	cin.tie(0)->ios::sync_with_stdio(false);
	int _ = 1;
	while( _ -- ) solve();
	return 0;
}
posted @ 2026-08-02 18:20  Mikan_QWQ  阅读(1)  评论(0)    收藏  举报