数据结构学习笔记

本文介绍了数据结构中的堆和线段树的应用。通过codevs 1052地鼠游戏问题展示了如何使用优先队列实现贪心策略求解问题。接着讲解了线段树在poj 2777和HDU2795等题目中的应用,包括区间覆盖和区间最大值。此外,还提到了树状数组、并查集以及它们在不同问题中的使用技巧。最后探讨了单调栈和单调队列在解决某些特定问题中的作用。

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

priority_queue
  1. 包含#include< queue >
  2. 默认大根堆
q.top();
q.pop();
q.push();
q.size();

codevs 1052地鼠游戏

题目描述 Description

地鼠游戏是一项需要反应速度和敏捷判断力的游戏。游戏开始时,会在地板上一下子冒出很多地鼠来,然后等你用榔头去敲击这些地鼠,每个地鼠被敲击后,将会增加相应的游戏分值。问题是这些地鼠不会傻傻地等你去敲击,它总会在冒出一会时间后又钻到地板下面去(而且再也不上来),每个地鼠冒出后停留的时间可能是不同的,而且每个地鼠被敲击后增加的游戏分值也可能是不同,为了胜出,游戏参与者就必须根据每个地鼠的特性,有选择地尽快敲击一些地鼠,使得总的得分最大。

这个极具挑战性的游戏王钢特别喜欢,最近他经常在星期天上午玩这个游戏,慢慢地他不但敲击速度越来越快(敲击每个地鼠所需要的耗时是1秒),而且他还发现了游戏的一些特征,那就是每次游戏重新开始后,某个地鼠冒出来后停留的时间都是固定的,而且他记录了每个地鼠被敲击后将会增加的分值。于是,他在每次游戏开始后总能有次序地选择敲击不同的地鼠,保证每次得到最大的总分值。

输入描述 Input Description
输入包含3行,第一行包含一个整数n(1<=n<=100)表示有n个地鼠从地上冒出来,第二行n个用空格分隔的整数表示每个地鼠冒出后停留的时间,第三行n个用空格分隔的整数表示每个地鼠被敲击后会增加的分值(<=100)。每行中第i个数都表示第i个地鼠的信息。

输出描述 Output Description
输出只有一行一个整数,表示王钢所能获得的最大游戏总分值。

样例输入 Sample Input
5

5 3 6 1 4

7 9 2 1 5

样例输出 Sample Output
24

solution
  1. 我们先按松鼠的时间排序,我们发现一个松鼠要不然可以打,要不然一定小于现在的时间1个单位(因为一只松鼠需要1个单位时间)
  2. 所以贪心,策略为能打就打,不能打只要找到前面松鼠中分值最小的一个(一个就行)和它交换
  3. 用priority_queue维护最小值
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<cstdlib>
#include<ctime>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
inline int read(){
	char ch=' ';int f=1;int x=0;
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
struct node
{
	int t,v;
}a[110];
bool cmp(node a,node b)
{
	return a.t<b.t;
}
priority_queue <int> q;
int main()
{
	int n=read();
	int ti=0,ans=0;
	for(int i=1;i<=n;i++)	a[i].t=read();
	for(int i=1;i<=n;i++)   a[i].v=read();
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++)
	{
		if(a[i].t>ti)
		{
			ti++;
			q.push(-a[i].v);
			ans+=a[i].v;
		}
		else
		{
			int tmp=q.top();tmp=-tmp;
			if(tmp>a[i].v) continue;
			else
			{
				ans=ans-tmp+a[i].v;
				q.pop();q.push(-a[i].v);
			}
		}
	}
	cout<<ans<<endl;
	return 0;
}


线段树

1.poj 2777

在这里插入图片描述

思路
  1. 考虑颜色<=30,把颜色压成数位来表示这个节点的状态
  2. 然后就是区间覆盖
  3. 唯一改的就是update的或
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=100010;
int s[N<<2];
int tag[N<<2];
inline void update(int rt)
{
	s[rt]=s[rt<<1]|s[rt<<1|1];
}
void build(int rt,int l,int r)
{
	if(l==r)
	{
		s[rt]=1;
		return ;
	}
	int mid=(l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	update(rt);
}
void pushdown(int rt,int l,int r)
{
	if(tag[rt])
	{
		tag[rt<<1]=tag[rt];
		s[rt<<1]=tag[rt];
		tag[rt<<1|1]=tag[rt];
		s[rt<<1|1]=tag[rt];
		tag[rt]=0;
	}
}
void modify(int rt,int l,int r,int x,int y,int k)
{
	if(l>=x&&r<=y)
	{
		s[rt]=1<<(k-1);
		tag[rt]=1<<(k-1);
		return ;
	}
	pushdown(rt,l,r);
	int mid=(l+r)>>1;
	if(x<=mid)	modify(rt<<1,l,mid,x,y,k);
	if(y>mid)	modify(rt<<1|1,mid+1,r,x,y,k);
	update(rt);
}
int query(int rt,int l,int r,int x,int y)
{
	if(l>=x&&r<=y)
	{
		return s[rt];
	}
	pushdown(rt,l,r);
	int mid=(l+r)>>1,ret=0;
	if(x<=mid) ret=ret|query(rt<<1,l,mid,x,y);
	if(y>mid) ret=ret|query(rt<<1|1,mid+1,r,x,y);
	return ret;
}
int main()
{
	int n,t,m;
	scanf("%d%d%d",&n,&t,&m);
	char s[10];int a,b,c;
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		cin>>s;
		if(s[0]=='C')
		{
			scanf("%d%d%d",&a,&b,&c);
			if(a>b) swap(a,b);
			modify(1,1,n,a,b,c);
		}
		else
		{
			scanf("%d%d",&a,&b);
			if(a>b) swap(a,b);
			int k=query(1,1,n,a,b),ans=0;
			while(k)
			{
				if(k&1==1) ans++;
				k=k>>1;
			}
			cout<<ans<<endl;
		}
	}
	return 0;
}

