算法模板:基础算法做题积累

Posted zhezhidashi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法模板:基础算法做题积累相关的知识,希望对你有一定的参考价值。

算法模板(1):基础算法(3)做题积累

一、C++语法问题

编译优化

  • 开启 O1 优化:#pragma GCC optimize(1)

  • 开启 O2 优化:#pragma GCC optimize(2)

  • 开启 O3 优化:#pragma GCC optimize(3)

stringstream && getline

  • 用的时候带上"``iostream", "sstream", "string`"三个头文件。
  • getline(cin, line)。其中line是一个string类对象。geiline结束读入标志是回车。但是一开始就遇到换行符的话还是会读入。
  • stringstream ss(line),其中line是一个string类对象。
  • int x; while(ss >> x),这样来把line中的数字挨个读入到 int 型变量里面。

二进制转化为格雷码方法:

unordered_map 键用 pair 类型。

struct pair_hash

    template<class T1, class T2>
    std::size_t operator() (const std::pair<T1, T2>& p) const
    
        auto h1 = std::hash<T1>(p.first);
        auto h2 = std::hash<T2>(p.second);
        return h1 ^ h2;
    
;
unordered_map<pair<int, bool>, int, pair_hash> Map;

lower_bound && upper_bound

  • lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

  • upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

提高 cin, cout 速度

ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);

大质数

  • 二进制下57位大质数:4179 34045 41998 20289LL
//求 x * y % mod, ll是long long, llf 是 long double
ll mul(ll x, ll y)

    ll tmp = x * y - ll(llf(x) * llf(y) / llf(mod)) * mod;
    return tmp < 0 ? tmp + mod : (tmp < mod ? tmp : tmp - mod);

随机数

mt19937 rnd(0);
int x = rnd();

二、排序

1. 122. 糖果传递

  • 环形均分纸牌问题

  • n n n 个小朋友坐成一圈,每人有 a [ i ] a[i] a[i] 个糖果。每人只能给左右两人传递糖果。

    每人每次传递一个糖果代价为 1。

    求使所有人获得均等糖果的最小代价。

  • 设第 i i i 个人给第 i − 1 i - 1 i1 个人的糖果数为 X i X_i Xi, 设所有人糖果的平均数是 A A A,那么第 i 个人的糖果数量是 a i − X i + X i + 1 a_i - X_i + X_i+1 aiXi+Xi+1.

  • f f f a a a 的前缀和,记 c [ i ] c[i] c[i] f [ i − 1 ] − ( i − 1 ) ∗ a f[i - 1] - (i - 1) * a f[i1](i1)a,mid 为 c 的中位数

对于第1个小朋友,A1-X1+X2=ave -> X2=ave-A1+X1 = X1-C1 (假设C1=A1-ave,下面类似)
对于第2个小朋友,A2-X2+X3=ave -> X3=ave-A2+X2=2ave-A1-A2+X1=X1-C2 即 C2=A1+A2-2ave=A2+C1-ave 以此类推
对于第3个小朋友,A3-X3+X4=ave -> X4=ave-A3+X3=3ave-A1-A2-A3+X1=X1-C3
我们的Ci的通式为 Ci=Ai+C[i-1]-ave!!!
……
对于第n个小朋友,An-Xn+X1=ave。
我们希望Xi的绝对值之和尽量小,即|X1| + |X1-C1| + |X1-C2| + ……+ |X1-Cn-1|要尽量小。注意到|X1-Ci|的几何意义是数轴上的点X1到Ci的距离

  • 其实就是列几个方程就推出来了,在纸上写一写就出来了

  • 答案就是 ∣ c 1 − m i d ∣ + ∣ c 2 − m i d ∣ + . . . + ∣ c n − m i d ∣ . |c_1 - mid| + |c_2-mid|+...+|c_n-mid|. c1mid+c2mid+...+cnmid∣.

int main() 
	int N;
	scanf("%d", &N);
	for (int i = 1; i <= N; i++) 
		scanf("%lld", &a[i]);
		a[i] += a[i - 1];
	
	ll A = a[N] / N;
	for (int i = 1; i <= N; i++) 
		c[i] = a[i - 1] - (i - 1) * A;
	
	ll ans = 0;
	sort(c + 1, c + N + 1);
	ll mid = c[(N + 1) / 2];
	for (int i = 1; i <= N; i++) 
		ans += abs(c[i] - mid);
	
	printf("%lld\\n", ans);
	return 0;

2. 105. 七夕祭

  • 一个 N ∗ M N*M NM 的方格,其中若干方格点上面放了一个糖果。让每一行和每一列的糖果数量相等,求最小操作数。一次操作就是把相邻的两个元素交换,并且每行每列都可以看作一个环,就是第一个元素和最后一个元素相连。就转化为二维的环形均分纸牌问题

