NOIP模拟19/07/22

本文解析了几道编程竞赛题目,包括字符处理题、棋盘问题和数据结构题。通过对题目的详细解答,展示了如何使用不同的数据结构和技术来解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字符处理

[题目描述]

妈妈的工作是英语翻译,经常和英语字符串打交道,今天妈妈给了 Tom 一个只有小写字 母构成的字符串,需要 Tom 做以下工作:

要是连续出现相同的小写字母,则把他们替换成这个字母的大写形式,后面紧跟相同字 母的个数,并把它之前跟之后的两段字串调

换,例如出现 bcaaaaaaef,则新字符串变成: efA6bc,然后重新扫描字串,直到没有出现连续相同的小写字母为止。

Tom 觉得自己字符串部分没有学好,请你帮帮他。

[输入格式]

输入一行小写字母构成的原始字符串(字符串长度不大于 250)。

[输出描述] 按妈妈要求输出新的字符串。

[输入样例 1] bcaaaaaaef    [输出样例 1]   efA6bc

[输入样例 2] cmmmcefffg    [输出样例 2]   gM3cF3c


题解:模拟,然后因为自己不仔细挂掉了50分,因为当长度大于10的时候,不能直接转

以后还是要花时间检查一下前面的题,挂分太可怕了

#include<bits/stdc++.h>
#define N 300
using namespace std;
char a[N], b[N];
int lena, lenb, sta[N];
void calc(int pos, int len){
	if(len < 2) return;
	int l = pos - len, r = pos - 1;
	lenb = 0;
	for(int i=r+1; i<=lena; i++){
		b[++lenb] = a[i];
	}
	b[++lenb] = (a[r] - 'a' + 'A');
	int top = 0;
	while(len) sta[++top] = len % 10, len /= 10;
	while(top) b[++lenb] = (sta[top] + '0'), top--;
	for(int i=1; i<=l-1; i++){
		b[++lenb] = a[i];
	} 
	for(int i=1; i<=lenb; i++) a[i] = b[i];
	lena = lenb;
}
int main(){
	scanf("%s", a+1); lena = strlen(a+1);
	a[0] = '&';
	int len = 0; 
	die:;
	for(int i=1; i<=lena; i++){
		if(a[i] == a[i-1]) len++;
		else{
			if(len == 0) continue;
			calc(i, len + 1);
			len = 0; goto die;
		}
	}
	for(int i=1; i<=lena; i++) printf("%c", a[i]);
	return 0;
}

棋盘问题

[题目描述]小 O 对国际象棋有着浓厚的兴趣,因为他水平高超,每次人机对战他总是轻松获胜,所以他决定自己跟自己下国

际象棋。 小 O 的棋盘非常大,达到了 10^9,现在他在棋盘上摆放了 n 个国王,并对你提出 了 q 次询问,每次询问指定一个坐

标,问将所有国王从初始位置全部移动到这个坐标所需要 的最小步数是多少,询问之间相互独立,也就是说每次询问结束后国王

会全部回到原来位置。

注意:由于小 O 担心大家无法理解过于高深的规则,所以在本题中,国王之间不会发生 相互攻击而且多个国王可以同时处在一

个格子中, 国际象棋中国王一步只能移动到与其八 连通的格子中。

[输入格式] 第一行一个正整数 T 表示数据组数。 对于每组数据,共有(n+q+1)行: 第一行两个数字 n,q 分别表示国王数量和

询问数量。 接下来 n 行,每行两个数字 Kxi,Kyi 表示国王所在坐标。 接下来 q 行,每行两个数字 Txi,Tyi 表示目标坐标。

[输出格式] 对于每组数据,输出共有 q 行,每行一个整数表示对应询问的答案。

[输入样例] 

1

1 1

233 666

666 233

[输出样例] 

433

[数据范围]

本题共 7 个测试点,不采用 subtask 评测,但每个测试点分值不同.

数据范围中的 X,Y 范围表示 Kxi,Kyi,Txi,Tyi 的范围,未标注即为没有特殊限制 对于全部数据

满足 N 的总和不超过 10^6 且 Q 的总和不超过 10^6


