2025ccpc南昌邀请赛个人题解
vp25cc邀请南昌
后面会补图论的(不会QAQ)
G(模拟)
数目最多, 大于1的奇数, 那就是先分3块最多, n%3 == 1时候, 就得俩个三合在一起也就是7才行, n % 3 == 2那就是3 + 2 = 5即可
inline void solve(){
int n;
cin>>n;
if(n<3||n==4)cout<<"-1"<<endl;
else{
int m=n%3;
if(m==0||m==2)cout<<n/3<<endl;
else cout<<n/3-1<<endl;
}
}
L(模拟)
模拟每种状态就好, if判断一下就可以了
int a,b;
cin>>a>>b;
if (a>=21&&a-b>=2||a==30) {
cout<<"Alice\n";
}
else if (b>=21&&b-a>=2||b==30) {
cout<<"Bob";
}
else {
cout<<"Underway";
}
A(二分答案)
二分答案, 去check是否可以满足即可, 每一个留1个就可以, 其他的都去换, 就好了
template<typename T, typename M>
T get_first_math(T lo, T hi, M math) {
while (lo <= hi) {
T mid = lo + (hi - lo) / 2;
if (math(mid)) {
hi = mid - 1;
} else {
lo = mid + 1;
}
}
return lo;
}
inline void solve() {
int n;
i64 k;
cin >> n >> k;
vector<i64> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a.begin() + 1, a.end(), greater<i64>());
vector<i64> pre(n + 1);
for (int i = 1; i <= n; i++) {
pre[i] = pre[i - 1] + a[i];
}
i64 sum = pre[n];
auto check = [&](i64 m) -> bool {
int d = lower_bound(pre.begin() + 1, pre.end(), m) - pre.begin();
return m + (k - 1) * d >= k * n;
};
i64 ans = get_first_math<i64>(0, sum, check);
cout << ans << endl;
}
K(思维)
让每次%4的值不为1即可, 那就是存每个数%4的数字就好, 然后记录{0, 1, 2, 3}都有多少个, 那么我们尽量去配对4就好了, 先用 3 1 消掉一部分,剩下的只会是 {1,2} 或 {3,2} 或 {2}。
剩下的 1 必须两个一组,用一个 2 消掉;剩下的 3 也必须两个一组,用一个 2 消掉;最后剩下的 2 必须两个一组。
inline void solve() {
int n;
cin >> n;
vector<vector<int>> id(4);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
id[x % 4].push_back(i);
}
vector<int> ans;
auto get = [&](int r) {
int x = id[r].back();
id[r].pop_back();
return x;
};
// 余数 0 放在最前面,前缀余数仍然是 0
while (!id[0].empty()) {
ans.push_back(get(0));
}
// 先尽量配 3 1
while (!id[3].empty() && !id[1].empty()) {
ans.push_back(get(3));
ans.push_back(get(1));
}
// 如果剩下 1,就用 2 1 1 消掉
while (id[1].size() >= 2 && !id[2].empty()) {
ans.push_back(get(2));
ans.push_back(get(1));
ans.push_back(get(1));
}
// 如果剩下 3,就用 3 3 2 消掉
while (id[3].size() >= 2 && !id[2].empty()) {
ans.push_back(get(3));
ans.push_back(get(3));
ans.push_back(get(2));
}
// 最后剩下的 2,只能 2 2 配
while (id[2].size() >= 2) {
ans.push_back(get(2));
ans.push_back(get(2));
}
if (!id[1].empty() || !id[2].empty() || !id[3].empty()) {
cout << -1 << endl;
return;
}
for (int i = 0; i < n; i++) {
cout << ans[i] << " \n"[i == n - 1];
}
}
H(思维, 后缀最小值)
把序列变成a_i - i, 此时的答案就是sum_{1 ...n} + su{a_i - i}, 然后对序列a_i - i进行操作, 然后从后往前走,找到第一个比他小的数字, 这些区间可以进行减少操作(他和比他小的数字的差 * 数字的个数)(其实就是维护后缀最小值就好了)
inline void solve() {
int n;
cin >> n;
vector<i64> a(n + 1), c(n + 2), mn(n + 2);
i64 sum = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
c[i] = a[i] - i;
if (c[i] < 0) {
cout << -1 << endl;
return;
}
sum += c[i];
}
mn[n + 1] = (i64)1e18;
for (int i = n; i >= 1; i--) {
mn[i] = min(mn[i + 1], c[i]);
}
i64 cur = 0;
i64 pool = 0;
i64 save = 0;
for (int i = 1; i <= n; i++) {
save += cur;
pool += c[i] - cur;
if (i < n) {
i64 can = mn[i + 1] - cur;
i64 d = min(pool, can);
pool -= d;
cur += d;
}
}
cout << n + sum - save << endl;
}
C(双指针+线段树)
可以转换为一个经典问题就是区间覆盖, 然后每个位置覆盖的数目不能超过k个, 就是用线段树维护就好了, 懒标记就是正常+ 1就好, 然后Node就是维护区间最大值就好,每次给这个右点放进去,看这个区间是里面的最大值,是否大于k, 如果大于k那么左指针就往后移动就好, 直到小于等于k就好, 每次记录一个max就好, 时间复杂度是O(nlogn)可以通过
struct Node {
int v = 0;
Node operator+(const Node& rhs) { return {max(v, rhs.v)}; }
template <typename Tag>
void apply(const Tag& tag) {
v += tag;
}
};
inline void solve() {
int n, k;
cin >> n >> k;
vector<pair<int, int>> a(n);
for (auto& [l, r] : a) {
cin >> l >> r;
}
seg_tree<Node, true, i64> seg(n + 1);
int ans = 0;
int l = 0;
for (int r = 0; r < n; r++) {
auto [ql, qr] = a[r];
seg.update(ql, qr, {1});
while (seg.top().v > k) {
auto [pl, pr] = a[l];
seg.update(pl, pr, {-1});
l++;
}
ans = max(ans, r - l + 1);
}
cout << ans << endl;
return;
}
D(最小生成树)
E(公式推导 + 线性转换)
-
对于一个同色线段, 左点为\(col_{i, j}\) 右点为\(col_{i, j + 1}\)表示
i颜色的第j个点, 那么对于他来说一个颜色的贡献为\(sum[y]_{in} * sum[y]_{out}\)
\(sum[y]_{out} = sum[y] - sum[y]_{in}\)
\(sum[y]_{in} * (sum[y] - sum[y]_{in})\)
其中\(sum[y]\)是固定的值, 然后\(sum[y]_{in}\)可以用前缀和快速求得
也就是\(sum[y]_{in} = pre[y][r] - pre[y][l - 1]\)
-
那么对于颜色
x所有的点都可以俩俩配对形成线段, 当你枚举到当前点j那么之前所有同色点i对答案的贡献就是\(res = \sum_{i}^{i\in x} a[i] * a[j] * sum[y]_{in} * (sum[y] - sum[y]_{out})\)
为了方便公式推导我们令
\(pj = pre[y][j]\)
\(pi = pre[y][i]\)
\(sy = sum[y]\)
原公式变为
\(res = \sum_{i}^{i\in x} a[i] * a[j] * (pj - pi) * (sy - pj + pi)\)
\(res = a[j] * \sum_{i}^{i\in x} a[i] * (sy * pj - pj^2 + 2 * pj * pi - sy * pi - pi^2 )\)
\(res = a[j] * \sum_{i}^{i\in x} a[i] * (sy * pj - pj^2 -pi^2 + pi *(2pj - sy))\)
\(res = a[j]* (sy*pj - pj^2) * \sum_{i}^{i\in x}a[i] - \sum_{i}^{i\in x}a[i]*pi^2 +(2pj - sy)* \sum_{i}^{i\in x}a[i] * pi\)
那就是维护 \(\sum_{i}^{i\in x}a[i]\) \(\sum_{i}^{i\in x}a[i]*pi^2\) \(\sum_{i}^{i\in x}a[i]*pi\)这三个就好了
考虑用开一个颜色维护区间就好了\(sum[x][0]\)去维护第一个, 然后其他维护第二个......
伪代码
for j = 1...n: x = c[j] // 先统计以 j 作为右端点的贡献 for y = 1...k: if y == x: continue pj = pre[y] syy = sy[y] cur = (pj * syy - pj * pj) * sum0[x] + (2 * pj - syy) * sum1[x][y] - sum2[x][y] ans += a[j] * cur // 再把当前点加入前缀 pre[x] += a[j] // 当前点 j 以后可以作为左端点 i sum0[x] += a[j] for y = 1...k: pi = pre[y] sum1[x][y] += a[j] * pi sum2[x][y] += a[j] * pi * pi -
最后一步 ans / 2
why?
如果有两条相交异色线段:
线段 A:颜色 x
线段 B:颜色 y当我们枚举线段 A 时,会把线段 B 算进去一次。
当我们枚举线段 B 时,也会把线段 A 算进去一次。
所以每一对相交异色线段会被统计两次。
因此最后答案要除以
2:ans = ans * inv2 % mod;
ok 那么来写代码
const int MOD = 998244353;
const int INV2 = 499122177;
inline int addmod(int a, int b) {
a += b;
if (a >= MOD) a -= MOD;
return a;
}
inline int submod(int a, int b) {
a -= b;
if (a < 0) a += MOD;
return a;
}
inline int mulmod(i64 a, i64 b) { return a * b % MOD; }
inline void solve() {
int n, k;
cin >> n >> k;
vector<int> c(n);
vector<int> a(n);
for (auto& x : c) cin >> x;
for (auto& x : a) cin >> x;
int ans = 0;
vector<int> pre(k + 10), sy(k + 10), sum0(k + 10);
vector<vector<int>> sum1(k + 10, vector<int>(k + 10, 0));
auto sum2 = sum1;
for (int i = 0; i < n; i++) {
sy[c[i]] = addmod(sy[c[i]], a[i]);
}
for (int i = 0; i < n; i++) {
int x = c[i];
for (int y = 1; y <= k; y++) {
if (y == x) continue;
int pj = pre[y];
int syy = sy[y];
int A = mulmod(pj, submod(syy, pj));
int B = submod(addmod(pj, pj), syy);
int cur = 0;
cur = addmod(cur, mulmod(A, sum0[x]));
cur = addmod(cur, mulmod(B, sum1[x][y]));
cur = submod(cur, sum2[x][y]);
ans = addmod(ans, mulmod(a[i], cur));
}
pre[x] = addmod(pre[x], a[i]);
sum0[x] = addmod(sum0[x], a[i]);
for (int y = 1; y <= k; y++) {
int pi = pre[y];
sum1[x][y] = addmod(sum1[x][y], mulmod(a[i], pi));
sum2[x][y] = addmod(sum2[x][y], mulmod(mulmod(a[i], pi), pi));
}
}
ans = mulmod(ans, INV2);
cout << ans << endl;
}

浙公网安备 33010602011771号