郑州大学2024年寒假训练 Day1:数据结构基础及常用STL(A-I)

进去玩了玩,这场主要是总结了一些STL的用法,有的题还是比较有难度的,方便寻找,会在题号后标记用到的数据结构以及难度(这个难度是建立在能够合理运用STL的前提下)。有的题尽管可以暴力去做,我还是会尽量使用效率优先的做法。这场不错,继续蹲后面的比赛。vjudge链接一篇题解


A:静态链表 难度 ⋆ ⋆ \star\star

思路:

洛谷原题 P1160 队列安排

先把学生随机插入,然后再随机删除,最后从头遍历一遍。

因为学生的插入和删除都需要用下标,而且数据量不小,有 1 0 5 10^5 105,所以使用动态存储结构实现的链表不能在短时间内查询到这个学生,STL的list直接枪毙,因此使用静态空间,用下标来代表学生编号实现静态访问。说白了就是用数组去模拟链表。不了解怎么写的可以先去学习一下,链表在CF里用的不少,难度一般在一千三四往上就会出现。

这个题的要求很简单,所以不带头节点的单链表也是可以实现的,不过我习惯写头节点为空节点的循环双链表,这会带来很多便利。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1e5+5;

int n,m;
int pre[maxn],nxt[maxn];

int main(){
	cin>>n;
	pre[1]=nxt[1]=0;
	pre[0]=nxt[0]=1;
	for(int i=2,k,p;i<=n;i++){
		cin>>k>>p;
		if(p==0)k=pre[k];
		//k后插i,前插可以先向前移动一位,就变成了后插
		pre[i]=k;
		nxt[i]=nxt[k];
		pre[nxt[k]]=i;
		nxt[k]=i; 
	}
	cin>>m;
	for(int i=1,x;i<=m;i++){
		cin>>x;
		if(pre[x]==x)continue;
		nxt[pre[x]]=nxt[x];
		pre[nxt[x]]=pre[x];
		pre[x]=nxt[x]=x;
	}
	for(int i=nxt[0];i;i=nxt[i])
		cout<<i<<" ";
	return 0;
}

B:栈 难度 ⋆ \star

思路:

洛谷原题 P1449 后缀表达式

后缀表达式又称逆波兰表达式,我们平常使用的是中缀表达式,另外还有一个前缀表达式(和后缀表达式正好相对,又称波兰表达式)。中缀表达式在日常使用中非常便利,但是机器理解就不那么便利了,再加上括号等复杂的优先级判断,会对运行效率产生影响。而逆波兰表达式通过把运算符直接放在两操作数之后,从而优化掉了优先级判断,更适合计算机的运算。

逆波兰表达式每遇到一个操作符,就会去找它前面(也就是最后放入)的两个操作数进行运算(有先后顺序),再将结果放到最后。这样后进先出的规则正好契合栈的性质,所以使用栈来存储操作数。

将操作数放入栈,读取到操作符后取出栈顶的两个元素,运算结束后将结果放回。问题在于如何读入操作数,由于操作数末尾一定存在一个’.’ ,因此我们读到数字时将它存到一个临时的数中,每读入一位,将临时数*10(十进制的向左移动一位),再加入这一位,读到’.’ ,后将临时数加入栈顶即可。

code:

#include <iostream>
#include <cstdio>
#include <stack>
using namespace std;

stack<int> s;

int main(){
	char ch;
	int tmp=0;
	while(true){
		ch=getchar();
		if(ch>='0' && ch<='9')tmp=(tmp<<3)+(tmp<<1)+(ch^48);
		else if(ch=='.')s.push(tmp),tmp=0;
		else if(ch=='+'){
			tmp=s.top();
			s.pop();
			tmp+=s.top();
			s.pop();
			s.push(tmp);
			tmp=0;
		}
		else if(ch=='-'){
			tmp=-s.top();
			s.pop();
			tmp+=s.top();
			s.pop();
			s.push(tmp);
			tmp=0;
		}
		else if(ch=='*'){
			tmp=s.top();
			s.pop();
			tmp*=s.top();
			s.pop();
			s.push(tmp);
			tmp=0;
		}
		else if(ch=='/'){
			tmp=s.top();
			s.pop();
			s.top()/=tmp;
			tmp=0;
		}
		else if(ch=='@'){
			printf("%d",s.top());
			break;
		}
	}
	return 0;
}

