前言
题解
VP了下这个赛道的国赛,感觉还是能AK的。
涉及的知识点,还是蛮多的,但是大部分点到为止。
压轴的题出得不错,但还是偏板子。
小码哥的滞销
难度: 钻石
思路: 反悔贪心
很典的反悔贪心,根据时间排序,然后用高价值替换低价值。
#include<bits/stdc++.h>
using namespace std;
struct Tx {
int w, t;
};
int main( )
{
int n;
vector<Tx> arr;
cin >> n;
for (int i = 0; i < n; i++) {
int w, t;
cin >> w >> t;
arr.push_back({w, t});
}
sort(arr.begin(), arr.end(), [&](auto &a, auto &b) {
return a.t < b.t;
});
long long res = 0;
priority_queue<int, vector<int>, greater<int> > pq;
for (auto &e: arr) {
if (e.t > pq.size()) {
res += e.w;
pq.push(e.w);
} else {
if (!pq.empty() && e.w > pq.top()) {
res -= pq.top();
res += e.w;
pq.pop();
pq.push(e.w);
}
}
}
cout << res << endl;
return 0;
}
小兔子乖乖,把门开开
难度: 钻石
思路: 单调队列+前缀和
关键词: 不超过k个元素的连续子数组,求最优解(最大/最小)
看到这些的时候,往往和单调队列能关联上,事实也是如此。
#include<bits/stdc++.h>
using namespace std;
int main( )
{
int n, m;
cin >> n >> m;
vector<int> arr(n);
for (int &x: arr) cin >> x;
vector<long long> pre(n + 1, 0);
for (int i = 0; i < n; i++) {
pre[i + 1] = pre[i] + arr[i];
}
long long res = -0x3f3f3f3f;
deque<int> deq;
deq.push_back(-1);
for (int i = 0; i < n; i++) {
if (!deq.empty() && i - deq.front() > m) {
deq.pop_front();
}
long long cur = pre[i + 1];
while (!deq.empty() && pre[deq.back() + 1] >= cur) {
deq.pop_back();
}
deq.push_back(i);
if (deq.size() == 1) {
res = max(res, (long long)arr[i]);
} else {
res = max(res, pre[i + 1] - pre[deq.front() + 1]);
}
}
cout << res << endl;
return 0;
}
出题人的烦恼
难度: 钻石
思路: 并查集 + 拓扑排序找环
图论综合题
- 增量构造的时候,判断构建环的时机
- 提取环
确切地讲,就是独立的点,逐渐合并,呈现森林化,直到其中一棵树(也是唯一)出现了环。
#include <bits/stdc++.h>
using namespace std;
class Dsu {
private:
int n;
vector<int> arr;
public:
Dsu(int n): n(n), arr(n + 1, 0) {}
void merge(int u, int v) {
int fu = find(u);
int fv = find(v);
if (fu != fv) {
arr[fu] = fv;
}
}
bool same(int u, int v) {
return find(u) == find(v);
}
int find(int u) {
if (arr[u] == 0) {
return u;
}
return arr[u] = find(arr[u]);
}
};
int main() {
int n, m;
cin >> n >> m;
vector<int> du(n + 1, 0);
vector<vector<int> > g(n + 1, vector<int>());
Dsu dsu(n);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
du[u]++;
du[v]++;
g[u].push_back(v);
g[v].push_back(u);
if (!dsu.same(u, v)) {
dsu.merge(u, v);
} else {
break;
}
}
// 拓扑排序
deque<int> que;
for (int i = 1; i <= n; i++) {
if (du[i] == 1) {
que.push_back(i);
}
}
while (!que.empty()) {
int u = que.front();
que.pop_front();
vector<int> &arr = g[u];
int nv = arr.size();
for (int i = 0; i < nv; i++) {
int v = arr[i];
if (--du[v] == 1) {
que.push_back(v);
}
}
}
vector<int> res;
for (int i = n; i >= 1; i--) {
if (du[i] == 2) {
res.push_back(i);
}
}
int k = res.size();
for (int i = 0; i < k; i++) {
cout << res[i] << " \n"[i == k - 1];
}
return 0;
}
区间数据处理
难度: 星耀
思路: 离散化 + 树状数组
其实具体的实现还是多种思路
- 莫队
- 离线+拆成前后操作序列
#include<bits/stdc++.h>
using namespace std;
class Fenwick {
public:
Fenwick(int n): n(n), arr(n + 1, 0) {}
int query(int p) {
int r = 0;
while (p > 0) {
r += arr[p];
p -= p & -p;
}
return r;
}
void update(int p, int d) {
while (p <= n) {
arr[p] += d;
p += p & -p;
}
}
private:
int n;
vector<int> arr;
};
int main( )
{
int n, q;
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> q;
set<int> st;
vector<array<int, 4>> ops;
for (int i = 1; i <= n; i++) {
int v;
cin >> v;
st.insert(v);
ops.push_back({0, i, v, 0});
}
for (int i = 0; i < q; i++) {
int l, r, x;
cin >> l >> r >> x;
ops.push_back({1, l - 1, i, x});
ops.push_back({2, r, i, x});
st.insert(x);
}
map<int, int> hp;
int ptr = 0;
for (int k: st) {
hp[k] = ++ptr;
}
vector<int> res(q, 0);
Fenwick bit(ptr);
sort(ops.begin(), ops.end(), [&](auto &a, auto &b) {
if (a[1] != b[1]) return a[1] < b[1];
return a[0] < b[0];
});
for (auto &op: ops) {
if (op[0] == 0) {
bit.update(hp[op[2]], 1);
} else if (op[0] == 1) {
res[op[2]] -= (bit.query(ptr) - bit.query(hp[op[3]] - 1));
} else {
res[op[2]] += (bit.query(ptr) - bit.query(hp[op[3]] - 1));
}
}
for (auto x: res) {
cout << x << '\n';
}
return 0;
}
破解宝物封印
难度: 钻石
思路: 组合数学 + 逆元 + 费马小定律
很板的一道基础数论题,属于那种会做的人很容易,没学过的人绝对不会。
本质上
C ( n , k ) = n ! / ( k ! ∗ ( n − k ) ! ) C(n, k) = n! / (k! * (n - k)!) C(n,k)=n!/(k!∗(n−k)!)
要么用大数计算,但是大数会非常的慢。
如果在模数前提下,是可以引入逆元的概念,来线性处理。
但是顺便吐槽下,为何模数是 p = 1 0 8 + 7 p = 10^8 + 7 p=108+7
因为一般模数约定为 1 0 9 + 7 或者 998244353 10^9+7或者998244353 109+7或者998244353,我更相信是出题人笔误了,然后将错就错,T_T.
#include<bits/stdc++.h>
using namespace std;
using int64 = long long;
int64 ksm(int64 b, int64 v, int64 p)
{
int64 r = 1;
while (v > 0) {
if ((v & 1) == 1) {
r = r * b % p;
}
b = b * b % p;
v /= 2;
}
return r;
}
int main()
{
int64 mod = (int)1e8 + 7;
int n, m;
cin >> n >> m;
vector<int64> fac(n + 1, 0);
vector<int64> inv(n + 1, 0);
fac[0] = 1;
for (int i = 1; i <= n; i++) {
fac[i] = fac[i - 1] * i % mod;
}
// 费马小定律
inv[n] = ksm(fac[n], mod - 2, mod);
for (int i = n - 1; i >= 0; i--) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
int64 ans = fac[n] * inv[m] % mod * inv[n - m] % mod;
cout << ans << endl;
return 0;
}
数字游戏
难度: 星耀
思路: lazy 线段树
很板的一道题