常用算法代码模板与技巧

​欢迎大家订阅我的专栏:算法题解:C++与Python实现
本专栏旨在帮助大家从基础到进阶 ,逐步提升编程能力,助力信息学竞赛备战!

专栏特色
1.经典算法练习:根据信息学竞赛大纲,精心挑选经典算法题目,提供清晰的代码实现与详细指导,帮助您夯实算法基础。
2.系统化学习路径:按照算法类别和难度分级,从基础到进阶,循序渐进,帮助您全面提升编程能力与算法思维。

适合人群:

  • 准备参加蓝桥杯、GESP、CSP-J、CSP-S等信息学竞赛的学生
  • 希望系统学习C++/Python编程的初学者
  • 想要提升算法与编程能力的编程爱好者

这篇文档其实是我自己学习路上的一份“复习笔记”。之前看了不少教程,零零散散记了一堆代码片段和小技巧,时间久了就容易忘,翻起来也麻烦。所以干脆花时间整理了一下,把常用的模板、容易踩的坑、还有一些个人觉得挺实用的小技巧都归到了一起。

如果你也在学类似的东西,或许能帮你省下一些翻找的时间;如果有哪里写得不清楚或不对,也欢迎随时指正交流。

希望这份整理也能对你有一点点帮助~ ✨


代码技巧

11、8的倍数

// 11的倍数:奇数位之和 与 偶数位之和 的差是11的倍数
for (int i=0; i<a.size(); i++) 
{
	if (i%2==0) ji += a[i]-'0';
	else ou += a[i]-'0';
}
if ((ji-ou)%11==0) cout << "Yes" <<endl;
else cout << "No" << endl;

// 8的倍数:后三位是8个倍数,i=5时,有i-1个数是8的倍数
for (int i=0; i<s.size(); i++) 
{
    if ((s[i]-'0')%8==0) cnt++;
    if (i>=1 && ((s[i-1]-'0')*10 + (s[i]-'0')) % 8 == 0) cnt++;
    if (i>=2 && ((s[i-2]-'0')*100 + (s[i-1]-'0')*10 + (s[i]-'0')) % 8 == 0) cnt += i-1;
}

题解:学而思编程 验证11的倍数

题解:学而思编程 八倍数子串

金币发放

int ci=0, jin=1, cnt1=0;
for (int i=1; i<=m; i++) 
{  // m为天数
	cnt1 += jin;  // cnt1为总金币数,jin为每天发放的金币数
	ci++;  // 持续天数
	if (ci==jin) 
	{
		jin++;
		ci=0;
	}
}

题解:学而思编程 国王发金币1

数位拆分

void chai(int n)
{
	if (n==0) flag[0]=1;
	while (n>0) 
	{
		flag[n%10]=1;  // 这里可以实现数码出现与否的统计,或数码出现次数的统计,或统计数位和
		n/=10; 
	}
}

题解:学而思编程 数字统计

约瑟夫环

int n, m;  // n个人,数到m出列 
int id=-1;  // id从-1开始,以便初始下标为0
for (int i=1; i<=n; i++) 
{  // n个人
	for (int j=1; j<=m; j++) 
	{  //数到m出列
		id++;  // id初始从-1开始
		id%=n;
		if (id<0) id += n;  // 处理id递减的场景
		while (flag[id]==0) 
		{
			id++;
			id%=n;
		}
	}
	flag[id]=0;  // 数到m的人出列
}

//(编号+n-1)%n+1

题解:学而思编程 约瑟夫游戏

最长连续值

// 求字符串中最长连续xx字符
int cnt, cur=0;
for (int i=0; i<s.size(); i++) 
{
	if (s[i]=='b') cnt++;
	else a[++cur] = cnt;
}
a[++cur]=cnt;  // 一定要加上这句,将最后的结果也保存进a数组中

题解:学而思编程 挑选香蕉

回文字符串判断

bool isPal(string s)
{
	string s1="";
	for (int i=s.size()-1; i>=0; i--) 
		s1 += s[i];
	return s1==s;
}
// 回文判断加速代码(设定开始和结束指针,从两边向中间靠拢)
for (int i=0; i+k-1<n; i++)
{
	int j = i+k-1;
	for (int x=i, y=j; x<y; x++, y--)
		if (path[x]!=path[y]) return false;
}
return true;

题解:学而思编程 回文判断

多重比较

// 依次看中总成绩、速写成绩、色彩成绩、常规素描成绩
bool cmp(Stu x, Sty y) {
	if (x.zf!=y.zf) return x.zf>y.zf;
	if (x.sx!=y.sx) return x.sx>y.sx;
	if (x.sc!=y.sc) return x.sc>y.sc;
	if (x.sm!=y.sm) return x.sm>y.sm;
}

题解:学而思编程 题解

矩阵对角线

// 左上至右下
a[i][i]

// 左下至右上
a[i][n-i+1]

// 与格子(x,y)同一左上至右下对角线
x-y == i-j

// 与格子(x,y)同一左下至右上对角线
x+y == i+j

题解:学而思编程 行列斜线

走格子路线

// 左上(1,)至右下(n,)的路线数
b[i][j] = b[i-1][j] + b[i][j-1]

题解:学而思编程 走格子2

杨辉三角形

for (int i=0; i<=n; i++)
{
    a[i][0] = 1;
    for (int j=0; j<=i; j++)
        a[i][j] = a[i-1][j-1] + a[i-1][j];
}

题解:学而思编程 杨辉三角形

矩阵翻转与旋转

// a[]为原始矩阵,b[]为变换后的矩阵
// 上下翻转
for (int i=1; i<=n; i++) 
{  // n为行
	for (int j=1; j<=m; j++) 
	{  // m为列
		b[i][j] = a[n-i+1][j];
	}
}

// 左右翻转
for (int i=1; i<=n; i++) 
{  // n为行
	for (int j=1; j<=m; j++) 
	{  // m为列
		b[i][j] = a[i][m-j+1];
	}
}

// 向右旋转90度
for (int i=1; i<=m; i++) 
{  // m为列
	for (int j=1; j<=n; j++) 
	{  // n为行
		b[i][j] = a[n-j+1][i];
	}
}
// 输出时先遍历列,再遍历行,输出b数组
for (int i=1; i<=m; i++)
{
    for (int j=1; j<=n; j++)
        cout << b[i][j] << " ";
    cout << endl;
}

// 向左旋转90度
for (int i=1; i<=m; i++) 
{  // m为列
	for (int j=1; j<=n; j++) 
	{  // n为行
		b[i][j] = a[j][m+1-j];
	}
}
// 输出时先遍历列,再遍历行,输出b数组

// 旋转180度
for (int i=1; i<=n; i++) 
{  // n为行
	for (int j=1; j<=n; j++) 
	{  // m为列
		b[i][j]=a[n+1-i][m+1-j];
	}
}
// 输出时先遍历行,再遍历列,输出b数组

// 矩阵A转置(A^T)
for (int i=1; i<=m; i++) 
{
	for (int j=1; j<=n; j++) 
	{
		cout << a[j][i] << " ";
	}
	cout << endl;
}

题解:学而思编程 矩阵旋转1

题解:学而思编程 神秘的图腾

题解:学而思编程 矩阵旋转2

矩阵查找

for (int i=1; i<=n-m+1; i++)  // 枚举大矩阵的行列坐标
    for (int j=1; j<=n-m+1; j++)
    {
        bool flag = true;
        for (int a=1; a<=m; a++)  // 枚举小矩阵的行列坐标
            for (int b=1; b<=m; b++)
                if (s[a+i-1][b+j-1]!=t[a][b]) flag = false;  // 在大矩阵的行列坐标下扩展小矩阵
        if (flag) cout << i << " " << j << endl;  // flag==true说明大矩阵以(i,j)为起点的矩阵与小矩阵完全匹配
    }

进制转换

进制间转换

// 十进制转十六进制(也适用于十进制转二进制)
string DtoH(int n)
{
	string d="0123456789ABCDEF",ans="";
	if (n==0) return "0";  //对于n为0的情况,需要这样特判
	while (n>0) 
	{
		ans=d[n%16]+ans;
		n/=16;
	}
	return ans;
}
// 十进制转m进制
string Dtom(int n,int m)
{
	string d="0123456789ABCDEF",ans="";
	while (n>0)
	{
		ans=d[n%m]+ans;
		n/=m;
	}
	return ans;
}
// 二进制转十进制
int BtoD(string s)
{
	int ans=0;
	for (int i=0;i<s.size();i++) 
	{
		ans=ans*2+s[i]-'0';
	}
	return ans;
}
// 十六进制转十进制
int HtoD(string s)
{
	int ans=0;
	for (int i=0;i<s.size();i++) 
	{
		if (s[i]<='9')
			ans=ans*16+s[i]-'0';
		else
			ans=ans*16+s[i]-'A'+10;
	}
	return ans;
}
// m进制转十进制
int mtoD(string s,int m)
{
	int ans=0;
	for (int i=0;i<s.size();i++) 
	{
		if (s[i]<='9')
			ans=ans*m+s[i]-'0';
		else
			ans=ans*m+s[i]-'A'+10;
	}
	return ans;
}

保留进制转换后每个位数的数字

// 将数字转为k进制后,保存每位的数字,s[]保存每个x的转为k进制后,每个位数上的数字
void f(int x)
{
	for (int j=0; x; j++, x/=k)
		s[j] += x%k;
}

题解:洛谷 B4499 [GESP202603 三级] 二进制回文串

