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;
}

浙公网安备 33010602011771号