FZU ACM 2025寒假集训,专题3

一个朴实无华的目录

  • 一:做题思路
    • 1.Priority Queue
    • 2.ST 表 && RMQ 问题
    • 3.合并果子
    • 4.约瑟夫问题
    • 5.Look Up S(单调栈和单调队列)
      • 单调栈
      • 单调队列
    • 6. 国旗计划
  • 二:知识点总结
    • 1.stack
    • 2.queue
    • 3.priority_queue
    • 4.ST表
      • 核心思想:使用预处理解决可重复贡献问题

一:做题思路

FZU ACM 2025寒假集训,专题3_第1张图片

1.Priority Queue

1.创建优先队列和string(用于记录指令)。
2.循环输入字符串指令,判断分支操作。

#include 
#include 
#include 
using namespace std;

int main() {
    priority_queue<int> maxHeap; // 默认是最大堆
    string command;
    int k;

    while (true) {
        cin >> command;
        if (command == "insert") {
            cin >> k;
            maxHeap.push(k); // 将元素插入堆中
        } else if (command == "extract") {
            if (!maxHeap.empty()) {
                cout << maxHeap.top() << endl; // 输出堆顶元素
                maxHeap.pop(); // 移除堆顶元素
            } else {
                cout << "Heap is empty" << endl; // 如果堆为空,输出提示信息
            }
        } else if (command == "end") {
            break; // 结束程序
        } else {
            cout << "Invalid command" << endl; // 处理无效命令
        }
    }

    return 0;
}

2.ST 表 && RMQ 问题

#include 
#include 
#include 
using namespace std;

const int MAXN = 1e5 + 10;
const int MAXLOG = 20; // log2(MAXN) 的上限
int st[MAXN][MAXLOG];  // ST 表
int log2Table[MAXN];   // log2Table[i] 表示 i 的以 2 为底的对数的整数部分

// 快速读入函数
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 - '0';
        ch = getchar();
        
    }
    return x * f;
}

// 预处理 log2Table
void preprocessLog2Table(int n) {
    for (int i = 2; i <= n; ++i) {
        log2Table[i] = log2Table[i >> 1] + 1;
    }
}

// 预处理 ST 表
void preprocessST(int n, const vector<int>& arr) {
    for (int i = 0; i < n; ++i) {
        st[i][0] = arr[i];
    }
    for (int j = 1; (1 << j) <= n; ++j) {
        for (int i = 0; i + (1 << j) - 1 < n; ++i) {
            st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
        }
    }
}

// 查询区间 [l, r] 的最大值
int query(int l, int r) {
    int j = log2Table[r - l + 1];
    return max(st[l][j], st[r - (1 << j) + 1][j]);
}

int main() {
    int n = read(), m = read();
    vector<int> arr(n);
    for (int i = 0; i < n; ++i) {
        arr[i] = read();
    }

    preprocessLog2Table(n);
    preprocessST(n, arr);

    while (m--) {
        int l = read() - 1, r = read() - 1; // 输入的区间是 1-based,转换为 0-based
        //cout << query(l, r) <<"\n";
        printf("%d\n",query(l, r));
    }

    return 0;
}

3.合并果子

使用优先队列读取数据,通过top(),找出最小的两个元素,相加并记录为ans,重复此过程直到所有元素都被弹出队列。

#include
using namespace std;
int pile[10005]={0};
int len=0;
int main()
{
    int n;
    scanf("%d",&n);
    priority_queue<int,vector<int>,greater<int> > q;
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        q.push(x);
    }

    int ans=0;
    while(q.size()!=1)
    {
        int a1=q.top();
        q.pop();
        int a2=q.top();
        q.pop();
        ans+=a1+a2;
        q.push(a1+a2);
    }
    printf("%d\n",ans);
}

4.约瑟夫问题

#include
using namespace std;
int a[100]={0};
//元素值为0表示未出局
//i既代表数组的下标,也代表每个人的编号
//k是用来计数的,一旦k的值达到m,代表此人需要出局,并且k需要重新计数,这样才能够找出所有需要出局的人
//数组的0代表未出局的人,数组非0代表出局的人,未出局的人需要报数,出局的人不需要报数
int main()
{
    int m,n,i=1,sum=0,k=0;//sum是出局人数
    cin>>n>>m;
    while(n!=sum)//当出局人数!=n 进入
    {
        if(i>n)//当i>n的时候从1重新报数
            i=1;
        if(a[i]==0)//只有当元素值为0进入 否则直接++
        {
            k++;//k计数器++
            if(k==m)
            {
                sum++;
                k=0;//k从0开始报数
                a[i]=1;//元素值变1 表示出局 往后不再报数
                cout<<i<<" ";
            }
        }
        i++;
    }
    return 0;
}

5.Look Up S(单调栈和单调队列)

单调栈

首先循环读入数组a[],
f[]数组用于记录仰望对象。
由于向右看齐,最后一个元素必定没有仰望对象,所以从最后开始倒序处理。
单调栈中仅保留最大的对象的下标,其他比它小的元素弹出。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define frep(i,x,y) for(int i=x;i>=y;i--)
const int N=100005;
int n;
stack<int> s;
int a[N];
int f[N];
int main()
{
	scanf("%d",&n);
	rep(i,1,n)
		scanf("%d",&a[i]);
	frep(i,n,1)
	{
		while(!s.empty()&&a[s.top()]<=a[i]) s.pop();
		if(!s.empty()) f[i]=s.top();
		else f[i]=0;
		s.push(i);
	}
	rep(i,1,n) printf("%d\n",f[i]);
	return 0;
}

