2023/8/15 模拟赛题解
2023/8/15 模拟赛题解
T1 Simfonija
准确来说场上只有这道是自己现做的(另外两道都是原题)。
题意
给定两个长度为 \(n\) 的数组 \(A\) 和 \(B\),你可以给 \(A\) 数组中的所有元素加上 \(X\)(这里 \(X\) 应该能是负数),并修改不超过 \(K\) 个元素,使得下列代数式最小:
思路
首先我们可以考虑 \(K = 0\) 的情况。因为是让差最小,所以我们并不关心 \(A_i\) 和 \(B_i\) 的值,只关心他们的差值即可。我们令 \(d_i\) 表示 \(B_i-A_i\)。如果没有 \(X\),那么最后答案可以分为三部分:\(d_i < 0\),\(d_i = 0\),\(d_i>0\)。这时候,如果我们将 \(d_i\) 排序,会发现其值呈阶梯状分布,如图:
我们发现,答案就是这些黑线覆盖的面积。(想象一下从每一条黑线两侧延伸下来两条边,形成若干个矩形)
现在我们来考虑 \(X\)。我们发现,给 \(A\) 加上 \(X\) 相当于在上下平移这条红线,即 \(0\) 所在的位置。我们来考虑平移时面积的变化 :
假如红线现在向上平移,设绿色面积为 \(s_1\),蓝色面积为 \(s_2\),两条黑线 \(d_1\) 与 \(d_2\) 交界处为 \(mid\),我们发现,由于 \(mid\) 偏左,故向上平移时,左侧面积的增加量要小于右侧面积的减少量,所以向上平移答案会更优;同理,如果 \(mid\) 偏右,结果相反。不难想到,\(mid\),也就是 \(0\) 值的位置,应该位于一个靠中间的位置。而这个位置就是中位数所在的位置。
但是我们还有 \(K\) 个数可以改动。显然,每次改动一定是让某个 \(B_i\) 等于 \(A_i\),也就是让 \(d_i\) 为 \(0\)。另一个显然的事实是,无论 \(mid\) 选在哪里,需要改动的 \(d_i\) 一定是左右两侧连续的几个。那么,我们可以枚举左侧选 \(i\) 个,那么右侧就是 \(K-i\) 个。每次的中位数是单调不减的。我们只需要处理好每次改变 \(0\) 值点后答案的变化即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
const int M = 2e6+100, del = 2000000;
inline int read(){
int 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*10+ch-48, ch = getchar();}
return x * f;
}
int n, K;
int a[N], b[N], d[N];
int e[N];
int ed[N];
int id[M<<1], idx;
int main(){
// freopen("simfonija.in", "r", stdin);
// freopen("simfonija.out", "w", stdout);
n = read(), K = read();
for(int i = 1; i<=n; ++i){
a[i] = read();
}
for(int i = 1; i<=n; ++i){
b[i] = read();
}
for(int i = 1; i<=n; ++i){
d[i] = b[i] - a[i];
}
sort(d+1, d+n+1);
for(int i = 1; i<=n; ++i){
if(i == n || d[i+1]!=d[i]){
id[d[i]+del] = ++idx;
ed[idx] = i;
}//预处理每段黑边的右端点。
}
int len = (n+1-K)/2;
int X = d[len];
long long ans = 0;
for(int i = 1; i<=(n-K); ++i){
ans+=abs(d[i]-X);
}//左侧不选的答案
int lst = X;
long long ret = ans;
for(int i = 1; i<=K; ++i){
int nX = d[i+len];
ans+= abs(d[n-K+i]-lst);//加上右侧弃选后增加的答案
ans-= abs(d[i]-lst);//减去左侧选后减少的答案
if(nX != lst){//如果中位数有变化,则 0 值线升高,要处理面积变化。
ans-=(1ll*((n-K+i)-ed[id[lst+del]]) * (nX-lst));
ans+=(1ll*(nX-lst)*(ed[id[lst+del]] - i));
lst = nX;
}
ret = min(ret, ans);//答案取最小的
}
printf("%lld\n", ret);
return 0;
}
T2 network (专业网络)
贪心,是某次的作业题。考虑按 \(a_i\) 从大到小枚举每个人,我们假定枚举到一个人 \(i\) 后,后面的人都要交往,这样就可以不花费任何代价来交到 \(i\) 这个朋友。如果无法满足,则从前面枚举过的人中挑代价最小的交往来满足限制。我们发现只有当 \(a_i=a_{i-1}\) 的时候才会出现不满足的情况,而这时候只需从前 \(i\) 个人(包括他自己)中挑最多一个花代价交往即可,所以不会出现多花代价的情况。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5+100;
inline int read(){
int x = 0;char ch = getchar();
while(ch<'0' || ch>'9') ch = getchar();
while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar();
return x;
}
struct node{
int a, b;
bool operator < (const node &y) const{
return b > y.b;
}
}p[N];
bool cmp(node x, node y){
if(x.a == y.a){
return x.b < y.b;
}
return x.a > y.a;
}
priority_queue<node> q;
int n;
int main(){
// freopen("network.in", "r", stdin);
// freopen("network.out", "w", stdout);
n = read();
for(int i = 1; i<=n; ++i){
p[i] = (node){read(), read()};
}
sort(p+1, p+n+1, cmp);
ll ans = 0, now = 0;
for(int i = 1; i<=n; ++i){
q.push(p[i]);
while(!q.empty() && n-i+now < p[i].a){
ans+=q.top().b;
q.pop();
++now;
}
}
printf("%lld\n", ans);
return 0;
}
T3 kangaroo
某天随机随到的题,看到有意思,而且思路新奇就做了,没想到后面这种题还挺多,更没想到今天还做到了……
具体见这篇博客
T4 korale
场上没时间做了 /kk
第一问参考超级钢琴做法,只有一个较小的贡献去掉后才加入较大的贡献,以保证枚举的连续性。
第二问考虑爆搜搜索。用线段树维护一个区间最靠左的最小值,尝试拿这个来组成答案,以保证字典序最小。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6+100;
inline int read(){
int x = 0; char ch = getchar();
while(ch<'0' || ch>'9') ch = getchar();
while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar();
return x;
}
int a[N], b[N];
int n, K;
struct Segment_Tree{
int tree[N<<2];
#define ls tr<<1
#define rs tr<<1 | 1
void build(int tr, int L, int R){
if(L == R){
return tree[tr] = a[L], void();
}
int mid = (L+R) >> 1;
build(ls, L, mid);
build(rs, mid+1, R);
tree[tr] = min(tree[ls], tree[rs]);
}
int query(int tr, int L, int R, int lq, int rq, ll val){
if (tree[tr] > val) return 0 ;
if(lq <= L && R <= rq){
if(tree[tr] > val) return 0;
if(L == R) return L;
}
int mid = (L+R) >> 1, pos;
if(lq <=mid){
pos = query(ls, L, mid, lq, rq, val);
if(pos) return pos;
}
return query(rs, mid+1, R, lq, rq, val);
}
}segt;
ll ans = 0, cnt = 1;
void write(int x){
if(x >= 10){
write(x/10);
}
putchar(x%10+48);
}
int p[N], tot;
void dfs(int pos, ll rst){
if(rst == 0){
--cnt;
if(!cnt){
printf("%lld\n", ans);
for(int i = 1; i<=tot; ++i){
write(p[i]);
putchar(' ');
}
putchar('\n');
exit(0);
}
}
for(int i = pos+1; i<=n; ++i){
i = segt.query(1, 1, n, i, n, rst);
if(!i) return;
p[++tot] = i;
dfs(i, rst-a[i]);
--tot;
}
}
struct xwx{
int id;
ll sum;
bool operator < (const xwx &y) const{
return sum > y.sum;
}
};
priority_queue<xwx> q;
int main(){
n = read(), K = read();
for(int i = 1; i<=n; ++i){
a[i] = b[i] = read();
}
sort(b+1, b+n+1);
q.push((xwx){1, b[1]});
if(K == 1){
puts("0");
return 0;
}
for(int i = 2; i<=K; ++i){
xwx tmp = q.top();
if(tmp.sum!=ans){
ans = tmp.sum, cnt = 1;
} else{
++cnt;
}
q.pop();
if(tmp.id<n){
q.push((xwx){tmp.id+1, tmp.sum-b[tmp.id]+b[tmp.id+1]});
q.push((xwx){tmp.id+1, tmp.sum+b[tmp.id+1]});
}
}
segt.build(1, 1, n);
dfs(0, ans);
return 0;
}