约数和

for (int i=1; i<=n; i++)
	for (int j=2; j<=n/i; j++)
		fsum[i*j] += i;  // fsum[i*j] 数x=i*j 的所有约数和

4个方向和8个方向的定义

// 4个方向
int dx[4] = {-1,1,0,0}  // 上下左右
int dy[4] = {0,0,-1,1};

// 8个方向
int dx[8] = {-1,-1,-1,0,1,1,1,0};
int dy[8] = {-1,0,1,1,1,0,-1,-1};

字符、数字与坐标转换

用字符串描述3*3二维矩阵中的字符并做上下左右更换

//将字符串中'x'的位置找出
string t = "123456x78"
int k = t.find('x');
int x = k/3, y = k%3;

//更换字符串中与'x'字符上下左右相邻的字符
for (int i=0; i<4; i++) 
{
	int a = x+dx[i], b = y +dy[i];
	if (a>=0 && a<3 && b>=0 && b<3) 
	{
		swap(t[k], t[a*3+b]]);
		// BFS的判断及队列添加(略)
		swap(t[k], t[a*3+b]]);  //需要还原现场
	}
}

数字转换为坐标

// a[i]为数字,转换为坐标
double y = ceil(a[i]*1.0/n);  // 坐标转换
int x = a[i] - ((y-1)*n);
vis[x][int(y)] = 1;  //标记

数值转二维平面坐标

// 输入数值为t,转为{x, y}坐标
PII getpos(int t)
{
    int x = (t + n - 1) / n;
    int y = t % n;
    if (y==0) y = n;
    return {x, y};
}

两个线段之间求交集