题解:切比雪夫距离转曼哈顿距离,然后求前缀和就可以了

然后询问的时候二分一下坐标,以后还是自己写二分,不要用lower_bound了

#include<bits/stdc++.h>
#define N 1000050
using namespace std;
typedef long long ll;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = (cnt << 1) + (cnt << 3) + (ch-'0'), ch = getchar();
	return cnt * f; 
}
int T, n, m;
int x[N], y[N]; ll sumx[N], sumy[N];
void print(ll x){
	if(x > 9) print(x / 10);
	putchar(x % 10 + '0'); 
}
int main(){
	T = read();
	while(T--){
		n = read(), m = read();
		for(int i=1; i<=n; ++i){
			int p = read(), q = read(); 
			x[i] = p + q; y[i] = p - q;
		}
		sort(x+1, x+n+1); sort(y+1, y+n+1);
		for(int i=1; i<=n; ++i){
			sumx[i] = sumx[i-1] + x[i];
			sumy[i] = sumy[i-1] + y[i];
		}
		while(m--){
			int p = read(), q = read();
			int xx = p + q, yy = p - q;
			ll ans = 0;
			int l = 0, r = n;
			while(l < r){
				int mid = (l+r+1) >> 1;
				if(x[mid] <= xx) l = mid;
				else r = mid - 1;
			}
			ans += 1ll * l * xx - sumx[l];
			ans += (sumx[n] - sumx[l]) - 1ll * (n - l) * xx;
			l = 0, r = n;
			while(l < r){
				int mid = (l+r+1) >> 1;
				if(y[mid] <= yy) l = mid;
				else r = mid - 1;
			}
			ans += 1ll * l * yy - sumy[l];
			ans += (sumy[n] - sumy[l]) - 1ll * (n - l) * yy;
			print(ans >> 1); puts("");
		}
	} return 0;
}

简单数据结构

[题目描述]

在看了 jiry_2 的课件《Segment Tree Beats!》后,小 O 深深沉迷于这种能单次 O(logn) 支持区间与一个数取 min/max,

查询区间和等信息的数据结构,于是他决定做一道与区间与 一个数取 min/max 的好题。

这题是这样的:你得到了一个长度为 n 的数列{ai}, 要求支持以下 2 种操作:

第一种是给 定 L,R,X,要求把区间中比 X 小的数字全部修改为 X;

第二种是给定 L,R,K,X,查询区间中比 X 小的最小的 K 个数,并且将它们升序输出,没有则输出-1。

小 O 觉得这题太简单了,于是把这题丢给了你,请你帮忙实现。 

[输入格式] 第一行一个数字 n 表示数列长度, 第二行 n 个数字分别表示 a1....an, 第三行一个数字 m 表示操作次数,

接下来 m 行每行表示一次操作, 第一个数 op 表示操作类型,op 可能是 1 或 2,

如果 op=1,后面有 L,R,X 三个正整数表示把区间[L,R]中比 X 小的数字全部改成 X

如果 op=2,后面有 L,R,X,K 四个正整数表示查询区间[L,R]中比 X 小的最小的 K 个数

[输出格式] 对于每个 op=2,输出一行, 如果比 X 小的数达到了 K 个,升序输出最小的 K 个数, 如果比 X 小的数小于 K 个,

输出一行一个-1 即可. 

[数据范围]  本题共 6 个测试点,不采用 subtask 评测,但每个测试点分值不同。 对于全部数据,满足 1<=n,m<=500000,1<=L<=R<=n,1<=K<=n,1<=Ai,X<=10^9,

对于所有 操作 2 中的 K,K 的总和不超过 5*10^6。

#1:12pts,满足 1<=n,m<=3000;

#2:7pts,满足 1<=n,m<=100000, 没有操作 1,且对于所有操作 2 有 K=1;

#3:23pts,满足 1<=n,m<=100000,对于所有操作 2 有 K=1;

#4:37pts,满足 1<=n,m<=100000,没有操作 1;

#5:6pts,满足 1<=n,m<=100000;

#6:15pts,无特殊限制。


关于 Subtask 的写法

Case 1 : O(n^2 log n)

Case 2 : 线段树 / st 表 区间最小

