D
题意
给定一个序列,可以选择一个位置的数修改成1到1e9的任何数,最多进行k次这样的操作
操作结束后 需要求出当前完全图的直径,即全源最短路的最长值
解题思路
首先需要找到一些性质
- 因为需要找的是最大值,所以修改操作最优是直接修改成1e9;
- 可以发现每个点到其他点的最短路至多中间经过一个点;
- 假设 u , v 为其中两个点, 直接相连的最短路 d(u,v) = min(av ~ au);
中间隔一个点达到的最短路 即 av -> ai -> au 求出的最短路是 2 * min(a1 ~ an); - 所以对于任何的点u ,v (u < v)d(u , v) = min{2 * min(a1 ~ au-1) , min(au ~ av}, 2 * min(av+1 ~ an); 根据贪心策略, 我们尽量让u, v中间没有其他点,这样我们就能尽量把u,v的最短路变成2倍的其他点和一倍的u, v本身的点的值取小
性质到这里,题目就可以写了,有两种解法,一、贪心直接枚举所有相邻点,查看当前位置是否为前k个小的值,然后直接求答案(本来是打算直接将前k个数变成1e9直接求答案的,奈何出现多个相同数的时候修改操作的答案最优解出现跟把这些相同数错开来修改最优,不好写)二、二分答案来check正确性可以说二分比较好理解也比较好写
代码实现
二分 + check函数
check的思路: 对于chekc的值x,我们预处理数组,前缀和后缀的s数组有多少小于(x + 1 )/ 2 的个数, 然后枚举所有相邻点,只需要查看当前两个相邻点小于x和前后缀需要修改的值的个数是否小于k即可
int n, k;
int s1[N], s2[N], a[N];
bool check (int x)
{
int t = (x + 1) / 2;
s1[0] = s2[n + 1] = 0;
for (int i = 1 ; i <= n ; i ++)
s1[i] = s1[i - 1] + (a[i] < t);
for (int i = n ; i >= 1 ; i --)
s2[i] = s2[i + 1] + (a[i] < t);
for (int i = 1 ; i <= n - 1 ; i ++)
if (s1[i - 1] + s2[i + 2] + (a[i] < x) + (a[i + 1] < x) <= k)
return true;
return false;
}
void solve()
{
cin >> n >> k;
for (int i = 1 ; i <= n ; i ++) cin >> a[i];
int l = 1, r = INF;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (!check (mid)) r = mid - 1;
else l = mid;
}
cout << l << endl;
}
E
题意
给定区间l , r 求有多少个三元组(i , j , k)满足要求i + j + k <= lcm(i , j , k)
解题思路
正难则反, 我们知道所有三元组的个数是Cn3 我们找到所有 i + j + k > lcm(i , j , k)的个数即可
因为i + j + k < 3 * k 而lcm(i , j , k) 是k的倍数,所以lcm(i , j , k)只能等于k , 2 * k
但是直接枚举肯定不行
通过打表我们发现等于2*k的情况非常少,可以特判,剩下全是等于k的,我们暴力预处理k的因子,然后枚举k的时候,如果k的因子个数是p ans 减去 Cp2 加上2 * k的特判
3 4 5 和 6 10 15 这两组的倍数 ans –
代码实现
void solve()
{
int l, r;
cin >> l >> r;
int n = r - l + 1;
int ans = n * (n - 1) * (n - 2) / 6;
vector<int> f (r + 1, 0);
for (int i = l ; i <= r ; i ++)
for (int j = i + i ; j <= r ; j += i) f[j] ++;
for (int i = l; i <= r ; i ++)
{
ans -= (f[i] * (f[i] - 1) / 2);
if (i % 6 == 0 and i / 2 >= l) ans --;
if (i % 15 == 0 and i *2 /5 >= l) ans --;
}
cout << ans << endl;
}