// 一个区间的左右端点为l,r,另一个区间的左右端点为a,b
res = max(0, (min(r, b)-max(l,a) + 1)  // 右端点的最小值 - 左端点的最大值 + 1,与0取max是因为有可能没交集

常用位运算

在这里插入图片描述

在这里插入图片描述

找树根

for (int i = 0; i < n - 1; i++)  // 输入n-1条边,构建树结构
{
    int a, b; 
    cin >> a >> b;  // 输入边的两个节点
    add(b, a);  // 添加从b到a的边,表示b是a的父节点
    st[a] = true;  // 标记a有父节点
}  

int root = 1;
while (st[root]) root++;  // 找到没有父节点的节点,即为根节点

向上取整

// 公式
int t = (x + k - 1) / k; 
// 举例:
// 如果是除2向上取整
int t = (x + 2 - 1) / 2;
// 如果是除3向上取整
int t = (x + 3 - 1) / 3;

运算符重载

// 结构体+
Point operator+(Point a, Point b)
{
    Point c;
    c.x = a.x + b.x;
    c.y = a.y + b.y;
    return c;
}
// 结构体-
Point operator-(Point a, Point b)
{
    Point c;
    c.x = a.x - b.x;
    c.y = a.y - b.y;
    return c;
}
// 结构体*
Point operator*(Point a, int b)
{
    Point c;
    c.x = a.x * b;
    c.y = a.y * b;
    return c;
}

计算位置不同的元素的最小交换次数

memset(vis, 0, sizeof(vis));  // 重置访问标记
int cycles = 0;  // 环的数量

// 计算当前排列的环数量
for (int i = 1; i <= n; i++)
{
    if (!vis[i])
    {
        cycles++;
        int j = i;
        while (!vis[j])
        {
            vis[j] = true;
            j = a[j];  // 从公司i开始,沿着排列找到下一个公司
        }
    }
}

// 最小交换次数 = n - 环的数量
int min_swaps = n - cycles;

生成不同排列

// a[]为{1, 2, 3, 4};
do 
{
    // 会打印 1 2 3 4 / 1 2 4 3 / ... 的不同排列
    for (int i=1; i<=n; i++)
        cout << a[i] << " ";
    cout << endl;
} while (next_permutation(a+1, a+n+1));

找循环节

字符映射(含循环节)

// mp保存的是已经输入的字符映射关系
for (int ch = 'a'; ch <= 'z'; ch++) {
	memset(vis, 0, sizeof(vis));
	char cur = ch, tmp = cur;
           
	while (true) 
	{
		vis[cur] = true;
		tmp = mp[tmp];
		if (vis[tmp]) break;
		cur = tmp;
	}
	mp[ch] = tmp;
}

模拟移动寻找循环

int cur = pow_new[s];  // 当前位置
int time = 0, cnt = 0;  // time: 当前时间,cnt: 访问过的位置数量
memset(vis_time, -1, sizeof(vis_time));  // 初始化访问时间为-1
    
while (vis_time[cur] == -1)  // 如果当前位置未被访问过
{
    vis_time[cur] = time;  // 记录访问时间
    vis[cnt++] = cur;  // 记录访问序列
    cur = nxt_pos[cur];  // 移动到下一个位置
    time++;  // 时间增加
}
int cycle_start = vis_time[cur];  // 循环开始的时间
int cycle_length = time - cycle_start;  // 循环长度
    
// 计算第q步后的位置
if (q < cnt)  // 如果在循环开始之前
{
    res_pos = vis[q];
}
else  // 如果进入循环
{
	int pos_in_cycle = (q - cycle_start) % cycle_length;  // 在循环中的位置
	res_pos = vis[cycle_start + pos_in_cycle];
}
cout << a[res_pos].id << endl;  // 输出结果编号

题解:AtCoder AT_awc0025_d Constellation Observation Tour

DFS+回溯寻找循环

// 使用DFS+回溯求解任意点开始的环的长度
void dfs(int u)
{
    // 如果已经计算过,直接返回
    if (ans[u])
    {
        return;
    }

    // 如果访问到已标记的节点,说明找到了环
    if (vis[u])
    {
        int len = 1;
        int v = t[u];
        // 计算环的长度
        while (v != u)
        {
            len++;
            v = t[v];
        }
        // 为环上所有节点设置结果
        ans[u] = len;
        v = t[u];
        while (v != u)
        {
            ans[v] = len;
            v = t[v];
        }
        return;
    }

    // 标记当前节点已访问
    vis[u] = true;
    dfs(t[u]);  // 递归探索下一个节点
    vis[u] = false;  // 回溯,取消标记
    
    // 如果当前节点的结果还未计算,则继承下一个节点的结果
    if (!ans[u])
    {
        ans[u] = ans[t[u]];
    }
}

题解:AtCoder AT_awc0030_d Telephone Game of Messages

数据超int范围时不可达判断

// 可使用INF=1e18来赋初值
for (int i=1; i<=n; i++)
	dist[i] = INF;
if (dist[n]>=INF/2)  // 判断>=INF/2即可
	cout << -1;

计算某个区间 最大值-最小值=k 的区间对数量

// 分别计算<=k的区间对数量和<=k-1的区间对数量
ans = calc(k) - calc(k-1)

题解:AtCoder AT_awc0026_e Just the Right Temperature Difference

数字序列转字符串后的字符串截取

// 5 5 6 11 4 4 2 7 7 12,这样的数字序列转为字符串后,求从i截取到x个数字序列的字符串
// 预处理字符串前缀和长度
for (int i=1; i<=n; i++)
	sa[i] = sa[i-1] + a[i].size();

// 计算第i个到第x个长度的字符串
for (int i=1; i+x-1<=n; i++)
{
	int st_pos = sa[i]-a[i].size()+1;
	int ed_pos = sa[i+x-1];
	string tmp = s.substr(st_pos, ed_pos-st_pos+1);  // s字符串下标从1开始
}

题解:洛谷 P2852 [USACO06DEC] Milk Patterns G

约束传递

// 求数组中相邻元素大小之差的绝对值的最小值,要求每个数只能增加不能减少
l[i] = max(l[i-1]-k, a[i])
r[i] = max(r[i+1]-k, a[i])
b[i] = max(l[i], r[i])
ans += b[i]-a[i]

题解:AtCoder AT_awc0005_c Staircase-Shaped Flower Bed

在一条路径上找最远的父节点

for (int i=2; i<=n; i++)
{
    int fa = 1;
    for (int j=1; j<=n; j++)
    {
        if (i==j) continue;
        if (a[1][i]==a[1][j]+a[j][i] && a[1][j]>a[1][fa]) fa = j;
    }
    int w = a[1][i] - a[i][fa];  // fa为父节点
}

题解:洛谷 AT_abc451_e [ABC451E] Tree Distance

A - 基础算法

【高精度】

高精度加法

//高精度数输入
void s2BIG(string s, int a[])
{
    a[0] = s.size();
    for (int i=1; i<=a[0]; i++)
        a[i] = s[a[0]-i] - '0';
}
// 高精度数输出
void printBIG(int a[])
{
    for (int i=a[0]; i>=1; i--)
        cout << a[i];
    cout << endl;
}
//低精度转高精度
void i2BIG(int n, int a[])
{
	while (n>0)
	{
		a[++a[0]] = n % 10;
		n /= 10;
	}
	if (a[0] == 0) a[0] = 1;
}
//高精加
void addBIG(int a[], int b[], int c[])
{
	c[0] = max(a[0], b[0]);
	int u = 0;  // u存储低位向高位的进位
	for (int i=1; i<=c[0]; i++)
	{
		int t = a[i] + b[i] + u;
		c[i] = t % 10;
		u = t / 10;
	}
	if (u>0) c[++c[0]] = u;
}

高精度减法

//高精减
void subBIG(int a[], int b[], int c[])
{
	c[0] = max(a[0], b[0]);
	int u = 0;
	for (int i=1; i<=c[0]; i++)
	{
		int t = a[i]-b[i]-u;
		if (t<0) 
		{
			u = 1;
			c[i] = t + 10;
		}
		else
		{
			u = 0;
			c[i] = t;
		}
	}
	while (c[c[0]] == 0 && c[0]>1) c[0]--;
}

高精度乘法

//高精乘(高精*低精)
void mulBIG(int a[], int b, int c[])
{
	c[0] = a[0];
	int u = 0;
	
	for (int i=1; i<=c[0]; i++)
	{
		int t = a[i] * b + u;
		c[i] = t % 10;
		u = t / 10;
	}
	
	while (u>0) 
	{
		c[++c[0]] = u % 10;
		u /= 10;
	}
}
//高精乘(高精*高精)
void mulBIG2(int a[], int b[], int c[])
{
    c[0] = a[0] + b[0];
    int u = 0;
    for (int j=1; j<=b[0]; j++)
    {
        int u = 0;
        
        for (int i=1; i<=a[0]; i++)
        {
            int t = a[i]*b[j] + c[i+j-1] + u;
            c[i+j-1] = t % 10;
            u = t / 10;
        }
        if (u>0)
            c[a[0]+j] += u;        
    }
    while (c[0]>1 && c[c[0]]==0)
    {
        c[0]--;
    }
}

高精度除法

//高精度除法
// 高精除低精
void divBIG(int a[], int b, int c[])
{
	c[0] = a[0];
	int r = 0;
	
	for (int i=c[0]; i>=1; i--)
	{
		int t = r * 10 + a[i];
		c[i] = t / b;
		r = t % b;
	}
	while (c[c[0]] == 0 && (c[0]>1)) c[0]--;
}

// 高精除高精
void divBIG2(int a[], int b[], int c[])
{ // 将高精度数字a[]与高精度数字b[]相除,所得的商存入高精度数组c[]中
	// int temp[1005] = {0}; // 临时数组
	// int count[1005] = {0}; // 计数数组
	c[0] = 1; // 初始商为0
	c[1] = 0;
	
	// 特殊情况处理
	if(cmpBIG(a, b))
	{ // 如果a < b,直接返回0
		c[0] = 1;
		c[1] = 0;
		return;
	}
	
	// 计算商的位数
	c[0] = a[0] - b[0] + 1;
	for(int i = 1; i <= c[0]; i++) c[i] = 0;
	
	// 模拟竖式除法
	for(int i = c[0]; i >= 1; i--)
	{
		// 调整除数位数
		int tempB[1005] = {0};
		tempB[0] = b[0] + i - 1;
		for(int j = 1; j <= b[0]; j++)
			tempB[j+i-1] = b[j];
			
		// 试商
		while(!cmpBIG(a, tempB))
		{
			// a = a - tempB
			subBIG(a, tempB, a);
			
			c[i]++; // 商对应位加1
			
			// 处理进位
			int j = i;
			while(c[j] > 9)
			{
				c[j+1] += c[j] / 10;
				c[j] %= 10;
				j++;
				if(j > c[0]) c[0] = j;
			}
		}
	}
	
	// 去除前导零
	while(c[c[0]] == 0 && c[0] > 1) c[0]--;
}

高精度求余

int mod(string s, int div)
{
    int rem = 0;
    for (int i=0; i<s.size(); i++) 
	{
        rem = (rem*10+(s[i]-'0')) % div;
    }
    return rem;
}
//如果rem为0说明是s是div的倍数

高精度封装

// 高精度结构体
struct BIG
{
    int len, num[1005];
    BIG()
    {// 构造函数:将高精度数字初始化为0
        memset(num, 0, sizeof(num));
        len = 1;
    }
    void set(int x)
    {// 将int类型数字x转化为高精度数字
        memset(num, 0, sizeof(num));
        len = 0;
        while (x>0)
        {
            num[++len] = x % 10;
            x /= 10;
        }
        if (len==0) len = 1;  // 特判0的情况
    }
    void set(string s)
    {// 将string类型转化为高精度数字
        memset(num, 0, sizeof(num));
        len = s.size();
        for (int i=1; i<=len; i++)
            num[i] = s[len-i] - '0';
    }
    void print()
    {// 输出高精度数字
        for (int i=len; i>=1; i--)
            cout << num[i];
        cout << endl;
    }
    string toS() // 将高精度数存成字符串,用法 string t = A.toS();
    {
        for (int i=len; i>=1; i--)
            t += to_string(num[i]);
        return t;
    }
};

// 高精度加法封装
BIG operator+(BIG a, BIG b)
{
    BIG c;
    c.len = max(a.len, b.len);
    int u = 0;
    for (int i=1; i<=c.len; i++)
    {
        int t = a.num[i] + b.num[i] + u;
        c.num[i] = t % 10;
        u = t / 10;
    }
    if (u>0) c.num[++c.len] = u;
    return c;
}

// 高精度减法封装
bool operator<(BIG a, BIG b)
{
    if (a.len != b.len) return a.len < b.len;
    for (int i=a.len; i>=1; i--)
        if (a.num[i] != b.num[i])
            return a.num[i] < b.num[i];
    return false;
}
bool operator==(BIG a, BIG b)
{
    if (a.len != b.len) return false ;
    for (int i=a.len; i>=1; i--)
        if (a.num[i] != b.num[i])
            return false;
    return true;
}

BIG operator-(BIG a, BIG b)
{
    BIG c;
    c.len = max(a.len, b.len);
    int u = 0;
    for (int i=1; i<=c.len; i++)
    {
        int t = a.num[i] - b.num[i] - u;
        if (t<0)
        {
            c.num[i] = t + 10;
            u = 1;
        }
        else
        {
            c.num[i] = t;
            u = 0;
        }
    }
    while (c.num[c.len] == 0 && c.len>1) c.len--;
    return c;
}

// 高精度乘法封装
BIG operator*(BIG a, int b)
{
    BIG c;
    c.len = a.len;
    int u = 0;
    for (int i=1; i<=c.len; i++)
    {
        int t = a.num[i] * b + u;
        c.num[i] = t % 10;
        u = t / 10;
    }
    while (u>0)
    {
        c.num[++c.len] = u % 10;
        u /= 10;
    }
    return c;
}

// 高精度除法封装
BIG operator/(BIG a, int b)
{
    BIG c;
    c.len = a.len;
    int r = 0;
    for (int i=c.len; i>=1; i--)
    {
        int t = r * 10 + a.num[i];
        c.num[i] = t / b;
        r = t % b;
    }
    while (c.num[c.len] == 0 && c.len > 1) c.len--;
    return c;
}

题解:学而思编程 a+b(高精度)

题解:学而思编程 a-b(高精度)

题解:学而思编程 a乘b(高精乘单精)

题解:学而思编程 a除b(高精除单精)

【整数二分】

二分查找

// lower_bound(): 返回第一个 >= x 的位置
// upper_bound(): 返回第一个 > x 的位置
// upper_bound()-1: 返回最后一个 <= x 的位置
// upper_bound()-lower_bound(): 等于x的个数

二分答案

// [l,r]区间寻找最小的最大值
int l = 0, r = 1e18;
while (l < r)
{
    int mid = l + r >> 1;
    if (check(mid)) r = mid;    // check()判断mid是否满足性质
    else l = mid + 1;
}
// l就是答案

// [l, r]区间寻找最大的最小值
while (l < r)
{
    int mid = l + r + 1 >> 1;  //当l=mid时,一定要l+r+1
    if (check(mid)) l = mid;
    else r = mid - 1;
}
// l就是答案

二分分段数

// 寻找最大的最小值时,确认分段数是否>=k
bool check(int x)
{
    for (int i=1; i<=n; i++)
    {
        if (sum+a[i]>=x)
        {
            ans++;
            sum = 0;
        }
        else sum += a[i]; 
    }
    return ans >= k;
}

题解:AcWing 789 数的范围

【浮点二分】

bool check(double x) {/* ... */} // 检查x是否满足某种性质,注意如果求等于某值,那这里判断要为小于或大于,不能像整数二分一样为等于

double bsearch_3(double l, double r)
{
    double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求,需要比预留的小数位+2
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

题解:AcWing 790 数的三次方根

【前缀和】

一维前缀和

// 前缀和求解
for (int i=1; i<=n; i++) s[i] = s[i-1] + a[i];
printf("%d\n", s[r]-s[l-1]);
// 可用于连续子数组查找的优化,例如查找有多少的[L,R]满足区间和为k,即查找以R结尾的数组中有多少个 
sum[L-1] == sum[R]-k。
// 快速求数组中所有数字的曼哈顿距离之和
// 1 4 7,曼哈顿距离和 = 3 + 6 + 3 = 12
for (int i=1; i<=n; i++)
	ans += a[i]*i - sa[i];

题解:AcWing 795 前缀和

二维前缀和

//预处理s[i,j]
for (int i=1; i<=n; i++)
    for (int j=1; j<=m; j++)
        s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];

//询问
while (q--) {
    int x1, y1, x2, y2;
    scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
    printf("%d\n", s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]);
}

题解:AcWing 796 子矩阵的和

【差分】

一维差分基本操作

// 获得差分数组
for (int i=1; i<=n; i++) b[i] = a[i]-a[i-1];
// for (int i=1; i<=n; i++) insert(i, i, a[i]);

// 给区间[l, r]中的每个数加上c:
void insert(int l, int r, int c)
{
    b[l]+=c;
    b[r+1]-=c;
}

