蓝书(算法竞赛进阶指南)学习笔记

算法竞赛进阶指南——笔记

0X00

- 0x01位运算

知识:

  1. 位移(左移右移
    快速幂 快速乘等等
  2. 二进制压缩 (隐隐约约指向 状压DP
    基操
    1. 取数
  3. 位运算特点 二进制下不进位
    利用这特点可以我们可以做题
  4. 小技巧(应用邻接表 我不用)
    1. n为偶数时 n ^ 1 = n + 1;
    2. n为基数时 n ^ 1 = n - 1;
  5. lowbit 运算 最低位的1与之后的0 广泛应用于各地

例题

  1. CH0101/Acwing
    (vjudge)
    求a^b的次方mod p
    数据范围为1e9/1e18
    我们已知所有数可以拆分为2进制表示,所以我们可以拆分 b 得到 b = . . . . . . b = ...... b=......
    即快速幂
// 对于 1e9 的数据 可以强制类型转换
	ans = 1;
	ans %= p;
	while(b){
		if(b&1)ans = (long long)ans * a % p;
		a = (long long)a * a % p;
		b >>= 1;
	}
	printf("%d",ans%p);

对于1e18的数据我们可以用高精 龟速乘

#define intl long long
intl mul(intl a,intl b,intl p){
	intl ans = 0%p;
	while(b){
		if(b&1)ans =( ans + a ) % p;
		a = ( a + a ) % p;
		b >>= 1;
	}
	return ans;
}
intl power(intl a,intl b,intl p){
	intl ans = 1%p;
	while(b){
		if(b&1)ans = mul(ans,a,p) % p;
		a = mul(a,a,p) % p;
		b >>= 1;
	}
	return ans;
}
int main(){
	intl x,y,p;
	while(cin >> x >> y >> p){
		printf("%lld\n",power(x,y,p));
	}
	return 0;
}

有另一个快速乘

#define intl unsigned long long
intl mul(intl a,intl b,intl p){
	return (a*b -(intl)((long double)a/p*b)*p+p)%p;
}
  1. 状压DP初级(eee)
    简单描述
    f[i][j]表示在 j 点上我们有 i 为我们的状态
    在 j 点之前,先到点 k
    可以得出转移方程
f[i][j] = min(f[i][j],f[i^(1<<j)][k] + fin[j][k]);
#include<bits/stdc++.h>
using namespace std;
int n;
int fin[25][25];
int f[1<<20][20];
int main(){
	cin >> n;
	for(int i = 0;i < n;i ++)
		for(int j = 0;j < n;j ++)
			cin >> fin[i][j];
	memset(f,0x3f,sizeof(f));
	f[1][0] = 0;
	for(int i = 1;i < (1<<n);i ++)
		for(int j = 0;j < n;j ++)
			if( (i>>j) & 1)
				for(int k = 0;k < n;k ++)
					if((i ^ (1<<j))>>k&1)
						f[i][j] = min(f[i][j],f[i^(1<<j)][k] + fin[j][k]);
	printf("%d",f[(1<<n)-1][n-1]);
	return 0;
}
  1. 二进制特点 互不影响LGP2114
int n,m;
pair<string,int>op[100100];
int answer,answ;
int deal(int pet,int id){
	for(int i = 1;i <= n;i ++){
		int x = op[i].second >> pet & 1;
		if(op[i].first[0] == 'A'){
			id = id & x;
		}
		else if(op[i].first[0] == 'O'){
			id = id | x;
		}
		else if(op[i].first[0] == 'X'){
			id = id ^ x;
		}
	}
	return id;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i ++){
		cin >> op[i].first;cin >> op[i].second;
	}
	for(int i = 30;i >= 0;i --){
		int a = deal(i,0);//我们得出该位填0/1时
		int b = deal(i,1);//我们可以得出的结果
		if(answ + (1<<i) <= m && b > a){
			answ += (1<<i);answer += b * (1<<i);
		}
		else answer += a * (1<<i);
	}
	printf("%d",answer);
	return 0;
}
  1. lowbit
lowbit(x) = x & (-x);
  1. 内置函数
int __builtin_ctz(unsigned int x)
int __builtin_ctzll(unsigned long long x)
返回x的二进制下最低位的1后面有多少个0
int __builtin_popcount(unsigned int x)
int __builtin_popcountll(unsigned long long x)
返回x的二进制下有多少位是1

the end for the first and we have the second

- 0x02递推递归

知识

  1. 递归和递推为程序遍历状态空间的两种基本方式
    当一个问题我们可以在边界小范围特殊情况下,可以知道答案
    并且我们可以以相似的方法扩展状态空间
    我们可以考虑用递归递推求解~~(有DP味了)~~
  2. 简单的应用
    我们可以用递归递推枚举
    如 多项式型,指数型,组合型,排列型
  3. 分治,对于一些问题我们可以将问题划分为若干规模更小的问题
    递归求解
    回溯时推出原解
  4. 分形 (暂不会)
  5. 递归机器的实现

例题

  • 我们先来简单的枚举

    1. 指数型枚举(类似 状压DP,可以用位运算)2^n种
    2. 排列型枚举(额 如我要跑一个图 不太好理解)
      全排列(递归)
    3. 组合型枚举
      从1 ~ n中选出m个(剪枝)
    ----------------指数型
    int n;
    vector<int>v;
    void dfs(int x){
        if(x == n + 1){
            for(int i = 0;i < v.size();i ++){
                cout << v[i] << ' ';
            }
            cout << endl;
            return ;
        }
        dfs(x+1);
        v.push_back(x);
        dfs(x+1);
        v.pop_back();
    }
    
    int main(){
        scanf("%d",&n);
        dfs(1);
        return 0;
    }
    ----------------排列型
    void dfs(int x){
        if(x == n + 1){
            for(int i = 0;i < v.size();i ++)cout << v[i] << 	' ';
            cout << endl;
            return ;
        }
        for(int i = 1;i <= n;i ++){
            if(vis[i] == 0){
                vis[i] = 1;
                ord[i] = x;
                v.push_back(i);
                dfs(x+1);
                v.pop_back();
                ord[i] = 0;
                vis[i] = 0;
            }
        }
    }
    ----------------组合型
    void dfs(int x){
        if(v.size() > m || v.size() + (n - x  + 1) < m)	return ;
        if(v.size() == m){
            for(int i = 0;i < v.size();i ++)cout << v[i] << 	' ';
            cout << endl;
            return ;
        }
        
        v.push_back(x);
        dfs(x+1);
        v.pop_back();
        dfs(x+1);
    }
    
  • 我们可以继续学 分治
    我认为分治挺好(还有CDQ分治&& 动态树分治)

        /***********************************************************
      > File Name: 02T2.cpp
      > Author: lan_m
      > QQ: 2867930696
      > Created Time: 2021/9/7 20:40:35
      > Modified Time:2021/9/7 22:17:35
      > fighting for night
     *******************************************************/
    #include <bits/stdc++.h>
    // (分治,有一点数学知识)
    using namespace std;
    const int mod = 9901;
    int a,b;
    int power(int x,int y){
    	int sum = 1;
    	while(y){
    		if(y&1)sum = sum % mod * x % mod;
    		x = x % mod * x % mod;
    		y>>=1;
    	}
    	return sum;
    }
    
    int sum(int p,int t){
    	if(t == 0){
    		return 1;
    	}
    	if(t % 2 == 0){
    		return ((1 + power(p,t/2)) % mod * sum(p,(t/2)-1) % mod + power(p,t))%mod;
    	}
    	else {
    		return (1 + power(p,(t+1)/2))%mod * sum(p,(t-1)/2)%mod;
    	}
    }
    
    int answer = 1;
    void deal(int x){
    	for(int i = 2;i <= x;i ++){
    		int cnt = 0;
    		while(x % i == 0){
    			x /= i;
    			cnt++;
    		}
    		if(cnt){
    		    answer =( answer * sum(i,cnt * b)%mod ) % mod;
    		}
    	}
    }
    
    int main(){
    	scanf("%d%d",&a,&b);
    	deal(a);
    	if(a != 0)printf("%d",answer);
    	else printf("0");
    	return 0;
    }
    //--------------------------------//
    /***********************************************************
      > File Name: 02T4.cpp
      > Author: lan_m
      > QQ: 2867930696
      > Created Time: 2021/9/8 8:24:18
      > Modified Time:2021/9/8 8:24:18
      > fighting for night
     *******************************************************/
    //汉诺塔问题
    #include <bits/stdc++.h>
    
    using namespace std;
    int n;
    int d[100100];//三塔问题
    int f[100100];//四塔
    
    int main(){
    		n = 12;
    		for (int i = 1;i <= n;i ++) {
    			d[i] = d[i-1] * 2 + 1;
    		}
    		memset(f,0x3f,sizeof(f));
    		f[0] = 0;
    		for (int i = 1;i <= n;i ++) {
    			for (int j = 0;j <= i;j ++) {
    				f[i] = min(f[i] , 2 * f[j] + d[i-j]);
    			}
    		}
    		for (int i = 1;i <= n;i ++) {
    			cout << f[i] << endl;
    		}
    	return 0;
    }
    
  • 分形

    tomorrow see code 
    the code was so hard to write so i don't want to do it 
    
  • 递归机器实现

    code is missing
    

the end for the second and we have the third

- 0x03前缀和与差分

知识

  1. 对于一个序列,我们可以递推出前缀和
    用前缀和我们可以求出部分区间和
    对于一个矩阵可以做二维前缀和
  2. 我们对一个序列做差分,可以发现差分的前缀和是原数列,前缀和的差分也是原数列
    差分将区间操作转化为差分序列上单点操作

例题

  1. 前缀和 注意细节
    for (int i = 1;i <= 5005;i ++) {
    	for (int j = 1;j <= 5005;j ++) {
    		s[i][j] = s[i][j] + s[i][j-1] + s[i-1][j] - s[i-1][j-1];
    	}
    }
    int maxx = 0;
    for (int i = r;i <= 5005; i ++) {
    	for (int j = r;j <= 5005;j ++) {
    		maxx = max(maxx , s[i][j] - s[i - r][j] - s[i][j-r] + s[i-r][j-r]);
    	}
    }
    printf("%d",maxx);
    
  2. 差分
    for (int i = 2;i <= n;i ++) {
        d[i] = a[i] - a[i-1];
    }
    //去重
    map<pair<int,int>,bool>dxy;
    
    
    if(dxy[make_pair(a,b)])continue;
    dxy[make_pair(a,b)] = 1;
    

- 0x04二分

  1. 整数二分
    可以用两套二分写法
  2. 实数二分
  3. 三分
  4. 二分答案

例题:
innovative business (acwing 113)

  1. 实现,两种方法,两种边界
    /***********************************************************
      > File Name: 04T1.cpp
      > Author: lan_m
      > QQ: 2867930696
      > Created Time: 2021/9/13 16:06:49
      > fighting for night
     *******************************************************/
    
    #include <bits/stdc++.h>
    
    using namespace std;
    int n;
    int a[10010];
    int main () {
    	scanf("%d",&n);
    	for (int i = 1;i <= n;i ++) scanf("%d",&a[i]);
    	sort(a+1,a+1+n);
    	int x;
    	cin >> x;
    	int l = 0;int r = n;
    	while ( l < r ) {
    		int mid = ( l + r + 1 ) >> 1;
    		if ( a[mid] <= x ) l = mid;
    		else r = mid - 1;
    	}
    	cout << a[l] << endl;
    	l = 1;r = n + 1;
    	while ( l < r ) {
    		int mid = ( l + r ) >> 1;
    		if ( a[mid] >= x ) r = mid;
    		else l = mid + 1;
    	}
    	cout << a[l] << endl;
    	return 0;
    }
    
  2. 这是关于实数域上的二分
    两种方法
    确定精度 固定循环次数
    /***********************************************************
      > File Name: 04T2.cpp
      > Author: lan_m
      > QQ: 2867930696
      > Created Time: 2021/9/13 16:19:02
      > fighting for night
     *******************************************************/
    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    int main () {
    	//1
    	while (1 + 1e-5 < r) {
    		double mid = ( l + r ) / 2;
    		if (calc(mid)) r = mid;else l = mid;
    	}
    	//2
    	for (int i = 0;i < 100;i ++) {
    		double mid = ( l + r ) / 2;
    		if (calc(mid)) r = mid;else l = mid;
    	}
    	return 0;
    }
    
  3. 三分求单峰函数
    missing
    
  4. 二分答案转判定(这挺好用的)
    例题 二分实数域还是用**while**
    #include <bits/stdc++.h>
    
    using namespace std;
    const double eps = 1e-5;
    int n,F;
    double a[100100];
    double sum[100100];
    double maxn = 0;
    
    bool check(double mid) {
        for (int i = 1;i <= n;i ++) sum[i] = sum[i-1] + a[i] - mid;
        double ans,val;
        ans = -1e8;val = 1e8;
        for (int i = F;i <= n;i ++) {
            val = min (val , sum[i-F]);
            ans = max (ans , sum[i] - val);
        }
        if (ans >= 0) return true;
        else return false;
    }
    
    int main()
    {
        scanf("%d%d", &n,&F);
        for (int i = 1; i <= n; i ++ ) {
            scanf("%lf", &a[i]);
        }
        double l = -1e6;double r = 1e6;
        while (l + eps < r ) {
            double mid = ( l + r ) / 2;
            if( check(mid) )l = mid;
            else r = mid;
        }
        printf("%lld", (long long)(r * 1000));
        return 0;
    }
    
    

long time ago

- 0x05排序

  1. 离散化 (这是一个悲伤的故事)
    一种映射
    可以映射大小,也可以映射其他东西
    比较好的应用,就我们算法与值域有关时,我们可以把时间复杂度降为与n(n个整数)有关。
  2. 中位数
    众所周知,中位数有一些优美的性质
  3. 经典的 第K大数 问题
  4. 逆序对,我们可以用msort(归并排序),也可以用树状数组,一些其他东西

例题
Cinema cf670c
货仓选址 acwing 104
七夕祭 acwing 105
Running Median acwing106
Ultra-QuickSort acwing107
奇数码问题 acwing108

  1. Cinema
    /***********************************************************
      > File Name: 05T1-CF670C.cpp
      > Author: lan_m
      > QQ: 2867930696
      > Created Time: 2021/9/14 10:23:34
      > fighting for night
     *******************************************************/
    
    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 2e5+10;
    int n,m;
    int peo_lang[N];
    int cin_lang[N];
    int cin_fan[N];
    int lang[N<<2];
    int sum[N];
    int cnt;
    int main () {
    	scanf("%d",&n);
    	for (int i = 1;i <= n;i ++) { scanf("%d",&peo_lang[i]);lang[++cnt] = peo_lang[i]; }
    	scanf("%d",&m);
    	for (int i = 1;i <= m;i ++) { scanf("%d",&cin_lang[i]);lang[++cnt] = cin_lang[i];}
    	for (int i = 1;i <= m;i ++) { scanf("%d",&cin_fan[i]);lang[++cnt] = cin_fan[i];}
    	
    	sort(lang+1,lang+cnt+1);
    	cnt = unique(lang+1,lang+cnt+1) - lang;
    
    	for (int i = 1;i <= n;i ++) {
    		peo_lang[i] = lower_bound(lang+1,lang+cnt+1,peo_lang[i]) - lang;
    		sum[peo_lang[i]] ++;
    	}
    	for (int i = 1;i <= m;i ++) { 
    		cin_lang[i] = lower_bound(lang+1,lang+cnt+1,cin_lang[i]) - lang;
    		cin_fan[i] = lower_bound(lang+1,lang+cnt+1,cin_fan[i]) - lang;
    	}
    	int maxn = 0,op = 1;
    	for (int i = 1;i <= m;i ++) {
    		if (maxn < sum[cin_lang[i]]) {
    			maxn = sum[cin_lang[i]];
    			op = i;
    		}
    		else if (maxn == sum[cin_lang[i]]) {
    			if (sum[cin_fan[i]] > sum[cin_fan[op]]) {
    				op = i;
    			}
    			else if (sum[cin_fan[i]] == sum[cin_fan[op]]) {
    				op = min(op,i);
    			}	
    		}
    	}
    	cout << op << endl;
    
    	return 0;
    }
    
  2. 七夕祭
    missing
    
  3. Running Median
    动态维护中位数
    我们可以用对顶堆在线处理
    也可以尝试离线处理
    /***********************************************************
      > File Name: LGP1168.cpp
      > Author: lan_m
      > QQ: 2867930696
      > Created Time: 2021/9/14 14:21:51
      > fighting for night
     *******************************************************/
    //比较麻烦的写法
    #include <bits/stdc++.h>
    
    using namespace std;
    
    int n;
    priority_queue<int> que1;
    priority_queue<int,vector<int>,greater<int> >que2;
    int main () {
    	scanf("%d",&n);
    	for (int i = 1;i <= n; i++) {
    		int a;scanf("%d",&a);
    		if (i == 1) { que2.push(a);cout << a << endl;continue;}
    		if (a < que2.top())que1.push(a);
    		else que2.push(a);
    		if ( i%2==1 ) {
    			if (que1.size() + 1 > que2.size()) {
    				int tp = que1.top();que1.pop();
    				que2.push(tp);
    			}
    			else if (que1.size() + 1 < que2.size()) {
    				int tp = que2.top();que2.pop();
    				que1.push(tp);
    			}
    		}
    		else if ( i%2==0 ) {
    			if (que1.size() > que2.size()) {
    				int tp = que1.top();que1.pop();
    				que2.push(tp);
    			}
    			else if (que1.size() < que2.size()) {
    				int tp = que2.top();que2.pop();
    				que1.push(tp);
    			}
    		}
    		if ( i%2 == 1 ) cout << que2.top() << endl;
    	}
    	return 0;
    }
    
  4. 求逆序对
    missing
    
  5. 奇数码问题
    missing
    

- 0x06倍增

知识 (香甜的知识)

  1. 倍增 (与二进制拆分)
  2. ST算法(RMQ)
    Nlog(N)的预处理,查询为O(1)

例题
Genius ACM
(ST算法) 可以解决RMQ(区间查询最值问题)

  1. 倍增思想

    /***********************************************************
      > File Name: genius(acm).cpp
      > Author: lan_m
      > QQ: 2867930696
      > Created Time: 2021/9/15 20:45:22
      > fighting for night
     *******************************************************/
    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define int long long
    const int N = 5e5+10;
    int n,m,Q;
    int a[N],b[N],c[N];
    
    void merge(int l,int mid,int r) {
    	int i = l,j = mid+1,k = l;
    	while (i <= mid && j <= r) {
    		if (c[i] <= c[j]) b[k++] = c[i++];
    		else b[k++] = c[j++];
    	}
    	while (i <= mid) b[k++] = c[i++];
    	while (j <= r) b[k++] = c[j++];
    }
    bool check(int s,int mid,int e) {
    	for (int i = mid+1;i <= e;i ++) c[i] = a[i];
    	sort(c+mid+1,c+e+1);
    	merge(s,mid,e);
    	int sum = 0;
    	for (int i = 1;i <= (e - s + 1)>>1 && i <= m;i ++) {
    		sum += (b[e-i+1] - b[s+i-1]) * (b[e-i+1] - b[s+i-1]);
    	}
    	if (sum <= Q) {
    		for (int i = s;i <= e;i ++) c[i] = b[i];
    		return true;
    	}
    	return false;
    }
    
    signed main () {
    	int T;scanf("%lld",&T);
    	while (T--) {
    		scanf("%lld%lld%lld",&n,&m,&Q);
    		for (int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
    		int p = 1,ans = 0;
    		int st = 1,ed = 1;
    		c[1] = a[1];
    		while (ed <= n) {
    			if (p == 0) {
    				p = 1;
    				ans ++;
    				st = ed + 1;
    				ed += 1;
    				c[st] = a[st];
    			}
    			else if (ed + p <= n && check(st,ed,ed+p)) {
    			    ed = ed + p;
    			    p <<= 1;
    			    if (ed == n)break;
    			}
    			else p>>=1;
    		}
    		if( ed == n ) ans ++;
    		printf("%lld\n",ans);
    	}
    	return 0;
    }
    

    - 贪心

    贪就对了

    暂不跟新~~~(

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值