单调队列

从左到右入队,入队的信息为(高度,下标位置)
如果后入队的高度大于前面的高度,则将前面的元素弹出,并将它们的仰望对象= 后入队的下标
最后剩下的元素仰望对象= 0.

#include
using namespace std;
int n,f[100010];
struct node{
	int num;
	int id;
}e;
deque <node> q;
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		int a;
		scanf("%d",&a);
		while (!q.empty()&&q.back().num<a) f[q.back().id]=i,q.pop_back();
		//找比它小的数,成为这些数的仰望对象 
		e.num=a; e.id=i;
		q.push_back(e); //入队 
	}
	for (int i=1;i<=n;i++) printf("%d\n",f[i]);
	return 0;
}

6. 国旗计划

//递推关系为f[i][j]=f[f[i][j−1]][j−1]
#include 
using namespace std;
const int MAXN = 2e5+5;
int ans[MAXN];//记录每个士兵的答案
int n,m;//n:士兵数 m:站点数
int f[MAXN*2][20];//由于使用双倍链,所以maxn需要*2.

struct node {
	int id; // 士兵编号
	int l, r; // 奔袭区间的左右端点,已转换为“连续区间”的形式
} s[MAXN*2]; 

bool cmp(node a, node b) {
	return a.l < b.l;
}//比较士兵的左端点,用于排序。
void pre() {
	for (int i = 1, p = i; i <= 2 * n; i++) {
 
	while (p <= 2 * n && s[p].l <= s[i].r) {
 	p++;
 	}
 	// p-1 就是满? s[p-1].l <= s[i].r 的最后?个区间
 	f[i][0] = p - 1;
}
// 构造倍增表 f[i][j]
	for (int i = 1; i < 20; i++) {
		for (int j = 1; j <= 2 * n; j++) {
			f[j][i] = f[f[j][i-1]][i-1];
		}
	}
}

void solve(int k) {

	int rr = s[k].l + m;//目标终点
	int tot = 1; //使用的士兵个数
	int p = k; //记录原始士兵编号,用于给答案数组赋值
	
	for (int i = 19; i >= 0; i--) {
	
		if (f[k][i] != 0 && s[f[k][i]].r < rr) {
			tot += (1 << i); 
			k = f[k][i]; 
		}
	}

	//+1确保覆盖一圈
	ans[s[p].id] = tot + 1;
}

int main(){
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) {
	scanf("%d %d", &s[i].l, &s[i].r);
	//处理左端点比右端点大的士兵
	if (s[i].r < s[i].l) {
		s[i].r += m;
	}
		s[i].id = i; // 记录原始编号
	}
	//对士兵根据左端点从小到大排序
	sort(s + 1, s + 1 + n, cmp);
	//将所有士兵复制一份到新增的m区间
	for (int i = 1; i <= n; i++) {
		s[i + n] = s[i]; // 复制原区间
		s[i + n].l = s[i].l + m; // 左端点右移 m
		s[i + n].r = s[i].r + m; // 右端点右移 m
	}

	pre();

	for (int i = 1; i <= n; i++) {
		solve(i);
	}
	
	for (int i = 1; i <= n; i++) {
		printf("%d ", ans[i]);
	}
	return 0;
	
}

二:知识点总结

1.stack

特性:后进先出 ,只能处理栈顶元素

2.queue

特性:先进先出,仅支持查询或删除第一个加入的元素,添加进去的元素为最后一个元素。

3.priority_queue

类型:二叉堆
特性:插入元素后会根据优先级自动排序。

 priority_queue<int> q; 

里面的元素从小到大排列,top元素为队列最后一个元素。

小根堆写法 priority_queue<int, vector<int>,greater<int> > p; 

greater表示大的数放前面。

4.ST表

核心思想:使用预处理解决可重复贡献问题

可重复贡献问题在实际应用中具有广泛的用途,尤其是在需要高效处理区间查询的场景中。以下是一些实际应用的例子:

  1. 区间最大值/最小值查询(RMQ)
    这是可重复贡献问题最典型的应用之一。例如,在气象数据中,需要频繁查询某个时间段内的最高气温或最低气温。使用 ST 表可以高效地解决这类问题,预处理复杂度为 O(nlogn),查询复杂度为 O(1) 。
  2. 区间 GCD 查询
    在数学和算法竞赛中,经常需要计算某个区间内所有数的最大公约数(GCD)。由于 GCD 满足可重复贡献性质(gcd(x,x)=x),ST 表可以高效地解决这类问题。虽然其查询复杂度与线段树类似,但 ST 表的实现更简单。
  3. 区间按位与/或查询
    在计算机科学中,按位运算(如按位与、按位或)也满足可重复贡献性质。例如,在处理二进制数据时,需要计算某个区间内所有数的按位与或按位或结果。ST 表可以高效地完成这类查询。
  4. 数据压缩和编码
    在数据压缩算法中,某些操作(如哈夫曼编码)需要频繁查询区间内的最小值或最大值,以优化编码效率。ST 表可以用于快速查询这些区间信息,从而提高数据压缩的效率。

你可能感兴趣的:(算法,数据结构)