首先输出一个字符串。

如果能满足 Vani 的全部两个要求,输出 both;

如果通过调整只能使得各行中 cl 感兴趣的摊点数一样多,输出 row;

如果只能使各列中 cl 感兴趣的摊点数一样多,输出 column;

如果均不能满足,输出 impossible。

如果输出的字符串不是 impossible, 接下来输出最小交换次数,与字符串之间用一个空格隔开。

  • 行和列是独立的. 因为对行操作影响的是列, 对列操作影响的是行. 因此我们只需要求一下每行每列的和,这样就完全转化为一维的环形均分纸牌问题.

3. 106. 动态中位数

  • 这道题也可以用平衡树来做,不过用堆更简单。
  • 我们只关注中位数是多少,而前后是不用排序的。 所以,可以用一个小根堆和一个大根堆维护。
  • 需要满足两个性质:小根堆的所有元素大于等于大根堆所有元素;大根堆的元素个数至多比小根堆多一个。那么,中位数就是大根堆堆顶元素。
  • 第一个性质很好满足,对于一个新来的数x,如果大于小根堆堆顶元素,就放到小根堆里面,否则放到大根堆里面。
  • 一定要万分小心,que.size() 返回值是size_t,而无符号整数与有符号整数直接判断的话,会强制转化为无符号整数,因此会出现 -2 > 1 的情况。

三、二分

102. 最佳牛围栏

  • 题意:把选出一个连续子序列,该子序列至少有 F F F 元素. 问每个子序列的平均值可能的最大值是多少 ( n ≤ 1 0 5 ) . (n \\le 10^5). (n105).

  • 思路很简单,就是二分出答案。然后我们根据当前二分,把所有的数字都减去这个二分的结果。这样我们的目标就是找到一个连续子序列,使得平均值不小于0. 不需要写二重循环,打一个前缀和。固定右端点 r r r,寻找左端点 l l l,其实就是找到 1 ~ l l l 的前缀和最小值 m i n v minv minv,则固定右端点为 r r r 时能取到的最大值为 s [ r ] − m i n v s[r] - minv s[r]minv.

  • ans 在 [ l , r ] [l,r] [l,r] 范围里, l < = a n s < = r l <= ans <= r l<=ans<=r,因为向下取整,所以应该取输出时应取r.

  • 这道题循环次数过多的话会出现精度问题,感觉不太对劲儿.

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100010;
double a[maxn], s[maxn];
int N, F;
bool C(double x) 
	for (int i = 1; i <= N; i++) s[i] = a[i] - x + s[i - 1];
	double mins = 0, res = -1e9;
	for (int i = F; i <= N; i++) 
		mins = min(mins, s[i - F]);
		res = max(res, s[i] - mins);
	
	return res >= 0;

int main() 
	scanf("%d%d", &N, &F);
	for (int i = 1; i <= N; i++) scanf("%lf", &a[i]);
	double lb = 0, ub = 2000;
	while(ub - lb > 1e-5) 
		double mid = (lb + ub) / 2;
		if (C(mid)) lb = mid;
		else ub = mid;
	
	int ans = (int)(ub * 1000);
	printf("%d\\n", ans);
	return 0;

113. 特殊排序

  • 看一看交互题是怎么写的. 给 n n n 个点的图,有 n ( n − 1 ) 2 \\fracn(n-1)2 2n(n1) 条有向边,求一种排序方案. 该关系满足自反性,但不满足传递性.
  • 二分找到一个 r e s [ l ] ≤ i ≤ r e s [ r ] res[l] \\le i \\le res[r] res[l]ires[r] 的位置. 然后把 i i i 插入 r e s [ l ] res[l] res[l] r e s [ r ] res[r] res[r] 之间.
// Forward declaration of compare API.
// bool compare(int a, int b);
// return bool means whether a is less than b.

class Solution 
public:
    vector<int> specialSort(int N) 
        vector<int> res;
        res.push_back(1);
        for(int i = 2; i <= N; i++)
            int l = 0, r = res.size();
            while(r - l > 1)
                int mid = (l + r) / 2;
                if(compare(res[mid], i)) l = mid;
                else r = mid;
            
            res.push_back(i);
            for(int j = res.size() - 2; j >= r; j--) swap(res[j], res[j + 1]);
            //下面这个情况只可能是 l = 0 时. 否则一定会有一个 res[mid] < i 的情况使得 l = mid。
            if(compare(i, res[l])) swap(res[l], res[l + 1]);
        
        return res;
    
;

四、差分

100. 增减序列