Case 3 : 线段树随便维护一下最小(我被PPT误导了,还维护了次小)

Case 4: 我们发现我们需要输出区间前 k 小,加上没有修改,所以可以主席树

Case 5: PPT提示我们分块,所以应该可以 O(n sqrt(n) log(n))

大概就是每个块暴力排序然后每次修改暴力二分位置

写出前4个点就可以得到 79 分的好成绩

#include<bits/stdc++.h>
#define N 200050
using namespace std;
typedef long long ll;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f; 
}
int n, a[N], m, b[N], siz;
struct Quary{int l, r, x, k;}q[N];
int flag1; // without operation 1 -> 44pts
int flag2; // with operation 2 for k = 1 -> 23pts

int k;
struct President{
	int tot, rt[N], ls[N * 20], rs[N * 20], sum[N * 20];
	void Build(int &x, int l, int r){
		x = ++tot; if(l == r) return;
		int mid = (l+r) >> 1;
		Build(ls[x], l, mid); Build(rs[x], mid+1, r);
	}
	void Insert(int &x, int last, int l, int r, int pos){
		x = ++tot; ls[x] = ls[last]; rs[x] = rs[last]; sum[x] = sum[last] + 1;
		if(l == r) return; int mid = (l+r) >> 1;
		if(pos <= mid) Insert(ls[x], ls[last], l, mid, pos);
		else Insert(rs[x], rs[last], mid+1, r, pos);
	}
	int Query(int a, int b, int l, int r, int L, int R){
		if(!a) return 0; if(L<=l && r<=R) return sum[a] - sum[b];
		int mid = (l+r) >> 1, ans = 0; 
		if(L<=mid) ans += Query(ls[a], ls[b], l, mid, L, R);
		if(R>mid) ans += Query(rs[a], rs[b], mid+1, r, L, R);
		return ans;
	} 
	void Dfs(int x, int y, int l, int r){
		if(!k) return;
		if(l == r){ 
			for(int i=1; i <= min(k, sum[x] - sum[y]); i++) printf("%d ", b[l]); 
			k -= (sum[x] - sum[y]);
			return;
		}
		int mid = (l+r) >> 1;
		if(sum[ls[x]] - sum[ls[y]]) Dfs(ls[x], ls[y], l, mid);
		if(sum[rs[x]] - sum[rs[y]]) Dfs(rs[x], rs[y], mid+1, r);
	}
	void Cout(int a, int b, int l, int r, int k){
		if(!a) return; if(!k) return;
		if(sum[a] - sum[b] >= k){ Dfs(a, b, l, r); return;}
		int mid = (l+r) >> 1, val = sum[ls[a]] - sum[ls[b]];
		Cout(ls[a], ls[b], l, mid, min(k, val));
		if(k > val) Cout(rs[a], rs[b], mid+1, r, k - val);
	}
}P; 

void Solve0(){ // n ^ 2 * log n 保佑能过 
	for(int C = 1; C <= m; C++){ 
		int l = q[C].l, r = q[C].r, x = q[C].x, k = q[C].k;
		if(!k){ for(int i=l; i<=r; i++){ if(a[i] < x) a[i] = x;}}
		else{
			int len = 0;
			for(int i=l; i<=r; i++) b[++len] = a[i];
			sort(b+1, b+len+1); int cnt = 0;
			for(int i=1; i<=len; i++){ if(b[i] >= x) break; cnt++;}
			if(cnt < k) printf("-1\n");
			else{ for(int i=1; i<=k; i++) printf("%d ", b[i]); printf("\n");}
		}
	}
}

void Solve1(){ // president tree // no modify
	for(int i=1; i<=n; i++) b[++siz] = a[i];
	for(int i=1; i<=m; i++) b[++siz] = q[i].x - 1;
	sort(b+1, b+siz+1);
	siz = unique(b+1, b+siz+1) - (b+1); 
	P.Build(P.rt[0], 1, siz);
	for(int i=1; i<=n; i++){
		int x = lower_bound(b+1, b+siz+1, a[i]) - b;
		P.Insert(P.rt[i], P.rt[i-1], 1, siz, x);
	}
	for(int i=1; i<=m; i++){
		int l = q[i].l, r = q[i].r, x = q[i].x - 1; k = q[i].k;
		x = lower_bound(b+1, b+siz+1, x) - b;
		int sum = P.Query(P.rt[r], P.rt[l-1], 1, siz, 1, x);
		if(sum < k) printf("-1\n");
		else P.Cout(P.rt[r], P.rt[l-1], 1, siz, k), printf("\n");
	}
}

