test20230906考试总结
前言
这一场比赛已经彻底让我自闭了,看不起 whk 的我逐渐开始搞 whk 了,可见我有多自闭了。
赛时得分明细:
intervals | trans | ez | traffic | Total |
---|---|---|---|---|
40 | 60 | 0 | 0 | 100 |
改题明细:
intervals | trans | ez | traffic | Total |
---|---|---|---|---|
100 | 90 | 100 | 100 | 390 |
改题还算有进步的,只是效率慢了点qwq。
为了方便以后复习,所以每一道题都搬到了 luogu 上 ,由于版权问题,仅个人可见。
intervals
Problem
给定 \(n\) 个区间 \(I_1[l_1,r_1],I_2[l_2,r_2],\dots,I_n[l_n,r_n]\) 以及两个整数 \(A,B\)。保证任意一个区间都包含零点,即 \(l_i\le 0,r_i\ge 0\),从中选出若干个区间 \(I_{p_1},I_{p_2},\dots,I_{p_k}\),最大化:
Solve
分类讨论:
- 若 \(A<0,B<0\) 时,一个区间都不能选,因为一旦选了哪怕一个区间就会做负贡献了。
- 若 \(A<0,B>0\) 时,所有区间都要选,由于所有的区间都包含零点,所以所有区间都有交。现在想要让交的区间长度更小,并的区间长度更大。选上所有区间一定不会更劣。
- 若 \(A>0,B<0\) 时,至少选一个区间,但当 \(|B|>|A|\) 时,负贡献更多,那还不如不选。
- 若 \(A>0,B>0\) 时,考虑所有可能的并都能用至多两个区间表示出来,并且交集不为空。所以暴力的做法是枚举两个区间然后统计答案。
这里有一个小 trick:对于第 \(4\) 种情况来说,考虑按左端点从大到小排序,枚举一个区间,由于另一个区间肯定不能让枚举的区间所包含(设想一下,如果交了,只是交集变小了,并集不变,答案贡献只会变小,还不如不选qwq)。那么就检查所有的被包含在枚举区间的其他区间的左端点。然后取其中答案贡献最大的计算答案。
算法一
这里需要推一下式子:令 \(I_i\) 为枚举的区间,\(I_j\) 为左端点包含在 \(I_i\) 内且整体不被包含于 \(I_i\) 内的区间。则有
令 \(f(i)=Ar_i-Bl_i\) 且 \(g(i)=-Al_j+Br_j\),则答案总贡献为:
枚举 \(f(i)\),线段树去维护 \(g(j)\),再统计答案即可(记得将被包含的区间扔掉)。
算法二
可以注意到,答案是一边枚举一边计算贡献的。所以从前往后枚举,所以 \(g(j)\) 比 \(f(i)\) 要枚举到。记录一下前缀 \(g(j)\) 最大值即可。
Code
算法一:权值线段树写法
#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 998244353
#define inf 1e18
using namespace std;
inline int read() {
rint x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N = 1e5 + 10;
struct Node {
int l, r;
} a[N], b[N];
struct Seg_tree {
int l, r, mx;
} t[N * 100];
int n, len, A, B, ans = -inf, idx, rt, lmin = inf, rmin = inf, lmax = -inf, rmax = -inf;
bool cmp(Node x, Node y) {
return (x.l == y.l ? x.r > y.r : x.l < y.l);
}
void pushup(int p) {
t[p].mx = max(t[t[p].l].mx, t[t[p].r].mx);
}
void up(int &p, int l, int r, int k, int val) {
if(!p) p = ++idx, t[p].mx = -inf;
if(l == r) {
t[p].mx = max(t[p].mx, val);
return ;
}
int mid = (l + r) >> 1;
if(k <= mid) up(t[p].l, l, mid, k, val);
else up(t[p].r, mid + 1, r, k, val);
pushup(p);
}
int ask(int p, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) {
return t[p].mx;
}
int mid = (l + r) >> 1, ans = -inf;
if(ql <= mid) ans = max(ans, ask(t[p].l, l, mid, ql, qr));
if(qr > mid) ans = max(ans, ask(t[p].r, mid + 1, r, ql, qr));
return ans;
}
signed main() {
n = read();
For(i,1,n) a[i] = (Node){read(), read()};
For(i,1,n) {
lmin = min(lmin, a[i].l);
lmax = max(lmax, a[i].l);
rmin = min(rmin, a[i].r);
rmax = max(rmax, a[i].r);
}
A = read(), B = read();
sort(a + 1, a + n + 1, cmp);
int maxn = -inf;
For(i,1,n) {
if(maxn < a[i].r) a[++len] = a[i];
maxn = max(maxn, a[i].r);
}
n = len;
ans = max(0ll, A * (rmin - lmax + 1) + B * (rmax - lmin + 1));
For(i,1,n) {
up(rt, -1e9, 1e9, a[i].l, (-a[i].l * A + B * a[i].r));
}
For(i,1,n) {
ans = max(ans, ask(rt, -1e9, 1e9, a[i].l, a[i].r) + a[i].r * A - B * a[i].l + A + B);
}
cout << ans << '\n';
return 0;
}
算法二:前缀 Max 写法
#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 998244353
#define inf 1e18
using namespace std;
inline int read() {
rint x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N = 1e5 + 10;
struct Node {
int l, r;
} a[N];
int n, len, A, B, ans = 0, lmin = inf, rmin = inf, lmax = -inf, rmax = -inf, maxn;
bool cmp(Node x, Node y) {
return (x.l == y.l ? x.r > y.r : x.l < y.l);
}
signed main() {
n = read();
For(i,1,n) a[i] = (Node){read(), read()};
A = read(), B = read();
For(i,1,n) {
lmin = min(lmin, a[i].l);
lmax = max(lmax, a[i].l);
rmin = min(rmin, a[i].r);
rmax = max(rmax, a[i].r);
maxn = max(maxn, a[i].r - a[i].l + 1);
}
if(A <= 0 && B <= 0) ans = 0;
else if(A <= 0 && B > 0) ans = A * (rmin - lmax + 1) + B * (rmax - lmin + 1);
else if(A > 0 && B <= 0) ans = max(ans, (A + B) * maxn);
else {
sort(a + 1, a + n + 1, cmp);
int maxi = -inf, maxx = -inf;
For(i,1,n) {
if(maxx < a[i].r) a[++len] = a[i];
maxx = max(maxx, a[i].r);
}
n = len;
For(i,1,n) {
maxi = max(maxi, A * a[i].r - B * a[i].l);
ans = max(ans, maxi + A * (-a[i].l) + B * a[i].r + A + B);
}
}
cout << ans << '\n';
return 0;
}
trans
Problem
给定一个长度为 \(n\) 的非负数列 \(a_i\),每一次操作可以将相邻两项合并,合并之后的元素为两个元素之和,并将序列大小 \(-1\)。求将序列变为非降序列所需要的最少合并次数。
Solve
考虑 dp。
设 \(dp_i\) 表示将前 \(i\) 项变为非降序列所需要的最少合并次数,\(dp2_i\) 表示将前 \(i\) 项变为非降序列后第 \(i\) 项的最小值,\(sum_i\) 表示 \(a_i\) 的前缀和。则有:
时间复杂度 \(O(n^2)\)。
还没完,由于在 \(dp_i\) 最小的同时要满足 \(dp2_i\) 最小,而 \(sum_i-sum_j\) 是从左往右单调递增的,所以只要倒序枚举 \(j\),当 \(dp2_j\le sum_i-sum_j\) 时,直接更新答案,然后 break。
这样可以多过一个点,拿到 \(90pts\) 的好成绩。
Code
以下时 \(90pts\) 的代码:
#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 998244353
using namespace std;
inline int read() {
rint x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N = 3e6 + 10;
int n, a[N], sum[N], dp[N], dp2[N], lastj;
signed main() {
n = read();
For(i,1,n) a[i] = read(), sum[i] = sum[i-1] + a[i];
memset(dp, 0x3f, sizeof dp);
memset(dp2, 0x3f, sizeof dp2);
dp[0] = 0, dp2[0] = 0;
For(i,1,n) {
FOR(j,i-1,lastj) {
int x = sum[i] - sum[j];
if(x >= dp2[j]) {
dp[i] = dp[j] + i - j - 1;
dp2[i] = x;
lastj = j;
cout << j << '\n';
break;
}
}
For(j,lastj,i-1) cout << dp2[j] << ' ';
cout << '\n';
}
cout << dp[n] << '\n';
return 0;
}
ez
Problem
给出 \(k\) 个大小为 \(k\) 的集合,集合中的每个元素带权。一个合法的选择方案从每个集合中选出恰好一个元素, 且获得这 \(k\) 个元素的总权值作为方案的代价。
显然, 一共有 \(k^{k}\) 种合法的选择方案。求出这些方案中前 \(k\) 小的代价是多少。
Solve
正解不太会,但是可以用暴力创!!
操作以每一行为基准,每一次从优先队列里取出前 \(k\) 大的数,并保留下来,其他的数弹出舍弃,然后将这些数与这一行的所有数两两匹配求和,放入优先队列中,然后进入下一行,以此类推。
这样最后优先队列里的数一定是前 \(k\) 大的。
时间复杂度 \(O(k^3\log k)\),可以过(就很神奇。
Code
#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 998244353
using namespace std;
inline int read() {
rint x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N = 6e2 + 10, M = 3e6 + 10;
int n, a[N][N];
signed main() {
while(scanf("%d", &n) != EOF) {
priority_queue<int> q;
vector<int> p;
For(i,1,n) For(j,1,n) a[i][j] = read();
For(i,1,n) {
if(i == 1) {
For(j,1,n) q.push(a[i][j]);
continue;
}
p.clear();
while(!q.empty()) {
p.push_back(q.top()); q.pop();
}
For(j,1,n) {
FOR(k,n-1,0) {
int x = p[k] + a[i][j];
if(q.size() != n) q.push(x);
else if(q.top() > x) q.pop(), q.push(x);
else break;
}
}
}
p.clear();
while(!q.empty()) {
p.push_back(q.top()); q.pop();
}
FOR(i,n-1,0) cout << p[i] << ' ';
cout << '\n';
}
return 0;
}
traffic
Problem
给定一张由 \(n\) 个点 \(m\) 条边构成的图,每条边有一个单向的流量,每一个点流入的流量之和等于流出的流量之和。现在你可以调查一条边的状态,代价为 \(w_i\)(可以是负数)。需要最小化代价之和,并直接或间接得到所有边的状态。
Solve
正难则反,考虑保留(不调查)哪些边可以推出所有边。
通过手模样例可得,边的状态(连边方向)与是否能推出边无关,只在乎一个点的入度和出度。
可以发现,假如保留的边形成了环,则其一定不合法。换句话说,保留的边不能有环。大胆猜测其形态为一棵树。
现在是要被调查的边,也就是被删除的边的代价尽可能小,那么,保留的边的代价要尽可能大。所以,大胆猜测其为最大生成树。
最后答案为总代价之和减去最大生成树的代价之和。
由于负代价边选了一定不会劣,所以要全选上,所以保留的边所组成的图为最大生成树森林。求最大生成树的时候要将负代价边去掉,图不一定连通。
\(Kruskal\) 板子,时间复杂度 \(O(n\log n)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define mod 998244353
using namespace std;
inline int read() {
rint x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N = 5e5 + 10;
struct Node {
int u, v, w;
} e[N];
int n, m, f[N], ans, tot, W;
int find(int x) {
return (x == f[x] ? x : f[x] = find(f[x]));
}
bool cmp(Node x, Node y) {
return x.w > y.w;
}
void kurskal() {
sort(e + 1, e + m + 1, cmp);
For(i,1,m) {
int u = e[i].u, v = e[i].v;
if(find(u) == find(v)) continue;
f[find(u)] = find(v);
tot++;
ans += e[i].w;
if(tot == n-1) break;
}
}
signed main() {
n = read(), m = read();
For(i,1,n) f[i] = i;
For(i,1,m) {
int u = read(), v = read(), w = read();
W += w;
if(w > 0) e[i] = (Node){u, v, w};
}
kurskal();
cout << W - ans << '\n';
return 0;
}