2HDU2795

在这里插入图片描述

思路
  1. 把每一行当作剩余的空间(列数)当作一个子节点及其权值
  2. 维护区间最大值
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200010;
int s[N<<2];
int z[N];
inline void update(int rt)
{
    s[rt]=max(s[rt<<1],s[rt<<1|1]);
}
void build(int rt,int l,int r,int c)
{
    if(l==r)
    {
        s[rt]=c;
        return ;
    }
    int mid = (l+r) >> 1;
    build(rt<<1,l,mid,c);
    build(rt<<1|1,mid+1,r,c);
    update(rt);
}
int tmp;
void modify(int rt,int l,int r,int c)
{
    if(l==r)
    {
        s[rt]=s[rt]-c;
        tmp=l;
        return ;
    }
    int mid= (l+r)  >>1;
    if(s[rt<<1]>=c)
    {
        modify(rt<<1,l,mid,c);
    }
    else 
    {
        modify(rt<<1|1,mid+1,r,c);
    }
    update(rt);
}
int main()
{
    int h,w,n;
    int i,j;
    
    while(scanf("%d%d%d",&h,&w,&n)!=EOF)
    {
        if(h>n)
        {
            h=n;
        }
        build(1,1,h,w);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&z[i]);
            if(s[1]<z[i])
            {
                cout<<-1<<endl;
            }
            else
            {
                modify(1,1,h,z[i]);
                cout<<tmp<<endl;
            }
        }
    }
    return 0;
}

3 hdu 1542

扫描线+线段树+离散化

4 hdu 1823

二维线段树

树状数组

ST表

并查集

  1. 不路径压缩
int find(int x)
{	if(f[x]==x) return x;
	return find(f[x]);
}
  1. 路径压缩
int find(int x)
{
	if(f[x]==x) return x;
	return f[x]=find(f[x]);
}

虽然改动很小,但是时间复杂度大幅降低

  1. 按秩合并(小树合到大树上)
void merge(int x,int y)
{
	int f1=find(x);
	int f2=find(y);
	if(siz[f1]>siz[f2])
	f[f2]=f1,siz[f1]+=siz[f2];
	else f[f1]=f2,siz[f2]+=siz[f1];
}

时间复杂度:路径压缩快于按秩合并,但是按秩合并可以保留树的形态

POJ2492

思路:在一颗并查集内黑白染色

#include <iostream>
#include <cstdio>

using namespace std;

int n,m,fa[100100],dis[100100];

int find(int x)
{
	if (fa[x] == x)     return x;
	find(fa[x]);
	dis[x] = dis[x] ^ dis[fa[x]];	fa[x] = fa[fa[x]];  
	return fa[x];
}

int main()
{
	int T = 0;
	cin >> T;
	for (int t = 1;t <= T;t++)
		{
			printf("Scenario #%d:\n",t);
			cin >> n >> m;
			for (int i = 1;i <= n;i++)     fa[i] = i,dis[i] = 0;
			int x,y;   bool yes = 0;
			for (int i = 1;i <= m;i++)
				{
					scanf("%d %d",&x,&y);
					int r1 = find(x),r2 = find(y);
					int v = dis[x] ^ dis[y];
					if (r1 == r2  && v == 0 && yes == 0)    {printf("Suspicious bugs found!\n");yes = 1;}
					if (r1 != r2)   {
						fa[r1] = r2;     dis[r1] = dis[x] ^ dis[y] ^ 1;
					}
				}
			if (yes == 0)    printf("No suspicious bugs found!\n");
			printf("\n");
		}
}

extra

& P2024
在这里插入图片描述

单调栈

#include<stack>
stack <int> s
stack.pop().top().psuh().empty().size()
hdu1506
  1. 维护左右 最小值
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<stack>
#include<cstdlib>
#include<ctime>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
inline int read(){
	char ch=' ';int f=1;int x=0;
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
stack <int> s;
const int N=1e5+100;
int a[N],l[N],r[N];
int main()
{
	int n;
	while(true)
	{
		n=read();
		if(n==0) break;
		while(!s.empty()) s.pop();
		for(int i=1;i<=n;i++) a[i]=read();
		for(int i=1;i<=n;i++)
		{
			while(!s.empty()&&a[i]<=a[s.top()]) s.pop();
			if(s.empty()) l[i]=0;
			else l[i]=s.top();
			s.push(i);
		}
		while(!s.empty()) s.pop();
		for(int i=n;i>=1;i--)
		{
			while(!s.empty()&&a[i]<=a[s.top()]) s.pop();
			if(s.empty()) r[i]=n+1;
			else r[i]=s.top();
			s.push(i);
		}
		long long ans=0;
        for(int i=1;i<=n;i++)
            ans=max(ans,(long long)a[i]*(r[i]-l[i]-1));
        printf("%lld\n",ans);
	}
	return 0;
}


单调队列

这是维护最小值的一个单调队列

  1. 与普通队列不同的是,单调队列头和尾都可以进出元素
int head,tail;
struct node{ int w,p; }q[N];
int a[N];
void add(int w,int p)
{
	while(head<=tail&&w<=q[tail].w)	tail--;
	tail++;q[tail].p=p;q[tail].w=w;
}
void del(int p)
{
	if(q[head].p==p) head++;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值