const int inf = 0x3fffffff;
struct Segment{
	int mi[N << 1], se[N << 1], tag[N << 1];
	void Pushup(int x){
		if(mi[x<<1] < mi[x<<1|1]){
			mi[x] = mi[x<<1]; 
			if(se[x<<1] < mi[x<<1|1]) se[x] = se[x<<1];
			else se[x] = mi[x<<1|1];
		}
		else if(mi[x<<1] > mi[x<<1|1]){
			mi[x] = mi[x<<1|1];
			if(se[x<<1|1] < mi[x<<1]) se[x] = se[x<<1|1];
			else se[x] = mi[x<<1];
		}
		else{
			mi[x] = mi[x<<1];
			if(se[x<<1] < se[x<<1|1]) se[x] = se[x<<1];
			else se[x] = se[x<<1|1];
 		}
	}
	void Build(int x, int l, int r){
		if(l == r){ mi[x] = a[l]; se[x] = inf; return;}
		int mid = (l+r) >> 1;
		Build(x<<1, l, mid); Build(x<<1|1, mid+1, r);
		Pushup(x);
	}
	void Pushdown(int x){
		if(tag[x]){
			if(mi[x<<1] < tag[x]) mi[x<<1] = tag[x], tag[x<<1] = tag[x];
			if(mi[x<<1|1] < tag[x]) mi[x<<1|1] = tag[x], tag[x<<1|1] = tag[x];
			tag[x] = 0;
		}
	}
	void Dfs(int x, int l, int r, int v){
		if(mi[x] >= v) return; 
		if(mi[x] < v && se[x] >= v){ mi[x] = v; tag[x] = v; return;} 
		if(l == r){ mi[x] = v; se[x] = inf; return;} Pushdown(x);
		int mid = (l+r) >> 1;
		Dfs(x<<1, l, mid, v);
		Dfs(x<<1|1, mid+1, r, v);
		Pushup(x);
	}
	void Modify(int x, int l, int r, int L, int R, int v){
		if(L<=l && r<=R){ Dfs(x, l, r, v); return;}
		Pushdown(x);
		int mid = (l+r) >> 1;
		if(L<=mid) Modify(x<<1, l, mid, L, R, v);
		if(R>mid) Modify(x<<1|1, mid+1, r, L, R, v);
		Pushup(x);
	}
	int Query(int x, int l, int r, int L, int R){
		if(L<=l && r<=R) return mi[x];
		Pushdown(x);
		int mid = (l+r) >> 1, ans = 2e9;
		if(L<=mid) ans = min(ans, Query(x<<1, l, mid, L, R));
		if(R>mid) ans = min(ans, Query(x<<1|1, mid+1, r, L, R));
		return ans;
	}
}S;
void Solve2(){
 	S.Build(1, 1, n);
 	for(int C = 1; C <= m; C++){
 		int l = q[C].l, r = q[C].r, x = q[C].x, k = q[C].k;
 		if(!k){ S.Modify(1, 1, n, l, r, x);}
 		else{
		 	int v = S.Query(1, 1, n, l, r);
		 	if(v >= x) printf("-1\n");
			else printf("%d \n", v); 
		}
 	} 
}
int main(){
	freopen("segtree.in","r",stdin);
	freopen("segtree.out","w",stdout);
	n = read();
	for(int i=1; i<=n; i++) a[i] = read();
	m = read();
	for(int i=1; i<=m; i++){
		int op = read(), l = read(), r = read(), x = read();
		if(op == 1) q[i] = (Quary){l, r, x, 0}, flag1 = 1;
		else{
			int k = read(); q[i] = (Quary){l, r, x, k};
			if(k != 1) flag2 = 1;
		} 
	}
	if(n <= 3000 && m <= 3000){ Solve0(); return 0;}
	if(!flag1){ Solve1(); return 0;}
	if(!flag2){ Solve2(); return 0;}
	return 0;
}