// 还原差分数组为原数组
for (int i=1; i<=n; i++) b[i] += b[i-1];

题解:AcWing 797 差分

0/1之间状态切换(开关问题)

// 状态在0/1之间切换,某个位置操作会影响一个区间
for (int i=1; i<=n; i++)
{
	diff[i] = (diff[i-1] + diff[i])%2;
	int state = a[i] ^ diff[i];
	if (state ==1)
	{
		if (i+k-1<=n)
		{
			ans++;
			diff[i] = (diff[i] + 1) % 2;
			diff[i+k] = (diff[i+k] - 1 + 2) % 2;
		}
		else
		{
			cout << -1 << endl;
			return 0;
		}
	}
}

题解:AtCoder AT_awc0022_d Simultaneous Control of Light Bulb Panels

区间修改操作,问多少区间满足操作次数>=k

// 区间长度达到10^9,针对区间进行修改操作,问多少区间满足操作系统>=k
for (int i = 1; i <= n; i++)  // 处理每个区间
{
    int l, r; 
    cin >> l >> r;  // 读入区间左端点和右端点
    mp[l]++;  // 在l处进入区间,覆盖次数+1
    mp[r]--;  // 在r处离开区间,覆盖次数-1
}
    
int last = 0, cnt = 0;  // last: 上一个坐标点,cnt: 当前覆盖次数
    
// 扫描线遍历所有关键点
for (auto x : mp)  // 按坐标从小到大遍历
{
    int pos = x.first;  // 当前坐标
    int delta = x.second;  // 覆盖次数的变化量
        
    if (cnt >= k)  // 如果上一个区间的覆盖次数≥k
    {
        // 累加上一个区间[last, pos)的长度
        ans += pos - last;
    }
        
    last = pos;  // 更新上一个坐标
    cnt += delta;  // 更新当前覆盖次数
}

题解:AtCoder AT_awc0026_d Repainted Wall

让每个元素至少达到阈值

for (int i = 1; i <= n; i++)  // 遍历每个位置
{
    cur += imos[i];  // 更新当前位置的累计增加值
    if (a[i] + cur < t)  // 如果当前值小于目标值
    {
        int need = t - (a[i] + cur);  // 计算需要增加的值
        ans += need;  // 累计操作次数
        if (i + k <= n)  // 如果影响范围未超出数组
        {
            imos[i + k] -= need;  // 在差分数组记录减少
        }
        cur += need;  // 当前累计值增加
    }
}

题解:AtCoder AT_awc0034_c Watering the Flower Bed

二维差分基本操作

//预处理出b[i][j]差分数组
for (int i=1; i<=n; i++)
    for (int j=1; j<=m; j++)
        insert(i, j, i, j, a[i][j]);

// 给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
void insert(int x1, int y1, int x2, int y2, int c)
{
    b[x1][y1] += c;
    b[x2+1][y1] -= c;
    b[x1][y2+1] -= c;
    b[x2+1][y2+1] += c;
}

//通过前缀和还原数组
for (int i=1; i<=n; i++)
    for (int j=1; j<=m; j++)
        b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1];

题解:AcWing 798 差分矩阵

【ST表】

