2023.10.28 做题记录
2023.10.28
[NOIP2018 提高组] 铺设道路
选择一个区间进行“填坑”操作;
所以我们的贪心策略是:
若 a[i] > a[i - 1]
, sum += a[i] - a[i - 1];
假设现在有一个坑,但旁边又有一个坑。
你肯定会选择把两个同时减 1
;
那么小的坑肯定会被大的坑带着填掉。
所以只要计算每个坑之间的差就可以。
P1106 删数问题
从头遍历,删去下坡数即可。
注意如果全局下坡路的时候倒着删。
[NOIP2012 提高组] 国王游戏
根据题意我们可以知道,大臣 1 和大臣 2 位置能交换的必要条件是:大臣 2 放在大臣 1 的前面得到的最大值更加小。那么我们分别讨论两种情况下的最大值。
假设只有两个大臣
如果大臣 1 放在前面,金币数分别为:\(a_0/b_1\), \(a_0×a_1/b_2\)
如果大臣 2 放在前面,金币数分别为:\(a_0/b_2\), \(a_0×a_2/b_1\)
约去式子里面的 \(a_0\),讨论两种情况的最大值,就变成了比较:\(max(1/b_1, a_1/b_2)\) 和 \(max(1/b_2, a_2/b_1)\)
根据 \(a≥1\),得出: \(a_1/b_2 >= 1/b_2\), \(1/b_1 <= a_2/b_1\)
如果 \(1/b_1\) 是最大的,则 \(1/b_1>=a_2/b_1\),只可能左右两边相等,则 \(1/b_2<=a_2/b_1\),所以两种情况的最大值是一样的,则不用交换。
同理可得 \(1/b_2\) 是最大的情况也不用交换。
那么只要 \(a_1/b_2\) 和 \(a_2/b_1\) 的大小就可以了,如果 \(a_1/b_2>a_2/b_1\),那么就要交换,变形得:\(a_1*b_1 > a_2*b_2\)
无高精度代码如下:
bool cmp(node a, node b)
{
return a.l * a.r < b.l * b.r;
}
signed main()
{
cin >> n >> l >> r;
for (rint i = 1; i <= n; i++)
{
cin >> a[i].l >> a[i].r;
}
sort(a + 1, a + n + 1, cmp);
for (rint i = 1; i <= n; i++)
{
ans = max(ans, l / a[i].r);
l *= a[i].l;
}
cout << ans << endl;
}
[SHOI2006] 作业
第一眼没有思路时,我们可以考虑暴力:
\(30pts\) \(Code\) 如下:
#include <bits/stdc++.h>
#define rint register int
#define endl '\n'
using namespace std;
set<int> s;
unordered_map<int, bool> v;
const int N = 3e5 + 5;
int p[N];
int n;
signed main()
{
int n;
cin >> n;
while (n--)
{
char op;
cin >> op;
if (op == 'A')
{
int x;
cin >> x;
s.insert(x);
}
else
{
int x, minn = 1e9;
cin >> x;
memset(p, 0, sizeof p);
int j = 1;
for (auto i = s.begin(); i != s.end(); i++, j++)
{
p[j] = *i;
//cout << j << " " << p[j] << endl;
}
int n = j - 1;//cout << n;return 0;
for (rint i = 1; i <= n; i++)
{
minn = min(minn, p[i] % x);
}
cout << minn << endl;
}
}
return 0;
}
我们发现,这个题貌似并不需要用到数据结构。考虑两个优化:
-
1.存储
ans[i]
表示 \(mod\) \(i\) 历次最小答案 -
2.根号分治卡阈值
这个题就做完了,如果忘记根号分治详见浅谈根号分治
#include <bits/stdc++.h>
#define rint register int
#define endl '\n'
using namespace std;
const int N = 3e5 + 5;
const int M = 1e5 + 5;
int n, ans[M];
set<int> s;
int main()
{
for (rint i = 1; i <= M; i++)
{
ans[i] = i;
}
n = 1000;
int T;
cin >> T;
while(T--)
{
char op;
int x;
cin >> op >> x;
if(op == 'A')
{
for (rint i = 1; i <= n; i++)
{
ans[i] = min(ans[i], x % i);
}
s.insert(x);
}
else
{
if(x < n)
{
cout << ans[x] << endl;
}
else
{
int res = x;
for(int i = 0; i < N; i += x)
{
auto it = s.lower_bound(i);
if(it == s.end())
{
break;
}
res = min(res, *it - i);
}
cout << res << endl;
}
}
}
return 0;
}
[NERC2018] Fractions
题目传送门
这个题的严格推导我并不会,但是我们可以手搓一下这个题相关的好玩的东西。
在做这个题之前,先把这个题改一下:
给你一个整数 \(n\),你需要构造出若干个形如 \(\dfrac{1}{b_i}\) 的真分数,使得 \(\sum^n_{i=1} \frac{1}{b_i} = \frac{n - 1}{n}\),且 \(b_i\) 可以整除 \(n\)。
无论是这个改的题还是原题,我们都可以很轻松的判断出,它必不为质数。
那我们举个例子,\(20\) 的因子有 \(2,4,5,10\), \(2 + 4 + 5 + 10 > 20 - 1\)那么 \(20\) 就一定可以拆成若干个几分之一的数相加。
所以只要一个数字的非自己和一的因数以外,如果加起来比它自己减一要大,那么就一定可以拆成若干个几分之一的数相加(瞎几把猜的)
那么能不能把这个结论扩展到这个题,让我们的每一个 \(a_i = 1\),好,显然不对。为什么?
因为必定存在一个数可以拆成别的情况的几个分数相加的形式,且分子不为一。
然后我又瞎搓搓出来一个正解算法。。。。。。
我们先来想两个问题。
第一个问题,我们拆出来的 \(b_i\) 是什么?一定是 \(n\) 的因子对不对。
再来想一个问题,我们既然可以把上面那个情况(改题)拆成若干个几分之一的数相加,那么是不是就可以将其中两个分数两两相加加成一直加加加加加到只剩两个分数相加?
我们之前无法用几个几分之一相加表示出来的数但是可以通过其他方式表示的数是不是也可以表示成两个分数相加?
抱着这样的猜想,我猜了一个结论:
如果这个数能拆,那么必然有一种拆解为两个分数相加,其中这两个分数的分母分别为 \(a\), \(n / a\)。\(a\) 为 n / 2或3或5..... 不断地除同一个质数
举个例子,\(45\) 先除以 \(3\) 再除以 \(3\) ,那么第一个分母为 \(9\),第二个分母为 \(5\),然后分子必为整数!虽然不会证明,但是它一定成立,因为过了。
代码如下
#include <bits/stdc++.h>
#define rint register int
#define endl '\n'
using namespace std;
int n;
int m;
signed main()
{
cin >> n;
m = n;
for (rint i = 2; i * i <= n; i++)
{
if (!(m % i))
{
while(!(m % i))
{
m /= i;
}
break;
}
}
/*
m 为 n 不断地除以它第一个因数
*/
if (m == 1 || m == n)//显然质数时不对
{
puts("NO");
return 0;
}
int y = n / m;
if(m > y)
{
swap(m, y);
}
//cerr << m << " " << y;return 0;
for(rint i = 1; i < m; i++)
{
int t = n - 1;
if((t - y * i) % m == 0)
{
cout << "YES" << endl;
cout << 2 << endl;
cout << i << " " << m << endl;
cout << (t - y * i) / m << " " << y;
return 0;
}
}
puts("NO");
return 0;
}
「KDOI-06-J」贡献系统
给定一个数列 $c$,你需要给第 $i$ 个位置赋一个唯一排名 $b_i$,若 $b_i>i$,则将答案减去 $c_i$。若 $b_i<i$,则将答案加上 $c_i$,若相等,则什么都不做。求答案的最大值。
- 当 $c_i>0$ 时,我们肯定希望 $i$ 的排名上升。
- 当 $c_i<0$ 时,我们肯定希望 $i$ 的排名下降。
- 当 $c_i=0$ 时,则排名任意。
先讨论 $c_i$ 全部大于 $0$ 的情况。
显然可以让第一名掉到最后一名,其他人顺次往前移一位。但这可能不是最优方案。
但是我们可以让 $1$ 至 $i$ 名保持不变,第 $i+1$ 名掉到最后一名,$i+2$ 到 $n$ 名顺次往前移($1\le i\le n$),这才是一种最优方案。
那么 $c_i$ 全部小于 $0$ 的情况也可以照样推出来,这里直接放结论:让 $i$ 至 $n$ 名保持不变,第 $i-1$ 名升到第一名,$1$ 到 $i-2$ 名顺次往后移($1\le i\le n$)。
现在考虑正解。
我们可以将原数列分成三部分:
第一部分:$1$ 到 $x-1$,里面全是正数。
第二部分:$y+1$ 到 $n$,里面全是负数。
第三部分:$x$ 到 $y$,里面有正有负有 $0$。($1\le x\le y\le n$)
对于第一、二部分,我们可以直接套用上面的结论。
对于第三部分,$c_x$ 必然是非正数,$c_y$ 必然是非负数。我们可以先让第 $x$ 位空出来,接着让所有正数顺次往前移,所有非正数往后移,你会发现刚好能移完(自己画个图就能懂)。也就是这一部分累加 $c_i$ 的绝对值即可。
#include <bits/stdc++.h>
#define rint register int
#define int long long
#define endl '\n'
using namespace std;
const int N = 2e5 + 5;
int T, n;
int c[N], s[N];
int ans1, ans2, ans3;
int sum;
int calc(int l, int r)
{
return s[r] - s[l - 1];
}
signed main()
{
cin >> T;
while (T--)
{
ans1 = 0;
ans2 = 0;
ans3 = 0;
cin >> n;
for (rint i = 1; i <= n; i++)
{
int useless;
cin >> useless;
//这个题跟r就没关系
}
for (rint i = 1; i <= n; i++)
{
cin >> c[i];
s[i] = s[i - 1] + abs(c[i]);
}
int x = 1, y = n;
while (c[x] > 0 && x <= n) x++;
while (c[y] < 0 && y >= 1) y--;
for (rint i = 1; i < x; i++)
{
ans1 = max(ans1, calc(i + 1, x - 1) - c[i]);
}
for (rint i = n; i > y; i--)
{
ans2 = max(ans2, calc(y + 1, i - 1) + c[i]);
}
ans3 = calc(x, y);
sum = ans1 + ans2 + ans3;
cout << sum << endl;
}
return 0;
}