正解

有一个求区间前 k 大的套路, 就是用堆维护

我们将一个区间 (l, r, pos,val), 放进优先队列,pos 表示最小值的位置,val 表示最小值

取出后,将 l -- pos-1, pos+1 -- r 插入堆,重复 k 次

我们发现对于区间取Max,最小值的点不会变,变的只会是值

于是我们可以线段树维护区间最小值,以及区间最小值的位置,修改标记,就可以完美的A掉这道题

#include<bits/stdc++.h>
#define N 1000050
using namespace std;
typedef long long ll;
inline int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f; 
}
inline void print(int x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) print(x / 10); 
	putchar(x % 10 + '0');
}
int n, a[N], m;
const int inf = 0x3fffffff;
#define pa pair<int, int>
#define mp make_pair
struct Segment{
	int mi[N << 1], tag[N << 1], pos[N << 1];
	inline void Pushup(int x){
		if(mi[x<<1] < mi[x<<1|1]) mi[x] = mi[x<<1], pos[x] = pos[x<<1];
		else mi[x] = mi[x<<1|1], pos[x] = pos[x<<1|1];
	}
	inline void Build(int x, int l, int r){
		if(l == r){ mi[x] = a[l]; pos[x] = l; return;}
		int mid = (l+r) >> 1;
		Build(x<<1, l, mid); Build(x<<1|1, mid+1, r);
		Pushup(x);
	}
	inline void Pushnow(int x, int v){ tag[x] = max(tag[x], v); mi[x] = max(mi[x], v);}
	inline void Pushdown(int x){ if(tag[x]){ Pushnow(x<<1, tag[x]); Pushnow(x<<1|1, tag[x]); tag[x] = 0;}}
	void Modify(int x, int l, int r, int L, int R, int v){
		if(L<=l && r<=R){ Pushnow(x, v); return;} Pushdown(x); int mid = (l+r) >> 1;
		if(L<=mid) Modify(x<<1, l, mid, L, R, v);
		if(R>mid) Modify(x<<1|1, mid+1, r, L, R, v); Pushup(x);
	}
	inline pa Merge(pa x, pa y){ if(x.first < y.first) return x; else return y;}
	pa Query(int x, int l, int r, int L, int R){
		if(L<=l && r<=R) return mp(mi[x], pos[x]); Pushdown(x);
		int mid = (l+r) >> 1; pa ans = mp(inf, 0);
		if(L<=mid) ans = Merge(ans, Query(x<<1, l, mid, L, R));
		if(R>mid) ans = Merge(ans, Query(x<<1|1, mid+1, r, L, R));
		return ans;
	}
}S;
struct Node{ 
	int l, r, pos, val;
	Node(int _l = 0, int _r = 0){ 
		l = _l, r = _r; pa tmp = S.Query(1, 1, n, l, r);
		val = tmp.first, pos = tmp.second;
	}
	inline bool operator < (const Node &a) const{ return val > a.val;}
}; 
int sta[N];
inline void Query(int l, int r, int x, int k){
	priority_queue<Node> q; 
	q.push(Node(l, r));
	for(register int i=1; i<=k; ++i){
		Node now = q.top(); q.pop();
		if(now.val >= x){ puts("-1"); return;}
		sta[i] = now.val;
		if(now.l <= now.pos-1) q.push(Node(now.l, now.pos-1));
		if(now.pos+1 <= now.r) q.push(Node(now.pos+1, now.r));
	}
	for(register int i=1; i<=k; ++i) print(sta[i]), putchar(' ');
	puts("");
}

inline void Solve(){
 	S.Build(1, 1, n);
	while(m--){
		int op = read(), l = read(), r = read(), x = read();
		if(op == 1){ S.Modify(1, 1, n, l, r, x);}
		if(op == 2){ int k = read(); Query(l, r, x, k);}
	} 
}
int main(){
	n = read();
	for(register int i=1; i<=n; ++i) a[i] = read();
	m = read(); Solve();
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值