// 预处理ST表
for (int i=1; i<=n; i++) scanf("%d", &f[i][0]);
for (int j=1; j<=20; j++)
	for (int i=1; i+(1<<j)-1<=n; i++)
		f[i][j] = max(f[i][j-1], f[i+(1<<(j-1))][j-1];

for (int i=1, l, r; i<=m; i++)
{
	scanf("%d%d", &l, &r);
	int k = log2(r-l+1);
	printf("%d\n", max(f[l][k], f[r-(1<<k)+1][k]);
}

题解:洛谷 P3865 【模板】ST 表 & RMQ 问题

【快速排序】

// AcWing模板
// do-while版本
void quick_sort(int q[], int l, int r)
{
    if (l>=r) return;
    int rnd_idx = rand() % (r-l+1)+l;
    swap(q[l], q[rnd_idx]);
    int x=q[l], i=l-1, j=r+1;
    while (i<j) 
	{
        do i++; while (q[i]<x);
        do j--; while (q[j]>x);
        if (i<j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j);
    quick_sort(q, j+1, r);
}

// while版本
void quick_sort(int q[], int l, int r)
{
    if (l>=r) return;
    int rnd_idx = rand() % (r-l+1)+l;
    swap(q[l], q[rnd_idx]);
    int x=q[l], i=l-1, j=r+1;
    while (i<j) 
	{
        while (q[++i]<x);
		while (q[--j]>x);
        if (i<j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j);
    quick_sort(q, j+1, r);
}
void qsort(int l, int r)
{  // 对区间[l, r]进行快速排序
    int x = a[(l+r)/2], i=l, j = r;  // 选择中间的元素作为基准值
    while (i<=j)
    {
        while (a[i]<x) i++;  // 在x左边从左到右找到第一个>=x的数字a[i]
        while (a[j]>x) j--;  // 在x右边从右到左找到第一个<=x的数字a[j]
        // 如果找到的两个元素的下标没有交错,交换这两个元素,并移动下标
        if (i<=j) swap(a[i], a[j]), i++, j--;
    }
    if (l<j) qsort(l, j);  // 递归地对左半部分进行快速排序
    if (i<r) qsort(i, r);  // 递归地对右半部分进行快速排序
}

题解:AcWing 785 快速排序

【归并排序】

// AcWing模板
void merge_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int mid = l + r >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else tmp[k ++ ] = q[j ++ ];

    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];

    for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
void merge_sort(int l, int r)
{
    if (l==r) return;  // 如果子数组只有一个元素,则不需要排序,直接返回
    int mid = (l + r) / 2 ;
    merge_sort(l, mid);  // 递归地对左半部分进行归并排序
    merge_sort(mid+1, r);  // 递归地对右半部分进行归并排序

    int x = l, y = mid+1, cnt = l-1;
    while (x<=mid && y<=r)  // 只要左右两端子数组都不为空
    {
        if (a[x]<=a[y])  // 将两段子数组左端点较小者插入b数组
        {
            b[++cnt] = a[x];
            x++;
        }
        else 
        {
            b[++cnt] = a[y];
            y++;
        }
    }
    // 合并过程中其中一个子数组的元素已全部合并完成,而另一个子数组仍有剩余元素的情况
    for (int i=x; i<=mid; i++) b[++cnt] = a[i];  // 如果左半部分还有剩余的元素,将它们复制到b中
    for (int i=y; i<=r; i++) b[++cnt] = a[i];  // 如果右半部分还有剩余的元素,将它们复制到b中

    for (int i=l; i<=r; i++) a[i] = b[i];  // 将b中已经排序好的部分复制回a中,完成合并
}

题解:AcWing 787 归并排序

【堆排序】

基本声明方式

// 整型大根堆
priority_queue<int, vector<int> > pq;
// 整型小根堆
priority_queue<int, vector<int>, greater<int> > pq;

// 结构体中符号重载,< 表示大的在前,> 表示小的在前(对应greater)
struct Node
{
    int val;
    // 自定义符号重载,< 表示大的在前,> 表示小的在前(对应greater)
    // bool operator<(const Node& other) const {
    //     return val < other.val; // 大的在前
    // }
};
// 结构体类型大根堆
priority_queue<Node, vector<Node> > pq;
// 在结构体外定义符号重载,< 表示大的在前,> 表示小的在前(对应greater)
bool operator<(Node a, Node b)  // 表示大的在前
{
    return a.val < b.val;
}

核心操作

// 插入元素
pq.push(x);
// 删除元素
pq.pop();
// 取队首元素
int top = pq.top();
// 判断堆是否为空
if (pq.empty()) { ... }
// 取堆的大小
int size = pq.size();

双堆技巧

// 应用场景:动态维护数据流的中位数
// 大根堆存储较小的一半,小根堆存储较大的一半
priority_queue<int> small;
priority_queue<int, vector<int>, greater<int> > bigg;
// 若元素为偶数个,smaller与bigger元素个数相等
// 若元素为奇数个,smaller比bigger元素多一,则small的队首即为中位数
if(smaller.size() > bigger.size() + 1)
{
	int tmp = smaller.top();
	smaller.pop();
	bigger.push(tmp);
    sumSmall -= tmp;
    sumBig += tmp;
}
else if(smaller.size() < bigger.size())
{
	int tmp = bigger.top();
	bigger.pop();
	smaller.push(tmp);
    sumSmall += tmp;
    sumBig -= tmp;
}
// 如果还要计算中位数到所有元素的距离和,需要维护一个变量sumSmall和sumBig,分别表示small和bigger中元素的和
int leftSum = x * (int)small.size() - sumSmall;   // small中元素到x的距离和
int rightSum = sumBig - x * (int)big.size();      // big中元素到x的距离和
int res = leftSum + rightSum;

题解:学而思编程 动态中位数

修改堆中数据

// 应用场景:修改优先队列中元素的值
// 可以定义一个数组用于离线处理,当堆中数据访问到的时候(pq.top()),再根据数组中的标志位来修改或删除元素

题解:学而思编程 x轴最大距离

自定义堆实现

// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], hsize;
// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}
void down(int u)
{
    int t = u;
    if (u*2<=hsize && h[u*2]<h[t]) t = u*2;
    if (u*2+1<= size && h[u*2+1]<h[t]) t = u*2+1;
    if (u != t) 
	{
        heap_swap(u, t);
        down(t);
    }
}
void up(int u)
{
    while (u/2>=1 && h[u]<h[u/2]) 
	{
        heap_swap(u, u / 2);
        u /= 2; 
    }
}
// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);

题解:AcWing 838 堆排序

题解:AcWing 839 模拟堆

【双指针】

基本声明方式(for+while)

for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;  // 这里while的内容也跟着题目要求变化

    // 具体问题的逻辑
}
/*
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
    (3) 对于两个序列,判断一个序列是否为另一个序列的子序列
*/

题解:AcWing 799 最长连续不重复子序列

题解:AcWing 800 数组元素的目标和

先移动j至合适位置,然后满足则i,j同时移动,不满足移动i

// 先移动j至合适位置,然后满足则i,j同时移动,不满足移动i
int i=1, j=1; 
while (i<=n && j<=m)
{
    // 跳过所有金额小于当前商品价格的优惠券
    while (j <= m && r[j] < c[i])
    {
        j++;
    }
    // 如果还有可用的优惠券
    if (j <= m)
    {
        i++;
        j++;  // 使用当前优惠券
        cnt++;  // 成功匹配一个商品
    }
    else 
        i++;
}

题解:AtCoder AT_awc0002_d Keys and Treasure Boxes

满足则i和j同时移动,不满足则移动j

// 满足则i和j同时移动,不满足则移动j
int i = 1, j = 1;  // i: 指向b数组的指针, j: 指向a数组的指针
    
// 使用双指针贪心匹配
while (i <= m && j <= n)
{
    if (a[j] >= b[i])  // 如果当前a元素 >= 当前b元素,可以匹配
    {
        ans++;  // 匹配数量加1
        i++;    // 移动到下一个b元素
        j++;    // 移动到下一个a元素
    }
    else  // 当前a元素 < 当前b元素,a元素太小,看下一个a元素    
    {
        j++;  // 移动到下一个a元素
    }
}

题解:AtCoder AT_awc0015_d Assignment of Cooking Tasks

满足则i移动,不满足则j移动

// 满足则i移动,不满足则j移动
int i = 1, j = 1;  // 双指针,i: 左边界,j: 右边界
while (i <= n && j <= n)
{
    if (sa[j] - sa[i - 1] > k)  // 区间和超过k
    {
        f[i] = j;  // 记录以i为左边界,区间和刚好超过k的右边界j
        i++;  // 左边界右移
    }
    else if (j == n)  // 右边界已到数组末尾
    {
        f[i] = j;  // 记录以i为左边界的右边界为n
        i++;  // 左边界右移
    }
    else
    {
        j++;  // 右边界右移
    }
}

题解:AtCoder AT_awc0016_d The Path of Carrying Luggage

满足条件则j移动,不满足条件则i移动

// 满足条件则j移动,不满足条件则i移动
int l = 1, r = 1;      // 双指针,表示当前区间[l, r]
while (l <= n && r <= n)  // 双指针遍历数组
{
    // 将a[r]加入最小值队列(维护单调递增)
    while (!dqmin.empty() && dqmin.back().v >= a[r])
    {
        dqmin.pop_back();  // 移除队尾比a[r]大的元素
    }
    dqmin.push_back({a[r], r});  // 将a[r]加入队列

    // 将a[r]加入最大值队列(维护单调递减)
    while (!dqmax.empty() && dqmax.back().v <= a[r])
    {
        dqmax.pop_back();  // 移除队尾比a[r]小的元素
    }
    dqmax.push_back({a[r], r});  // 将a[r]加入队列

    // 调整左指针l,使当前区间[l, r]的最大值-最小值<=k
    while (!dqmin.empty() && !dqmax.empty() && dqmax.front().v - dqmin.front().v > k)
    {
        // 如果队首元素是左指针l指向的元素,则从队列中移除
        if (dqmax.front().idx == l)
        {
            dqmax.pop_front();  // 从最大值队列中移除过期的元素
        }
        if (dqmin.front().idx == l)
        {
            dqmin.pop_front();  // 从最小值队列中移除过期的元素
        }
        l++;  // 左指针右移,缩小区间
    }
    r++;  // 右指针右移,扩展区间
        
    // 统计以r-1为右端点的满足条件的区间数量
    // 区间[l, r-1]满足条件,所以有(r-1)-l+1 = r-l+1个区间
    res += r - l + 1;
}

题解:AtCoder AT_awc0026_e Just the Right Temperature Difference

满足则i和j同时移动,不满足则重新枚举起始位置

int st = 1;  // 起始位置
// 遍历所有可能的起始位置
while (st <= n - m + 1) {
    int i = st, j = 1;
    // 尝试匹配
    while (j <= m && a[i] == b[j]) {
        i++;
        j++;
    }
    // 如果b的所有元素都匹配成功
    if (j > m) {
        cout << st << endl;  // 输出起始位置
        return 0;
    }
    st++;  // 尝试下一个起始位置
}

// 没有找到匹配
cout << -1 << endl;

题解:AtCoder AT_awc0045_b Order of Books on the Bookshelf

【贪心】

任务安排

// 区间 [l,r] 贪心,为解决尽可能安排所有的任务(每个任务都需在[l,r]内寻找一个可行的任务)
bool cmp(Node x, Node y)
{
    if (x.r != y.r)    // 如果右端点不同
        return x.r < y.r;  // 按右端点升序排序
    return x.l < y.l;  // 右端点相同时,按左端点升序排序
}

题解:AtCoder AT_awc0004_d Parking Lot Assignment

区间合并

// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
    vector<PII> res;
    sort(segs.begin(), segs.end());
    int st = -2e9, ed = -2e9;
    for (auto seg: segs) 
	{
        if (ed < seg.first) 
		{
            if (st!=-2e9) res.push_back({st, ed});
            st = seg.first, ed = seg.second;
        } 
		else 
		{
            ed = max(ed, seg.second);
        }
    }
    if (st!=-2e9) res.push_back({st, ed});
    segs = res;
}

题解:AcWing 803 区间合并

区间覆盖

// [1,n]中寻找最少区间覆盖
int r = 0;
for (int i=1; i<=m && r<n; i++)
{
    if (a[i].l > r + 1)
    {
        cout << -1 << endl;
        return 0;
    }
    int maxr = 0, maxi = i;
    for (int j=i; j<=m && a[j].l<=r+1; j++)
    {
        if (a[j].r>maxr) 
        {
            maxr = a[j].r;
            maxi = j;
        }
    }
    if (maxr<=r)
    {
        cout << -1 << endl;
        return 0;    
    }
    r = maxr; 
    cnt++;
    i = maxi;
}
if (r>=n) cout << cnt;
else cout << -1 << endl;
// [s,t]中寻找最少区间覆盖
int r=s;
for (int i=1; i<=n && r<t; i++)
{
	if (ls[i].a > r + 1) 
	{
		cout << "impossible" << endl;
		return 0;
	}
    int maxr = 0, maxi = i;
    for (int j=i; j<=n && ls[j].a<=r; j++)
    {
        if (ls[j].b>maxr)
        {
            maxr = ls[j].b;
            maxi = j;
        }
    }
    if (maxr<=r)
    {
        cout << "impossible" << endl;
        return 0;
    }
    r = maxr;
    cnt++;
    i = maxi;
}
if (r>=t) cout<<cnt;
else cout<<"impossible";
// 确认[0,L]区间是否全覆盖
for (int i = 1; i <= n && r < l; i++)
{
	// 检查是否有间隙:如果当前路由器左边界 > 已覆盖最右位置+1,说明有覆盖不到的区域
	if (a[i].l > r + 1)
	{
		cout << "No" << endl;
		return 0;
	}
	int maxr = 0, maxi = i;  // maxr: 能找到的最大右边界, maxi: 对应路由器的索引
        
	// 在左边界 <= 当前已覆盖位置的路由器中,找到右边界最大的
	for (int j = i; j <= n && a[j].l <= r; j++)
	{
		if (a[j].r > maxr)
		{
			maxr = a[j].r;  // 更新最大右边界
			maxi = j;       // 记录对应的路由器索引
		}
	}
        
	// 如果最大右边界 <= 当前已覆盖位置,说明无法继续扩展
	if (maxr <= r)
	{
		cout << "No" << endl;
		return 0;
	}
        
	r = maxr;    // 更新已覆盖的最右位置
	i = maxi;    // 跳转到具有最大右边界的路由器,继续处理
}

题解:AtCoder AT_awc0006_d Placement of Security Guards

添加更短的任务(更轻的箱子)

// 给定箱子的重量和承重,叠放每个箱子,要求下面的箱子可以承受上面所有箱子的重量
// 按照重量+承重,从小到大排序
bool cmp(Node x, Node y)
{
    return x.d + x.w< y.d + y.w;
}
sort(a+1, a+n+1, cmp);

for (int i = 1; i <= n; i++)  // 遍历所有箱子
{
    if (a[i].d >= tot)  // 如果当前箱子的承重大于等于所有叠放箱子的重量
    {
        pq.push(a[i].w);  // 将箱子加入堆
        tot += a[i].w;  // 增加总重量
    }
    else  // 否则
    {
        if (a[i].w < pq.top())  // 如果当前箱子的重量小于堆中最大箱子的重量
        {
            tot += (a[i].w - pq.top());  // 更新总重量
            pq.pop();  // 移除堆顶箱子
            pq.push(a[i].w);  // 加入当前箱子
        }
        // 否则,不选择当前箱子
    }
}

题解:AtCoder AT_awc0014_a Loading Cargo

检查二分图多重匹配是否存在

sort(l + 1, l + n + 1);  // 对学生能力排序
sort(r + 1, r + m + 1, greater<int>());  // 对老师要求降序排序
int suml = 0, sumr = 0;  // suml: 学生总能力, sumr: 老师总要求
    
for (int i = 1; i <= m; i++)  // 遍历老师
{
    sumr += r[i];  // 累加老师要求
        
    // 查找第一个能力 >= i 的学生位置
    int pos = lower_bound(l + 1, l + n + 1, i) - l;
    // 能力 >= i 的学生数量
    suml += (n - (pos - 1));
    cout << "sumr suml " << sumr << " " << suml << endl; 
    // 如果当前学生的总能力小于老师总要求
    if (suml < sumr)
    {
        cout << "No" << endl;
        return 0;
    }
}

题解:AtCoder AT_awc0031_d Library Inventory Check

哈夫曼编码模型

// 应用:合并果子问题
priority_queue<int, vector<int>, greater<int>> pq;
for (int i = 1; i <= n; i++) {
    pq.push(a[i]);
}
int sum = 0;
for (int i = 1; i < n; i++) {
    int x1 = pq.top(); pq.pop();
    int x2 = pq.top(); pq.pop();
    sum += x1 + x2;
    pq.push(x1 + x2);
}

题解:学而思编程 合并果子

关灯问题

// 0表示关着灯,1表示开着灯,如果出现 1 0 1,那么0就是被打扰的,问最少关掉多少房间的灯
// 贪心策略:如果消去左边的1,那么不会对后面的序列造成任何影响,所以每次发现1 0 1序列时将其转化为1 0 0
for (int i=2; i<n; i++)
	if (a[i-1] && a[i]==0 && a[i+1])
	{
		a[i+1] = 0;
		cnt++;
	}

题解:学而思编程 无法入睡

最大子序列和问题

// 每次操作可以讲子序列中的数都改为他们的平均数,问最多可以使多少个数大于等于x
// 贪心策略:从大到小枚举数字a[i],找到最后一个前i项和>=ans*x的项数i
for (int i=1; i<=n; i++)
{
	tot += a[i];
	if (tot/i>=x) continue;
	else 
	{
		cout << i-1 << endl;
		flag = 0;
		break;
	}
}
if (flag) cout << n << endl;

题解:学而思编程 均富卡

最多得分问题

// 玩家A攻击怪物得1分,玩家B攻击怪物得0分,每次都是A先攻击。A还可以迫使B放弃一次攻击,可使用k次,问A最多得到多少分
// 贪心策略
// 1. 剩余体力值r的伤害次数中只有第一次是A的轮次攻击,后面都得使用特殊能力使B放弃攻击,所以特殊能力的使用次数为(r-1)/a
// 2. 将所有怪物按照A使用特殊能力的次数从小到大排序,优先使用特殊能力次数少的怪物
for (int i=1; i<=n; i++)
{
	cin >> r[i];
	int r;
	if (c[i]%(a+b)==0) r = a+b;
	else r = c[i]%(a+b);
	if (r%a==0) d[i] = (r-1)/a;
	else d[i] = r/a;
}
sort(d+1, d+n+);

题解:学而思编程 与怪物战斗

叠罗汉问题

// 奶牛有自己的重量和力量,彼此叠罗汉,确定顺序,使风险中的最大值尽可能小
// 贪心策略:将所有奶牛按照重量+力量的和从小到大排序
bool cmp(Node x, Node y)
{
	return x.w+x.s < y.w+y.s;
}

题解:学而思编程 奶牛杂技团

字符串连接问题

// n个字符串,只包含s和h,问如何将这些字符串连起来,使得字符串中子序列sh的个数最多
// 贪心策略:将所有字符串按 s个数/h个数 从大到小排序
bool cmp(Node x, Node y)
{
	return x.s * y.h > y.s * x.h;
}

题解:学而思编程 真空扫地机器人

建货仓问题

// 建货仓,使得与每家商店的距离和最小
// 贪心策略:x为中位数,x1和xn中间的数,
sort(a+1, a+n+1);
for (int i=1; i<=(n+1)/2; i++)
	ans += a[n-i+1] - a[i];

题解:学而思编程 货仓选址

// 建货仓,距离d公里的运费是W*d,问建在何处,使得运费之和最小
// 贪心策略:
// 1. 按照d从小到大排序
// 2. 枚举i,当前i项的运费和大于一半时,货仓地址就是在d[i]其中
bool cmp(node x,node y)
{
	return x.d<y.d;
}
for (int i=1;i<=n;i++)
{
	cin>>a[i].w; 
	sum+=a[i].w*a[i].d;
}
sort(a+1,a+n+1,cmp);
int tot=0;
for (int i=1;i<=n;i++)
{
	tot+=a[i].w*a[i].d;
	if (tot>=sum-tot)
	{
		int pos=a[i].d;
		for (int j=1;j<=n;j++)
			ans+=abs(a[j].d-pos)*a[j].w;
		break;
	}
}

题解:学而思编程 货仓选址2

奶牛吃草

// 比较函数:按t[i]/d[i]从大到小排序
bool cmp(Node x, Node y)
{
    return x.t * y.d > x.d * y.t;  // 等价于x.t/x.d > y.t/y.d,避免浮点数
}

题解:洛谷 P2878 [USACO07JAN] Protecting the Flowers S

过河问题

sort(t + 1, t + n + 1);  // 按过河时间从小到大排序
for (idx = n; idx > 3; idx -= 2)  // 每次送最慢的两个人过河
{
    int p_1 = t[2] + t[1] + t[idx] + t[2];  // 方案1
    int p_2 = t[idx - 1] + t[1] + t[idx] + t[1];  // 方案2
    ans += min(p_1, p_2);  // 选择时间短的方案
}
if (idx == 2)  // 如果还剩2个人
{
    ans += t[2];  // 最快的两人一起过
}
else  // 如果还剩3个人
{
    ans += t[1] + t[2] + t[3];  // 特殊处理
}

购买牛奶最小成本(二进制优化)

ans = 1e18;  // 初始答案
sum = 0;  // 当前花费
for (int i = min(31LL, n); i >= 1; i--)  // 从大到小处理
{
    int base = pow(2, i - 1);  // 2^(i-1)
    sum += a[i] * (x / base);  // 用第i个物品填充
    int t = sum;
    if (x % base)  // 如果还有剩余
    {
        t += a[i];  // 多买一个
    }
    ans = min(ans, t);  // 更新最小花费
    x %= base;  // 更新剩余需求
}

题解:洛谷 P15256 [USACO26JAN2] Purchasing Milk B

环形数列移动至平均值的最小次数

// 贪心传递,使每个人达到平均数
for (int i = 1; i < n; i++)
{
    int t = avg - a[i];  // 第i人需要调整的数量
    a[i] = avg;  // 第i人调整为平均数
    a[i + 1] -= t;  // 从下一个人处调整
    b[i + 1] = t;  // 记录传递量
    ans += abs(t);  // 累加操作次数
}
// 尝试正偏移
for (int i = 1;; i++)
{
    int res = calc(i);
    if (res > ans)  // 如果操作次数增加,停止
    {
        break;
    }
    else
    {
        ans = res;  // 更新最少操作次数
    }
}
// 尝试负偏移
for (int i = 1;; i++)
{
    int res = calc(-i);
    if (res > ans)
    {
        break;
    }
    else
    {
        ans = res;
    }
}

题解:洛谷 P4016 负载平衡问题

【位运算】

// 求n的第k位数字(0或1): 
n >> k & 1
// 返回n的最后一位1:
lowbit(n) = n & -n
// 位运算求所有数位的组合(按顺序的组合)
for (int i=0; i<(1<<n); i++)
{
    int x = 0;
    for (int j=0; j<n; j++)
        if ((i>>j) & 1)
            x = x*10 + str[j]-'0';
}
// 例如数字8314,就会生成 0 8 3 83 1 81 31 831 4 84 ...(共16个)

题解:AcWing 801 二进制中1的个数

【离散化】

结构体方式离散化

//对原有数组去重
struct Node
{
    int val, pos, newval;
}a[N];
bool cmp(Node x, Node y)
{
    return x.val < y.val;
}
bool cmp2(Node x, Node y)
{
    return x.pos < y.pos;
}

for (int i=1; i<=n; i++)
{
    cin >> a[i].val;
    a[i].pos = i;
}
sort(a+1, a+n+1, cmp);
int cnt = 0;
for (int i=1; i<=n; i++)
{   
    if (a[i].val!=a[i-1].val)
        cnt++;
    a[i].newval = cnt;
}
sort(a+1, a+n+1, cmp2);  // 根据pos,从小到大还原
// 以下为示例
/*
7
10 9 7 11 10 8 6
1 10 5
2 9 4
3 7 2
4 11 6
5 10 5
6 8 3
7 6 1
14
*/

题解:AcWing 802 区间和

题解:洛谷 P1908 逆序对

vector方式离散化

vector<int> alls;
map<int, int> mp;
// 对alls添加数据(略)
// 离散化处理
vector<int> compressed = compress(alls);
// 建立映射关系:原值 -> 离散化后的索引
for (int i=0; i<alls.size(); i++)
  mp[alls[i]] = compressed[i];

// 离散化函数:将原始值映射到1开始的连续整数
vector<int> compress(vector<int> a)
{
    auto b = a;  // 复制一份原始数据
    sort(b.begin(), b.end());  // 排序
    b.erase(unique(b.begin(), b.end()), b.end());  // 去重
    vector<int> res(a.size());  // 创建结果数组,指定大小避免越界
    for (int i = 0; i < a.size(); i++)
    {
        // 使用二分查找获取离散化后的索引(从1开始)
        res[i] = lower_bound(b.begin(), b.end(), a[i]) - b.begin() + 1;
    }
    return res;
}

题解:AtCoder AT_awc0027_e Selection of Contiguous Intervals

B - 搜索

【DFS-一维】

void dfs(int u)
{
    if (u==n) 
	{  // 到达n层后结束搜索
        for (int i=0; i<n; i++) 
		{
			// 按照题目要求执行,或者打印、计算等
        }
        return ;  // 一定要加这句返回
    }
	// 注意:DFS没有通用模板,每道题都有自己的顺序,这里需要根据每道题进行修改
    for (int i=1; i<=n; i++) 
	{  // 每个位置可以遍历的内容,如这里是可以填入1~n的数字
        if (!st[i]) 
		{  // 判断状态,确定是否可以填入
            path[u] = i;
            st[i] = true;
            dfs(u+1);  // DFS搜索
            // path[u] = 0; // 可以不要这句,因为每次都被赋值
            st[i] = false;
        }
    }
}
// 从第0层开始遍历
dfs(0);
// 剪枝方法1
// 增加sum参数,直接传递当前已选数的总和,避免重复计算,从而提高效率
void dfs(int step, int sum)

// 剪枝方法2
// 只要满足一种方案就退出
void dfs(int step)
{
    if (flag) return;
}

【BFS-一维】

// 该模板存在问题是队列长度只增不减,可使用vector方式保持队列长度始终在范围内
typedef pair<int, int> PII;  // 三个及以上的参数适合用结构体
PII q[N*N];
int d[N][N];  // 记录每个点到起点的距离
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int bfs()
{
	int hh=0, tt=0;
	q[0] = {0, 0};
	memset(d, -1, sizeof(d));  // 初始化每个点到起点的距离
	d[0][0] = 0;  // 起点的距离为0
	while (hh<=tt) 
	{
		auto t = q[hh++];
		for (int i=0; i<4; i++) 
		{
			int x = t.first+dx[i], y = t.second+dy[i];
			if (x>=0 && x<n && y>=0 && y<m && g[x][y]==0 && d[x][y]==-1) 
			{
				d[x][y] = d[t.first][t.second]+1;
				q[++tt] = {x,y};
			}
		}
	}
	return d[n-1][m-1];
}
bfs()  // 调用bfs(),返回最短路径

【BFS-二维】

// 用vector改写
typedef pair<int, int> PII;  // 三个及以上的参数适合用结构体
queue<PII> q;
int d[N][N];  // 记录每个点到起点的距离
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int bfs()
{
	q.push({0,0});
	memset(d, -1, sizeof(d));  // 初始化每个点到起点的距离
	d[0][0] = 0;  // 起点的距离为0
	while (!q.empty()) 
	{
		auto t = q.front(); q.pop();
		for (int i=0; i<4; i++) 
		{
			int x = t.first+dx[i], y = t.second+dy[i];
			if (x>=0 && x<n && y>=0 && y<m && g[x][y]==0 && d[x][y]==-1) 
			{
				d[x][y] = d[t.first][t.second]+1;
				q.push({x,y});
			}
		}
	}
	return d[n-1][m-1];
}
bfs()  // 调用bfs(),返回最短路径
// 寻找最短路
void bfs()
{
    q.push({1, 1, dist[1][1]})
    while (!q.empty())
    {
        Node t = q.front(); 
        int x = t.x, y = t.y, cost = t.cost;
        q.pop();
        for (int i=0; i<4; i++)
        {
            int nx = x + dx[i], ny = y + dy[i];
            if (nx<1 || nx>n || ny<1 || ny>m) continue;
            if (dist[nx][ny]> cost + 1))  // 这里可以根据题目要求修改 
            {
                dist[nx][ny] = cost + 1;
                q.push({nx, ny, dist[nx][ny]});
            }
        }
    }
}
bfs()
// dist[n][m]为最短长度