C:队列+搜索树 难度 ⋆ ⋆ \star\star

思路:

洛谷原题 P1540 [NOIP2010 提高组] 机器翻译

这个题数据量很小,可以暴力。下面讲队列+搜索树的 O ( n l o g m ) O(nlogm) O(nlogm) 的做法。

假设内存已满,每次读入一个单词就要查询一下这个单词在不在内存,不在就需要删掉一个最早插入的单词。首先在一堆数据中动态插入查询一个数据,可以用搜索树,STL中的set和map本质就是封装的高级的搜索树——红黑树,这里我们用set足够。需要查找最早的单词,由于set需要以单词为关键字来查询,不能再按时间(也就是编号)来查询,因此需要再用另一个数据结构——队列。队列的先入先出的性质非常适合。

具体来说,set和queue存储相同的数据,插入和删除数据也是同步的。每次查询一个单词,如果不在set中,就加入set和queue,如果队列已满m长度,那么先删掉最早的(也就是队首)的单词,set也要删除。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <queue>
using namespace std;

set<string> s;
queue<string> q;
int n,m,cnt;
string t;

int main(){
	cin>>m>>n;
	for(int i=1;i<=n;i++){
		cin>>t;
		if(s.find(t)==s.end()){
			if(q.size()==m){
				s.erase(q.front());
				q.pop();
			}
			q.push(t);
			s.insert(t);
			cnt++;
		}
	}
	cout<<cnt;
	return 0;
}

D: 堆 难度 ⋆ \star

思路:

洛谷原题 P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G

这题只要想明白其实还是非常好做的。而且这题是用烂了的例题,堆啊,哈夫曼树啊都可以拿这个讲,而且衍生题也不少。名字就是大名鼎鼎的果子合并。

每次合并两堆果子,都要付出两堆果子的代价之和,并且两堆变成一堆,最多合并n-1次。所以我们每次只要贪心地合并两堆数目最小的即可。

我们需要动态地取出最小的两堆,合并后再放回,因此需要使用堆。

code:

#include <iostream>
#include <queue>
#include <cstdio>
using namespace std;

int n,ans;
priority_queue<int,vector<int>,greater<int> > h;

int main(){
	cin>>n;
	for(int i=1,t;i<=n;i++){
		cin>>t;
		h.push(t);
	}
	while(h.size()!=1){
		int t=h.top();
		h.pop();
		t+=h.top();
		h.pop();
		h.push(t);
		ans+=t;
	}
	cout<<ans;
}

E:搜索树 难度 ⋆ \star

思路:

洛谷原题 P5266 【深基17.例6】学籍管理

动态插入,查询,删除。很裸的搜索树,因为是按名字查找,同时还要查询分数,所以可以用set+pair,或者直接用map都行。用map的话查询一定要用find等成员函数,不要用下标访问,因为下标访问不到时会自动插入下标这个元素。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;

map<string,int> s;
int n;
string t;

int main(){
	cin>>n;
	for(int i=1,f,score;i<=n;i++){
		cin>>f;
		if(f==1){
			cin>>t>>score;
			s[t]=score;
			printf("OK\n");
		}
		else if(f==2){
			cin>>t;
			if(s.find(t)==s.end())printf("Not found\n");
			else printf("%d\n",s[t]);
		}
		else if(f==3){
			cin>>t;
			if(s.find(t)==s.end())printf("Not found\n");
			else {
				printf("Deleted successfully\n");
				s.erase(t);
			}
		}
		else if(f==4){
			printf("%d\n",s.size());
		}
	}
	return 0;
}

F:搜索树 难度 ⋆ ⋆ ⋆ \star\star\star

思路:

洛谷原题 P1102 A-B 数对

给你一堆数,查询 a − b = c a-b=c ab=c 的a b数对个数,c是已知的。乍一看有些无从下手,不妨变化一下式子,变为 a = b + c a=b+c a=b+c ,我们枚举b,问题就变成了询问满足式子的a的个数与b的个数乘积的总和。查询a及其个数正是搜索树擅长的部分(其实排个序然后二分搜索也可以)。

code:

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;

int n,c;
map<int,int> s;
long long ans;

