2023/8/15 模拟赛题解

2023/8/15 模拟赛题解

T1 Simfonija

准确来说场上只有这道是自己现做的(另外两道都是原题)。

题目链接

题意

给定两个长度为 \(n\) 的数组 \(A\)\(B\),你可以给 \(A\) 数组中的所有元素加上 \(X\)(这里 \(X\) 应该能是负数),并修改不超过 \(K\) 个元素,使得下列代数式最小:

\[\sum_{i = 1}^{n} \lvert A_i - B_i \rvert \]

思路

首先我们可以考虑 \(K = 0\) 的情况。因为是让差最小,所以我们并不关心 \(A_i\)\(B_i\) 的值,只关心他们的差值即可。我们令 \(d_i\) 表示 \(B_i-A_i\)。如果没有 \(X\),那么最后答案可以分为三部分:\(d_i < 0\)\(d_i = 0\)\(d_i>0\)。这时候,如果我们将 \(d_i\) 排序,会发现其值呈阶梯状分布,如图:

pPQpWFI.png

我们发现,答案就是这些黑线覆盖的面积。(想象一下从每一条黑线两侧延伸下来两条边,形成若干个矩形)

现在我们来考虑 \(X\)。我们发现,给 \(A\) 加上 \(X\) 相当于在上下平移这条红线,即 \(0\) 所在的位置。我们来考虑平移时面积的变化 :

pPQCFKS.png
假如红线现在向上平移,设绿色面积为 \(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;
} 
posted @ 2023-08-16 00:01  霜木_Atomic  阅读(30)  评论(0编辑  收藏  举报