题解:AcWing 844 走迷宫

折半搜索

void dfs(int pos, int end, ll sum, int type)
{
	if (pos>end)
	{
		if (sum>S) return;
		if (type==1) l[sum]++;
		else r[sum]++;
		return;
	}
	dfs(pos+1, end, sum, type);
	dfs(pos+1, end, sum+a[pos], type);
}

unordered_map<ll, ll> l, r;
int mid = n/2;
dfs(1, mid, 0, 1);
dfs(mid+1, n, 0, 2);
ll ans = 0;
for (auto c : l)
{
	ll left_sum = c.first, left_cnt = c.second;
	if (r[S-left_sum])
		ans += left_cnt * r[S-left_sum];
}
// ans就是方案数

题解:洛谷 P4799 [CEOI 2015] 世界冰球锦标赛 (Day2)

C - 数据结构

【队列】

基本操作

int q[N], hh, tt=-1;
//插入
q[++tt] = x;
//弹出
hh++;
//判断队列是否为空
if (hh<=tt) not empty
else empty
//取出队头元素
q[hh]

题解:AcWing 829 模拟队列

【链表】

list基本操作

(1) 获取起始迭代器: it = ls.begin();  // 指向第一个元素的首地址
(2) 获取结束迭代器: it = ls.end();  // 指向最后一个元素的尾地址
(3) 解引用访问数据: int v = *it;  // 获取当前迭代器指向的元素值  
(4) 向前/后遍历: it++或++it / it--或--it;  // 移动到下/上一个节点
it = ls.end(), it--;  // it指向名为ls的list的最后一个元素
(5) 移动操作:advance(it, n);  // 将迭代器it向前或向后移动n个位置
    next(it);  // 返回it的下一个位置的迭代器(不修改原迭代器)
    prev(it);  // 返回it的上一个位置的迭代器(不修改原迭代器)