int main(){
	cin>>n>>c;
	for(int i=1,t;i<=n;i++){
		cin>>t;
		s[t]++;
	}
	for(auto x:s){
//		cout<<x.first<<" "<<x.second<<endl;
		if(s.find(x.first+c)!=s.end())
			ans+=1ll*x.second*s[x.first+c];
	}
	cout<<ans;
	return 0;
}

G:string+搜索树 难度 ⋆ ⋆ \star\star

思路:

2022ICPC杭州站原题
The 2022 ICPC Asia Hangzhou Regional Programming Contest
F. Da Mi Lao Shi Ai Kan De
题目链接

题意有点难理解,说白了就是给你 n n n 个群,每个群 m i m_i mi 个消息,如果这个群里的消息有包含"bie"的子串,并且之前没有被转发过,就转发一下,如果这个群没有可以转发的信息,就转发"Time to play Genshin Impact, Teacher Rice!"。

其实题意理解了就很清楚怎么做了,一颗星是打给英语阅读理解上的。用string内置的成员函数find来查找一下有没有包含"bie",用set去动态维护转发过的消息,用一个bool变量标记这个群有没有转发内容即可。

code:

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;

int T,n;
set<string> s;
string t;

int main(){
	cin>>T;
	while(T--){
		bool f=false;
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>t;
			if(t.find("bie")!=string::npos && s.find(t)==s.end()){
				cout<<t<<endl;
				s.insert(t);
				f=true;
			}
		}
		if(!f)puts("Time to play Genshin Impact, Teacher Rice!");
	}
	return 0;
}

H:栈 难度 ⋆ ⋆ ⋆ ⋆ ⋆ \star\star\star\star\star

这个是codeforces往期比赛原题
Pinely Round 3 (Div. 1 + Div. 2)
C. Heavy Intervals
题目链接

思路:

我的题解,讲的很详细了我就不多赘述了。哪里不懂可以直接在评论里问,看到会回。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
using namespace std;
const int maxn=1e5+5;

int T,n,l[maxn],c[maxn];
pair<int,bool> a[maxn<<1];

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++)
			cin>>a[i].first,a[i].second=false;
		for(int i=n+1;i<=2*n;i++)
			cin>>a[i].first,a[i].second=true;
		for(int i=1;i<=n;i++)
			cin>>c[i];
		sort(a+1,a+2*n+1);
		
		stack<int> s;
		l[0]=0;
		for(int i=1;i<=2*n;i++){
			if(!a[i].second)s.push(a[i].first);
			else {
				l[++l[0]]=a[i].first-s.top();
				s.pop();
			}
		}
		sort(l+1,l+n+1);
		sort(c+1,c+n+1,greater<int>());
		
		long long ans=0;
		for(int i=1;i<=n;i++)
			ans+=1ll*l[i]*c[i];
		cout<<ans<<endl;
	}
	return 0;
}

I:链表 难度 ⋆ ⋆ ⋆ ⋆ \star\star\star\star

这个也是原题。
Educational Codeforces Round 161 (Rated for Div. 2)
D. Berserk Monsters
题目链接

思路:

我的题解,虽然用到了set,但是set并不是必要的,也没带来多少时间上的优化,主要是省点事。

code:

#include <iostream>
#include <cstdio>
#include <set>
#include <cstring>
using namespace std;
const int maxn=3e5+5;
const int inf=2e9+5;

int T,n,a[maxn],d[maxn];
int pre[maxn],nxt[maxn];
set<int> ck,dl;
bool dead[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		pre[0]=nxt[n]=0;
		a[0]=0;
		d[0]=inf;
		memset(dead,false,sizeof(dead));
		for(int i=1;i<=n;i++){
			cin>>a[i];
			pre[i]=i-1;
			nxt[i-1]=i;
			ck.insert(i);
		}
		for(int i=1;i<=n;i++)
			cin>>d[i];
		
		for(int i=1;i<=n;i++){
			for(auto x:ck){
				if(dead[x])continue;
				if(a[pre[x]]+a[nxt[x]]>d[x])dl.insert(x);
			}
			ck.clear();
			printf("%d ",dl.size());
			for(auto x:dl){
				nxt[pre[x]]=nxt[x];
				pre[nxt[x]]=pre[x];
				dead[x]=true;
				ck.insert(pre[x]);
				ck.insert(nxt[x]);
			}
			dl.clear();
		}
		puts("");
	}
	return 0;
}
  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值