(6) 判断两个迭代器的关系:== / !=;  // 判断两个迭代器是否指向同一元素
注意:链表迭代器不能比较大小
遍历方法一:用迭代器循环遍历链表;
for (list<int>:: iterator it = ls.begin(); it != ls.end(); it++)
    cout << *it << endl;
迭代器类型:list<int>:: iterator是双向链表迭代器,支持++和--。
终止条件:it != ls.end() 确保遍历到最后一个有效元素后停止。
前缀递增:++it 或 it++ 即可,表示将迭代器指向下一个元素。
遍历方法二:先定义迭代器,再循环遍历;
list<int>:: iterator it;
for (it = ls.begin(); it != ls.end(); it++)
    cout << *it << endl;
遍历方法三:使用auto自动推导迭代器类型来遍历链表;
for (auto it = ls.begin(); it != ls.end(); it++)
    cout << *it << endl;
遍历方法四:范围循环——自动匹配元素类型,并自动遍历;
for (auto x : ls) cout << x << endl;
(7) ls.push_back(x) / ls.push_front(x): 在链表ls的尾部/头部插入一个元素x;
(8) ls.insert(): 在链表ls中插入元素,返回新插入元素的迭代器;
    ① ls.insert(it, y);  // 在链表ls中it指向的位置之前插入元素y;
    ② ls.insert(it, x, y);  // 在链表ls中it指向的位置之前插入x个元素y;
    ③ ls.insert(it, it1, it2);  // 在链表ls中it指向的位置之前插入[it1, it2]之间的所有元素;
(9) ls.erase(it): 在链表ls中删除it所指向的元素;
    若删除的元素在list的尾部,返回ls.end();
    若删除的元素不再list的尾部,返回下一个元素的迭代器;
(10) ls.empty(): 判断链表ls是否为空;
(11) ls.clear(): 清空链表sl
(12) ls.size(): 返回链表ls中的元素个数;
(13) ls.remove(x): 在链表ls中删除所有等于x的元素;
(14) ls.reverse(): 反转链表ls中元素的顺序;
(15) ls.splice(): 将一个链表或子链表移动到当前链表的执行位置;
    ① 移动整个链表: s.splice(it, t); 将整个链表t都移动到链表s的指定位置it处;
    ② 移动单个元素: s.splice(it1, t, it2); 将链表t中it2指向的元素移动到链表s的指定位置it1处;
    ③ 移动子链表: s.splice(it1, t, it2, it3); 将链表t中[it2, it3)之间的元素移动到链表s的指定位置it1处;

单链表

// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
    head = -1;
    idx = 0;
}
// 在链表头插入一个数a
void add_to_head(int a)
{
    e[idx] = a, ne[idx] = head, head = idx++;
}
// 将x插到下标是k的点后面
void add(int k, int x)
{
    e[idx] = x, ne[idx] = ne[k], ne[k] = idx++;
}
// 将头结点删除,需要保证头结点存在
void remove_head()
{
    head = ne[head];
}
// 将下标是k的点后面的点删掉
void remove(int k)
{
    ne[k] = ne[ne[k]];
}

题解:AcWing 826 单链表

双链表

// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;
// 初始化
void init()
{
    //0是左端点,1是右端点
    r[0] = 1, l[1] = 0;
    idx = 2;
}
//在下标是k的点的右边,插入x
void add(int k, int x) 
{
    e[idx] = x;
    r[idx] = r[k], l[idx] = k;
    l[r[k]] = idx, r[k] = idx++;
}
//删除第k个点
void remove(int k) 
{
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

题解:AcWing 827 双链表

【单调队列】

基本声明方式

struct Node
{
    int v, idx; 
};
deque<Node> dq;  


for (int i = 1; i <= n; i++)  // 遍历数组
{
    while (!dq.empty() && dq.front().idx <= i - k)
        dq.pop_front();
    // 维护minq队列:保持队列单调递增
	// 如果修改为dq.back().v <= a[i],则维护maxq队列:保持队列单调递减
    while (!dq.empty() && dq.back().v >= a[i])
        dq.pop_back();
    dq.push_back({a[i], i});  // dq.front().v为最小值
  
    // 按照题目要求输出最小值、最大值等
}

题解:AcWing 154 滑动窗口

优化动态规划

// 用于优化动态规划
// 队列头存放dp[]的最小值,每次将 {dp[i], i} 插入队列
// 状态转移方程
dp[i] = dq.front().v + a[i];
// 超出窗口范围的元素,dq.front().idx < i - k,需要弹出队首
while (!dq.empty() && dq.front().idx < i - k)
        dq.pop_front();
// 维护minq队列:保持队列单调递增
while (!dq.empty() && dq.back().v >= dp[i])
        dq.pop_back();
dq.push_back({dp[i], i});

题解:AtCoder AT_awc0033_e Minimum Cost of Stepping Stones

【栈】

基本操作

// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[++tt] = x;
// 从栈顶弹出一个数
tt--;
// 栈顶的值
stk[tt];
// 判断栈是否为空,如果 tt > 0,则表示不为空
if (tt > 0) not empty
else empty

题解:AcWing 828 模拟栈

题解:AcWing 3302 表达式求值

【并查集】

基本操作

// (1)朴素并查集+路径压缩:
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点
int find(int x) 
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
void merge(int a, int b)
{
	p[find(a)] = find(b);
}


// (2)维护size的并查集:
int p[N], siz[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回x的祖宗节点
int find(int x) 
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) 
{
    p[i] = i;
    size[i] = 1;
}
// 合并a和b所在的两个集合:
void merge(int a, int b)
{
	if (find(a)==find(b)) return;
	size[find(b)] += size[find(a)];
	p[find(a)] = find(b);
}


// (3)维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
    if (p[x] != x) 
	{
        int u = find(p[x]);
        d[x] += d[p[x]];
        p[x] = u;
    }
    return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) 
{
    p[i] = i;
    d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = d[y]-d[x]; // X和Y是同类
d[find(a)] = d[y]+1-d[x];  // X吃Y

题解:AcWing 836 合并集合

题解:AcWing 837 连通块中点的数量

题解:AcWing 240 食物链

【线段树】

区修+区查

int n, m; 
int w[N];
struct Node
{
    int l, r;
    int dt, mn;  // (根据题目要求变化)求最小值,dt是懒标记
}tr[N*4];

void pushup(int u)  // 由子节点的信息,来计算父节点的信息
{
    tr[u].mn = min(tr[u<<1].mn, tr[u<<1|1].mn);  // (根据题目要求变化)求最小值
    // 如果要求区间和
    // tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum
}
void pushdown(int u)
{
    auto &root = tr[u], &l = tr[u<<1], &r = tr[u<<1|1];
    l.dt += root.dt, l.mn += root.dt;
    r.dt += root.dt, r.mn += root.dt;
    // 如果要求区间和
    // l.sum += root.dt * (l.r - l.l + 1)
    // r.sum += root.dt * (r.r - r.l + 1)
    root.dt = 0;
}
void build(int u, int l, int r)
{
    if (l==r) tr[u] = {l, r, 0, w[l]};
    else
    {
        tr[u] = {l, r};
        int mid = l+r >> 1;
        build (u<<1, l, mid), build(u<<1|1, mid+1, r);
        pushup(u);
    }
}
void update(int u, int l, int r, int d)
{
    if (tr[u].l>=l && tr[u].r<=r)
    {
        tr[u].dt += d, tr[u].mn += d;  // (根据题目要求变化)修改值
        // 如果要求区间和
        // tr[u].sum += d * (tr[u].r - tr[u].l + 1)
    }
    else 
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l<=mid) update(u<<1, l, r, d);
        if (r>mid) update(u<<1|1, l, r, d);
        pushup(u);
    }
}
int query(int u, int l, int r)
{
    if (tr[u].l>=l && tr[u].r<=r)  // 树中节点,已经被完全包含在[l,r]中了
    {
        return tr[u].mn;
        // 如果要求区间和
        // return tr[u].sum;
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        int res = INF;  // (根据题目要求变化)设最大值
        if (l<=mid) res = query(u<<1, l, r);
        if (r>mid) res = min(res, query(u<<1|1, l, r));  // 求最小值
        // 如果要求区间和
        // if (l<=mid) res += query(u<<1, l, r);
        // if (r>mid) res += query(u<<1|1, l, r);  // 求最小值
        return res;
    }
}

题解:AcWing 1275 最大数

点修+区查+二分

int n, m; 
int w[N];
struct Node
{
    int l, r;  // 区间左右端点
    int mx;  // mx: 区间最大值
}tr[N * 4];  // 线段树数组,开4倍空间

void pushup(int u)
{
    tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);  // 取左右子树的最大值
}

void build(int u, int l, int r)
{
    if (l == r)  // 叶节点
    {
        tr[u] = {l, r, c[l]};  // 初始懒标记为0,值为机器容量
    }
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);  // 递归构建左右子树
        pushup(u);  // 更新当前节点的值
    }
}

// 单点更新:将位置pos的值更新为d
void update(int u, int pos, int d)
{
    if (tr[u].l == tr[u].r)  // 找到叶节点
    {
        tr[u].mx = d;  // 更新值
        return;
    }
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (pos <= mid)  // 在左子树
        {
            update(u << 1, pos, d);
        }
        if (pos > mid)  // 在右子树
        {
            update(u << 1 | 1, pos, d);
        }
        pushup(u);  // 更新当前节点的值
    }
}

// 区间查询:查询区间[l, r]的最大值
int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)  // 当前节点完全包含在查询区间内
    {
        return tr[u].mx;
    }
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        int res = -INF;  // 初始化为负无穷
        if (l <= mid)  // 与左子树有交集
        {
            res = query(u << 1, l, r);
        }
        if (r > mid)  // 与右子树有交集
        {
            res = max(res, query(u << 1 | 1, l, r));  // 取最大值
        }
        return res;
    }
}

// 在区间[l, r]中查找第一个大于等于val的位置,找不到返回-1
int find(int u, int l, int r, int val)
{
    if (tr[u].mx < val)  // 当前区间最大值小于val,不可能找到
    {
        return -1;
    }
    if (tr[u].l == tr[u].r)  // 叶节点
    {
        return tr[u].l;  // 返回位置
    }
    
    int mid = (tr[u].l + tr[u].r) >> 1;
    int res = -1;
    if (l <= mid)  // 先在左子树找
    {
        res = find(u << 1, l, r, val);
    }
    if (res == -1 && r > mid)  // 左子树没找到,在右子树找
    {
        res = find(u << 1 | 1, l, r, val);
    }
    return res;
}

题解:AtCoder AT_awc0020_e Shelving Books on a Bookshelf

【树状数组】

基本操作

int lowbit(int x)  //提出x的低位2次幂数
{
    return x & -x;
}
void add(int x, int c)  //向后修
{
    for (int i=x; i<=n; i+=lowbit(i)) tr[i] += c;
}
int query(int x)  //向前查
{
    int res = 0;
    for (int i=x; i; i-=lowbit(i)) res += tr[i];
    return res;
}

// 初始化树状数组
for (int i = 1; i <= n; i++)
    cin >> a[i];

// 初始化树状数组
// 将原始数组转换为差分数组存储到树状数组中
for (int i = 1; i <= n; i++)
    add(i, a[i] - a[i - 1]);  // 存储差分值

题解:AcWing 241 楼兰图腾

阈值分治解决技巧

// 阈值分治解决方案
if (k <= B)  // 小k,使用分块标记
    tag[k] += v;
else  // 大k,直接修改树状数组
{
    for (int i = k; i <= n; i += k)
        add(i, v);
}

int x;
cin >> x;  // 查询前缀和
int ans = sa[x] + query(x);  // 基础前缀和+树状数组修改
for (int k = 1; k <= B; k++)  // 加上分块标记的贡献
    ans += tag[k] * (x / k);
cout << ans << endl;

题解:AtCoder AT_awc0032_e Multiple Bonus

区间不同物种的数量查询

// 区间查询物品数量,可以使用last[]保存物品最后一次出现位置,按照 r 从小到大排序后一边查询,一边修改
int cur = 1;  // 当前处理的数组位置
for (int i = 1; i <= q; i++)  // 处理每个查询
{
	int l = Q[i].l, r = Q[i].r, id = Q[i].id;
        
	// 处理数组位置,直到达到当前查询的右端点
	while (cur <= r)
	{
		int species = p[cur];  // 当前位置的物种
		if (last[species] != 0)  // 如果这个物种之前出现过
		{
			add(last[species], -1);  // 在之前出现的位置-1
		}
		add(cur, 1);  // 在当前位置+1
		last[species] = cur;  // 更新这个物种最后出现的位置
		cur++;
	}
        
	// 查询区间[l, r]的不同物种数量
	ans[id] = query(r) - query(l - 1);
	}

题解:AtCoder AT_awc0015_e Count the Types of Flowers

posted @ 2026-05-26 22:01  热爱编程的通信人  阅读(5)  评论(0)    收藏  举报