算法【板子】

freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);

一、动态规划

1.背包DP

n件价值vi重量wi的物品,放进一个容量为m的背包

01背包

dp[i]表示不同重量下的最大价值

for(int i=0;i=w[i];j--){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    	}
	}
	cout< 
  

dp[i]表示不同价值下的最小重量

memset(dp,INF,sizeof(dp));
	dp[0]=0;
	for(int i=0;i=v[i];j--){
			dp[j]=min(dp[j],dp[j-v[i]]+w[i]);
		}
	}
	long long i,ans;
	for(i=0;i 
  

完全背包

for(int i=0;i 
  

多重背包

二进制优化

时间复杂度:O(nmlog(si))

for(int i=0;i>tv>>tw>>tm;
		int c=1;
		while(tm>c){
            w[cnt] = c*tw;
            v[cnt++] = c*tv;
            tm -= c;
            c<<=1;
    	}
    	w[cnt] = tm*tw;  //补充不足指数的差值
        v[cnt++] = tm*tv;
	}
	for(int i=0; i=w[i]; j--){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
	    }
	}

单调队列优化

时间复杂度:O(nm)

for(int i=1;i<=m;i++){
			cin>>w>>v>>num;
			for(int d=0;dnum&&head 
  

超大背包

void solve(){
	//将 n 二分,前半部分的所有可能取值全部存入 pair 中(数位01设置选择) 
    int n1 = n / 2;
    int num1 = 1 << n1;
    for(int i = 0; i < num1; i++) {
        long long sw = 0, sv = 0;
        for(int j = 0; j < n1; j++) {
            if((i >> j) & 1) {
                sw += w[j];
                sv += v[j];
            }
        }
        pi[i] = make_pair(sw, sv);
    }
    
    //单调排序,筛除 w[i]>=w[j]但v[i]<=v[j]的 pair 
    sort(pi, pi + num1);
    int m = 1;
    for(int i = 1; i < num1; i++) {
        if(pi[m-1].second < pi[i].second) {
            pi[m++] = pi[i];
        }
    }
    
    //处理另一半 n ,寻找满足 w 时 最大的 v 
    int n2 = n - n1;
    int num2 = 1 << n2;
    long long ans = 0;
    for(int i = 0; i < num2; i++) {
        long long sw = 0, sv = 0;
        for(int j = 0; j < n2; j++) {
            if((i >> j) & 1) {
                sw += w[n1 + j];
                sv += v[n1 + j];
            }
        }
        if(sw <= W) {
            long long tv = (lower_bound(pi, pi + m, make_pair(W - sw, INF)) - 1)->second;
            ans = max(ans, sv + tv);
        }
    }
    cout< 
  

区间DP

编辑距离(Levenshtein距离)

for (int i = 0; i <= a.size(); i++) dp[i][0] = i;
    for (int j = 0; j <= b.size(); j++) dp[0][j] = j;
	for(int i=1;i<=a.size();i++){
		for(int j=1;j<=b.size();j++){
			if (a[i-1]==b[j-1]) {
                dp[i][j] = dp[i - 1][j - 1];  // 不需要编辑
            } else {
                int insert1 = dp[i][j - 1] + 1;  // A 插入 B 的第j个字符
                int insert2 = dp[i - 1][j] + 1;  // B 插入 A 的第i个字符
                int replace = dp[i - 1][j - 1] + 1; // A 替换成 B 的第j个字符
                dp[i][j] = min(replace, min(insert1, insert2));
            }
		}
	}
	cout< 
  

石子合并

for(int len=1;len 
  

2.数位DP

获取x各位数字,分别存储在a[i]中

int solve(int x){
    int pos=0;
    while(x){
        a[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,-1,0,true);
}

深度优先搜索(DFS)

int dfs(int pos,int pre,int sta,bool limit){
    if(pos==-1) return 1;
    if(!limit && dp[pos][sta]!=-1) return dp[pos][sta];
    int up=limit ? a[pos] : 9;
    int tmp=0;
    for(int i=0;i<=up;i++)
    {
        if(pre==6 && i==2)continue;
        if(i==4) continue;//都是保证枚举合法性
        tmp+=dfs(pos-1,i,i==6,limit && i==a[pos]);
    }
    if(!limit) dp[pos][sta]=tmp;
    return tmp;
}

3.ST表

二维数组 st[i][j] 表示着从序列第 i 个数开始,包含自身往后数 2^(j - 1)次方数,之前的区间中的最大值(或最小值)

即:序列区间 [i, i + 2^(j - 1)] 中的最大值(或最小值)

int n,log[N],st[N][40],a[N];
void init(){
    log[0]=-1;
    for(int i=1;i<=n;i++){
        log[i]=((i&(i-1))==0)?log[i-1]+1:log[i-1];
        st[i][0]=a[i];
    }
    for(int j=1;j<=log[n];j++)
        for(int i=1;i+(1< 
  

查询:

int query(int l,int r){
    int k=log[r-l+1];
    return max(st[l][k],st[r-(1< 
  

二、搜索

1.回溯算法

1) 回溯的实现

递归

举例:针对N叉树的递归回溯方法

void backtrack(int t){
	if(t>n) output(x);   //叶子节点,输出结果,x是可行解
	else for(int i=0;i 
  

递推

举例:针对N叉树的递归回溯方法

void backtrack(){
	int t=1;
	while(t){
		if(exitsubnode(t)){   //若当前节点的存在子节点  
			for(int i=0;i 
  

2) 子集树和排列树

子集树

所给的问题是从n个元素的集合S中找出满足某种性质的子集,相应的解空间成为子集树。 ​ 如0-1背包问题,从所给重量、价值不同的物品中挑选几个物品放入背包,使得在满足背包不超重的情况下,背包内物品价值最大。它的解空间就是一个典型的子集树。

void backtrack(int t){
	if(t>n) output(x);
	else for(int i=0;i 
  

排列树

所给的问题是确定n个元素满足某种性质的排列,相应的解空间就是排列树。 ​ 如旅行售货员问题,一个售货员把几个城市旅行一遍,要求走的路程最小。它的解就是几个城市的排列,解空间就是排列树。

void backtrack(int t){
	if(t>n) output(x);
	else for(int i=t;i 
  

2.BFS(广度优先搜索)

/**

  • 广度优先搜索

  • @param Vs 起点

  • @param Vd 终点

    */

bool BFS(Node& Vs, Node& Vd){  
    queue Q;  
    Node Vn, Vw;  
    int i;  
   
    //初始状态将起点放进队列Q  
    Q.push(Vs);  
    hash(Vw) = true;//设置节点已经访问过了!  
   
    while (!Q.empty()){
        Vn = Q.front();   
        Q.pop();  
   
        while(Vw = Vn通过某规则能够到达的节点){
            if (Vw == Vd){//找到终点了!  
                //把路径记录,这里没给出解法  
                return true;//返回  
            }  
   
            if (isValid(Vw) && !visit[Vw]){  
                //Vw是一个合法的节点并且为白色节点  
                Q.push(Vw);//加入队列Q  
                hash(Vw) = true;//设置节点颜色  
            }  
        }  
    }  
    return false;//无解  
}

3.DFS

三、数据结构

STL函数

vector v;

sort(v.begin(),v.end(),cmp);	//排序
reverse(v.begin(),v.end());	//翻转
rotate(v.begin(),v.begin()+midnum,v.end())	//旋转
swap_ranges(v.begin(),v.begin()+midnum,v.end())	//区域交换
v.erase(unique(v.begin(),v.end()),v.end())	//去重

运算符重载

在STL类的使用中,有时会需要遍历,若包含的数据类型为自定义结构体,则需重载运算符

自定义结构体操作符:

struct node{
    int x,y;
    bool operator < (const node& o) const{
    	if(x==o.x) return y 
  

泛型:

template
struct node{
	T x,y;
	node(T x=0,y=0):x(x),y(y){}
}
node operator + (const node& A,const node& B){
	return node(A.x+B.x,A.y+B.y);
}

迭代器

  1. 正向迭代器:容器类名::iterator 迭代器名;

  2. 常量正向迭代器:容器类名::const_iterator 迭代器名;

  3. 反向迭代器:容器类名::reverse_iterator 迭代器名;

  4. 常量反向迭代器:容器类名::const_reverse_iterator 迭代器名;

1.Vector

//头文件
#include

//创建vector对象
vector vec;

//尾部插入对象
vec.push_back(a);

//使用下标访问结点
cout< 
  

2.Map

#include

map s;

s.size();

//访问
s['a'] = 1;
s.at('b')=2;
for(auto& x : s) cout< 
  

3.Set

multiset可遗留元素,即同一元素

set s;

s.insert(1);       //插入元素

s.begin()       //返回set容器第一个元素的迭代器
s.end()      //返回一个指向当前set末尾元素的下一位置的迭代器
s.rbegin()    //返回的值和end()相同
s.rend()     //返回的值和begin()相同

s.clear()     //删除set容器中的所有的元素
s.empty()    //判断set容器是否为空
s.size()      //返回当前set容器中的元素个数

s.max_size()   //返回set容器可能包含的元素最大个数

//排序后返回某值
for(int i = 1 ; i <= 5; ++i)
     {
         s.insert(i);
     }
pair::const_iterator,set::const_iterator> pr;
pr = s.equal_range(3);
cout<<"第一个大于等于 3 的数是 :"<<*pr.first<::iterator,bool>
if (s.insert(key).second){
	set::iterator p = s.insert(key).first;
	//则*p==key;
}
//若存在返回false,不存在返回true

*/

//查找小于等于key的数
set::iterator it;
it=--s.upper_bound(key);

4.Stack

1)stack类

//头文件
#include

//栈的声明
stack s;

//入栈
s.push('a');

//若非空,则出栈
while(!s.empty()){
	s.pop();
}

//返回栈中元素个数
s.size();

//返回栈顶元素
s.top();

//栈的清空
while(!s.empty()) s.pop();

2)卡特兰数

一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列?

设h(n)为卡特兰数的第n项,令h(0)=1,h(1)=1,卡特兰数满足递推式 :

$$
h(n)=h(n-1)*(4*n-2)/(n+1)
$$

$$
h(n+1)=h(n) * (4*n + 2) / (n + 2)
$$

递推关系的解为:

$$
h(n)=C(2n,n)/(n+1) (n=0,1,2,...)
$$

递推关系的另类解为:

$$
h(n)=C(2n,n) - C(2n,n-1) (n=0,1,2,...)
$$

3)单调栈

直方图最大矩形

1、新建一个空栈,将 A[1] 压入栈中,此时 A[1] 位于栈顶。

2、A[i] 与栈顶元素比较。如果A[i] 大,那么将其入栈。如果两者相等,跳过,继续下一个元素。

如果A[i]小于当前栈顶元素,说明已经找到第一个位于栈顶右边的比它小的值(此时这个较小的元素还未入栈),在它的左边(在栈内就是它脚下的元素)即为第一个左边比它小的值。此时需要这样做:

以栈顶元素为最小高度计算最大矩形面积,并更新现在的最大面积,宽度为左边界到右边界。

弹出栈顶元素。

重复第2步。

3.扫描完后,一般情况下会剩下一个单调递增的堆栈,那么一个一个出栈计算面积就可以了。左边依旧还是栈顶下面那个值,右边这个时候就不存在了,最后一个栈状态的栈顶即为右边界。

例:直方图最大矩形

int n,h[MAXN];
long long mxs(){
	stack s;
	int i=0;
	long long ans=0,tmp;
	while(i=h[s.top()]){
			s.push(i++);
		}
		else{
			
			int tp=s.top();
			s.pop();
			tmp=h[tp]*(s.empty() ? i : i - s.top() - 1);
			ans=max(ans,tmp);
		}
	}
	while(!s.empty()){
        int tp = s.top();
		s.pop();
        tmp=h[tp]*(s.empty() ? i : i - s.top() - 1);
        ans=max(ans, tmp);
    }
    return ans;
}

5.Queue

1)queue类

//头文件
#include

//声明队列
queue q;

//入队,将x元素放入队列末端
q,push(x);

//出队,弹出队列的第一个元素
q.pop();

//返回队列首尾元素
q.front();
q.back();

//返回队列中元素数量
q.size();

//queue里面的元素是pair类型
queue< pair > q;

2)优先队列 :priority_queue类

priority_queue模版类有三个模版参数:元素类型,容器类型,比较算子。

后两个可省略,默认容器为vector,默认算子为less,即非递增排列(出队时序列尾的元素出队)。

//升序队列
priority_queue < int,vector,greater > q;
//降序队列
priority_queue < int,vector,less >q;

基础方法:

top 访问队头元素
empty 队列是否为空
size 返回队列内元素个数
push 插入元素到队尾 (并排序)
emplace 原地构造一个元素并插入队列
pop 弹出队头元素
swap 交换内容

如:	priority_queue>q;
	 priority_queue< int,vector,operator >q;

自定义类算子写法:

T类中以n的大小单调递增排列

bool operator < (const T&t1,const T&t2){
	return t1.n 
  

3)单调队列

举例:滑动窗口

给定一个数列,从左至右输出每个长度为m的数列段内的最小数和最大数。

struct node
{
    long long int h,p;
}v[1010000]; //h表示值,p表示位置 可以理解为下标

获取最小值维护单调递增队列

void getmin(){
	int i,head=1,trail=0;
	  // 默认起始位置为1 因为插入是v[++trail]故初始化为0
	for(i=0;i=head&&h[i]<=v[trail].h) trail--;
		v[++trail].h=h[i],v[trail].p=i+1;
	}
	for(;i=head&&h[i]<=v[trail].h) trail--;
		v[++trail].h=h[i],v[end].p=i+1;
		while(v[head].p<=i-m+1) head++;
		mn[i-m+1]=v[head].h;
	}
}

获取最大值维护单调递减队列

void getmax(){
	int i,head=1,trail=0;
	for(i=0;i=head&&h[i]>=v[trail].h) trail--;
		v[++trail].h=h[i],v[trail].p=i+1;
	}
	for(;i=head&&h[i]>=v[trail].h) trail--;
		v[++trail].h=h[i],v[trail].p=i+1;
		while(v[head].p<=i-m+1) head++;
		mx[i-m+1]=v[head].h;
	}
}

4)deque

deque t;

t.size();//返回容器中元素的个数
t.empty();//判断容器是否为空
t.resize(num);
deque.resize(num, elem);

t.push_back(elem);//在容器尾部添加一个数据
t.push_front(elem);//在容器头部插入一个数据
t.pop_back();//删除容器最后一个数据
t.pop_front();//删除容器第一个数据

t.at(idx);//返回索引idx所指的数据,如果idx越界,抛出out_of_range。
t.front();//返回第一个数据
t.back();//返回最后一个数据

t.insert(pos,elem);//在pos位置插入一个elem元素,返回新数据的位置。
t.insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。

t.clear();//移除容器的所有数据
t.erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
t.erase(pos);//删除pos位置的数据,返回下一个数据的位置。

6.List

#include 

listlst1;			//创建空list
list lst2(5);		//创建含有5个元素的list
listlst3(3,2);		//创建含有3个元素的list
listlst4(lst2);	//使用lst2初始化lst4
listlst5(lst2.begin(),lst2.end());		//同lst4

l1.assign(n,val); 给list赋值
l1.assign(l2.begin(),l2.end());
l1.back() 返回最后一个元素 
l1.begin() 返回指向第一个元素的迭代器 
l1.clear() 删除所有元素 
l1.empty() 如果list是空的则返回true 
l1.end() 返回末尾的迭代器 
l1.erase() 删除一个元素 
l1.front() 返回第一个元素 

l1.insert() 插入一个元素到list中
l1.insert(l1.begin(),100); 在l1的开始位置插入100。
l1.insert(l1.begin(),2,200); 在l1的开始位置插入2个100。
l1.insert(l1.begin(),l2.begin(),l2.end());

l1.max_size() 返回list能容纳的最大元素数量 
l1.merge(l2,greater()) 合并两个list,l2将清空
l1.pop_back() 删除最后一个元素 
l1.pop_front() 删除第一个元素 
l1.push_back(val) 在list的末尾添加一个元素 
l1.push_front(val) 在list的头部添加一个元素 
l1.rbegin() 返回指向第一个元素的逆向迭代器 
l1.remove() 从list删除元素
l1.erase(it) 根据迭代器删除指定位置
l1.remove_if() 按指定条件删除元素 
l1.resize(n) 改变list的大小,超出n的元素将被删除 
l1.reverse() 把list的元素倒转 
l1.size() 返回list中的元素个数 
l1.sort() 给list排序 
l1.splice() 合并两个list 
l1.swap(l2) 交换两个list 
swap(l1,l2) 
l1.unique() 删除list中重复的元素

list::iterator i=l1.begin();

advance(i,n) 将i向后挪n位
元素被插入到 pos 所指向的元素之前
l1.splice(const_iterator pos,list& other)
l1.splice(const_iterator pos,list& other,const_iterator it) 
	将other的第it元素转移到*this的第pos位置,操作后容器 other 变为空
l1.splice(const_iterator pos,list& other,const_iterator first	,const_iterator second)

7.树

树的重心:

定义:树上一点,去掉后最大子树可以取得最小值的点。去掉该点后最大子树大小不超过n/2

1)二叉树

结构体写法

class TreeNode{
private:
	int val;
	TreeNode *left;
	TreeNode *right;
public:
	TreeNode(int x) : val(x), left(NULL), right(NULL) {}
}

遍历

vector orderTraversal(TreeNode* root) {
    vector res;
    order(root,res);
    return res;
}

二叉树的先序遍历

void preorder(TreeNode* root, vector& res) {
    if(!root) return ;
    res.push_back(root->val);    
    preorder(root->left,res);
    preorder(root->right,res);
}

二叉树的中序遍历

void inorder(TreeNode* root, vector& res) {
    if(!root) return ;
    inorder(root->left,res);
    res.push_back(root->val);
    inorder(root->right,res);
}

二叉树的后序遍历

void proorder(TreeNode* root, vector& res) {
    if(!root) return ;
    proorder(root->left,res);
    porder(root->right,res);
    res.push_back(root->val);
}

深度

最大深度

int maxDepth(TreeNode* root) {
    if(!root) return 0;
    else if(!root->left) return maxDepth(root->right)+1;
    else if(!root->right) return maxDepth(root->left)+1;
    else return max(maxDepth(root->left),maxDepth(root->right))+1;
}

最小深度

int maxDepth(TreeNode* root) {
    if(!root) return 0;
    else if(!root->left) return maxDepth(root->right)+1;
    else if(!root->right) return maxDepth(root->left)+1;
    else return min(maxDepth(root->left),maxDepth(root->right))+1;
}

数组写法

深度与宽度

int n,t[100];
int deep[100];
int v[10000000],d,w[100];

for(int i=0;i>x>>y;
			if(v[t[x]*2]){
				t[y]=t[x]*2+1;
				v[t[y]]=1;	
			}
			else{
				t[y]=t[x]*2;
				v[t[y]]=1;
			}
			deep[y]=deep[x]+1;
			d=max(d,deep[y]);
			w[deep[y]]++;
		}

2)线段树

调用:build(1,n,1)

#define lson l,mid,i<<1
#define rson mid+1,r,i<<1|1
int sum[N<<2];
#define pushup(i) sum[i]=sum[i<<1]+sum[i<<1|1]
void build(int l,int r,int i){
	if(l==r){
		cin>>sum[i];
		re
	}
	int mid=(l+r)>>1;
	build(lson);
	build(rson);
	pushup(i);
}

传递lazy标记

void pushdown(int o,int k)
{
    sum[o*2]=(sum[o*2]*mul[o]+add[o]*(k-(k>>1)))%p;
    sum[o*2+1]=(sum[o*2+1]*mul[o]+add[o]*(k>>1))%p;
    //更新的顺序:先乘法标记的更新,再加上累加标记的更新。并且累加标记的更新还要注意累加的次数,一个是(k-(k>>1)),另一个是(k>>1)。
    mul[o*2]=mul[o*2]*mul[o]%p
    mul[o*2+1]=mul[o*2+1]*mul[o]%p;
    //乘法标记也要更新的。千万别忘记
    add[o*2]=(add[o*2]*mul[o]+add[o])%p;
    add[o*2+1]=(add[o*2+1]*mul[o]+add[o])%p;
    //加法标记更新的时候要先考虑乘法标记的更新。
    mul[o]=1;
    add[o]=0;
    //标记清空。
}

维护

void add(int k, int v, int l, int r, int i){//i是线段树数组中的下标,k,l,r是元素数组中的下标
	if (l == r){
		sum[i] += v;
		return;
	}
	int mid = (l + r) >> 1;
	if (k <= mid) add(k, v, lson);
	else add(k, v, rson);
	pushup(i);
}

区间自加

void jia(int l,int r,int o)//三个参数,l和r代表当前在查找的区间的两个端点,o代表当前查找的区间的根结点
{
    if(x<=l&&r<=y)//如果区间完全被覆盖
    {
        add[o]=(add[o]+v)%p;//对lazy标记直接累加v,并且模p(一步一模可以避免最后long long溢出的问题)
        sum[o]=(sum[o]+v*(r-l+1))%p;//对于该点的累加和也要相应加上每一个单点的改变量
        return;//lazy标记的初衷,直接退出
    }
    pushdown(o,r-l+1);//传递lazy标记
    int mid=(l+r)>>1;//获取区间中点
    if(x<=mid)
    jia(le);//如果x在中点左边,就说明有必要搜索左子树
    if(y>mid)
    jia(re);//如果y在中点右边,就说明有必要搜索右子树
    sum[o]=(sum[o*2]+sum[o*2+1])%p;
//是时候更新根结点的累加和了。

区间乘法

void cheng(int l,int r,int o)
{
    if(x<=l&&r<=y)
    //如果区间完全包含
    {
        add[o]=(add[o]*v)%p;
        mul[o]=(mul[o]*v)%p;
        sum[o]=(sum[o]*v)%p;
            //累加和、乘法标记、加法标记全部更新。
        return;//更新完就直接返回,不用多说话。
    }
    pushdown(o,r-l+1);//传递标记
    int mid=(l+r)>>1;
    if(x<=mid)
    cheng(le);
    if(y>mid)
    cheng(re);
    sum[o]=(sum[o*2]+sum[o*2+1])%p;
    //这一段同上文加法。
}

查询

int query(int x, int y, int l, int r, int i){
	if (x <= l && r <= y){
		return sum[i];
	}
	int mid = (l + r) >> 1;
	int ans = 0;
	if (x <= mid) ans += query(x, y, lson);
	if (y > mid) ans += query(x, y, rson);
	return ans;
}

3)树状数组

#define lowbit(x) -x&x

单点修改+区间查询

void update(int x,int y){
    for(int i=x;i<=n;i+=lowbit(i))
        a[i] += y;
}

long long getsum(int x){
    long long sum = 0;
    for(int i=x;i;i-=lowbit(i))
        sum += a[i];
    return sum;
}

//getsum(r)-getsum(l-1)

区间修改

void add(int p, int x){
    while(p <= n) sum[p] += x, p += p & -p;
}
void range_add(int l, int r, int x){
    add(l, x), add(r + 1, -x);
}

区间修改+区间查询

void add(int u,int x){
	for(int i=u;i<=n;i+=lowbit(i)){
		b[i]+=x;
		c[i]+=(ll)u*x;
	}
}

ll query(int x){
	ll res=0;
	for(int i=x;i;i-=lowbit(i))
		res+=(x+1)*b[i]-c[i];
	return res;
 } 

8.堆

快速建立

快速建立的前提条件是已知所有数据

for(int i=1;i<=n;i++) scanf("%d",&h[i]);
cnt=n;
for(int i=n/2;i;i--) down(i);

简单堆

实现插入、查询最值、弹出最值

#define MAXN 1e6+50
int h[MAXN];
int sz=0;
void up(int p){
	while(p>>1&&h[p]>1]){
		swap(h[p],h[p>>1]);
		p>>=1;
	}
}
void down(int p){
	int t=p;
	if(p<<1 <= sz && h[p<<1] < h[t]) t=p<<1;
	if((p<<1|1) <= sz && h[p<<1|1] < h[t]) t=p<<1|1;
	if(t!=p){
		swap(h[p],h[t]);
		down(t);
	}
}
void insert(int x){
	h[++sz] = x;
	up(sz);
}
int top(){
	return h[1];
}
void pop(){
	h[1] = h[sz--];
	down(1);
}

四、字符串基础

1.基础函数

输入:

while(getline(cin,s),s!="0")

字符串常见函数

//字符串的翻转
/strrev(s);	//cstring
reverse(s.begin(),s.end());	//algorithm,无返回值
string(s.rbegin(),s.rend());

//删除子串
s.erase(pos);
s.erase(pos,end);

s.insert(pos,str);	//插入子串
s.find(str,pos);	//查找子串
	//找不到返回string::npos
s.substr(pos,lenth);	//截取子串
s.replace(pos,lenth,str);	//替换z

类型的转换

//字符串转换为char*
s.c_str()
//字符串转整形
int atoi(const char *nptr);
int stoi(string str);
//字符串转浮点数
double stof(string str);
//整形、浮点数转字符串
string str = to_string(v)

字符串的比较

#include 
using namespace std;
int main(){
	string str1="hello";
	cout<
#include 
using namespace std;
int main(){
	char* str1="hello";
	char* str2="hell";
	char *str3="helloo";
    char *str4="hello";
    
    //原型extern int strcmp(const char *s1,const char *s2);
	cout< 
  

字符串截取

str.substr(i,j);	//从i一直截取j个字符
str.substr(i);	//从i一直截取到末尾

                 //char数组的截取
                 
//从str2开始,截取num个字符到str1中
strncpy(str1,str2,num);

2.hash算法

不取模

#define BASE 37
#define PRIME 233317

long long gethash(char *s){
	long long n=strlen(s);
	long long hash=0;
	for(long long i=0;i 
  

取模

#define BASE 37
#define PRIME 233317

long long gethash(char *s,int mod){
	long long n=strlen(s);
	long long hash=0;
	for(long long i=0;i 
  

3.KMP算法

经典字符串查找算法

确定next数组

char p[MAXN],s[MAXN];
int nxt[MAXN],slen,plen;
void getnext(){
	slen=strlen(s),plen=strlen(p);
    nxt[0] = -1;
    int i = 0, j = -1;
    while (i < plen){
        if (j == -1 || p[i] == p[j]){
            nxt[++i] = ++j;
        }
        else{
            j=nxt[j];
        }
    }
}

主函数

getnext();
	int i=0,j=0,ans=0;
	while(i 
  

4.Manacher算法

求回文子串

#include
using namespace std;
const int mx = 10000;
 
char ss[mx + 5], s[(mx << 1) + 5]; /// ss为源串,s为处理后的字符串
int len[(mx << 1) + 5];
 
void debug()
{
	int i;
	for (i = 1; s[i]; ++i) printf("%c ", s[i]);
	puts("");
	for (i = 1; s[i]; ++i) printf("%d ", len[i]);
	puts("");
}
void init(){
	memset(s, 0, sizeof(s));
		s[0] = '#';
		for (i = 0; ss[i]; ++i) s[(i << 1) + 1] = '#', s[(i << 1) + 2] = ss[i];
		s[(i << 1) + 1] = '#';
		memset(len, 0, sizeof(len));
}
int main()
{
	int right, mid, i, maxlen;
	while (gets(ss))
	{
		init();
		
		maxlen = right = mid = 0;
		for (i = 1; s[i]; ++i)
		{
			len[i] = (i < right ? min(len[(mid << 1) - i], right - i) : 1);
			/* 取min的原因:记点i关于mid的对称点为i',
			若以i'为中心的回文串范围超过了以mid为中心的回文串的范围
			(此时有i + len[(mid << 1) - i] >= right,注意len是包括中心的半长度)
			则len[i]应取right - i(总不能超过边界吧) */
			while (s[i + len[i]] == s[i - len[i]]) ++len[i];
			maxlen = max(maxlen, len[i]);
			if (right < i + len[i]) mid = i, right = i + len[i];
		}
		printf("%d\n", maxlen - 1);
		debug();
	}
	return 0;
}

// 判断源串中的某一子串 ss[l...r] 是否为回文串

inline bool Query(int l, int r) 
{
    return len[l + r + 2] - 1 >= r - l + 1;
}

5.LCS(最长公共子序列)

string s,t;
int ls,lt,dp[MAXN];
void init(){
	int x;
	ls=s.size(),lt=t.size();
	memset(dp, 0, sizeof(dp));
	for(int i=0;idp[j]) dp[j]=dp[j-1];
			x=tmp;
		}
	}
}

主函数

init();
cout< 
  

6.LIS(最长上升子序列)

动态规划

时间复杂度:O(n^2)

int a[MAXN],dp[MAXN],n,ans;
void lis(){
	ans=0;
	for(int i = 0; i 
  

贪心+二分

时间复杂度:O(nlogn)

#define INF 0x3f3f3f3f
int arr[N],g[N],n;
int lis(){
//	memset(g,INF,sizeof(g));
	fill(g, g+n, INF);
    int ans=-1;
    for(int i=0; i 
  

7.后缀数组

sa[l] = 排序第 l 的后缀的开始位置

rank[i] = 后缀 suf(i) 的排名

故:rank[sa[l]] = l; sa[rank[i]] = i;

int ht[MAXN];	//维护rk相邻的两个后缀的lcp(最长公共前缀)
int sa[MAXN];	//记录排名为i的后缀初始位置
int rk[MAXN],rk2[MAXN];	//rk指下标为i的后缀的排名

bool cmp(int i,int j){
	if(rk[i]!=rk[j])
		return rk[i]0) h--;	//除去开头的位置,至少h-1的位置相同
		for(;j+h<=n&&i+h<=n;h++) if(str[j+h]!=str[i+h]) break;
		ht[rk[i]] 
	}
}

两后缀的最长公共前缀

$$
LCP(i, j) = min(height[k] ,min(i, j) < k <= max(i, j))
$$

8.求前后字典序排列

给出某一序列,求按字典序排列的前一序列

bool cmp(int x,int y){
	return x>y;
}
string getpre(string str){
	int n=str.length();
	bool judge=false;	//用于判断序列是否存在 ,也可返回
	string s=str; 
	int i,j;
	for(i=n-2;i>=0;i--){
		if(s[i]>s[i+1]){
			judge=true;
			break;
		}
	}
	for(j=n-1;j>i;j--) if(s[j] 
  

求按字典序排列的后一序列

string getpro(string str){
	int n=str.length();
	bool judge=false;	//用于判断序列是否存在 ,也可返回
	string s=str; 
	int i,j;
	for(i=n-2;i>=0;i--){
		if(s[i]i;j--) if(s[j]>s[i]) break;
	char tmp=s[i];
	s[i]=s[j],s[j]=tmp;
	sort(s.begin()+i+1,s.end());
	return s;
}

五、图论

建图

1)链式前向星

struct edge{
    int to, w, next;
}e[maxn];
int head[MAXN],cnt;
void init(){
	memset(head,-1,sizeof(head));
    cnt=0;
}
void addedge(int u, int v, int w){
    e[cnt].to = v;
    e[cnt].w = w;
    e[cnt].next = head[u];
    head[u] = cnt++;//更新以u为起点上一条边的编号
}

遍历函数

for(int i = 1; i <= n; i++){//n个起点
    		cout << i << endl;
   			for(int j = head[i]; j != -1; j = edge[j].next){//遍历以i为起点的边
        		cout << edge[j].to << " " << edge[j].w << endl;
    		}
   			cout << endl;
		}

1.最短路

1)Floyd算法

多源最短路

for(int k=1;k<=n;k++){
		for(int x=1;x<=n;x++){
			for(int y=1;y<=n;y++){
				f[x][y]=min(f[x][y],f[x][k]+f[k][y]);
			}
		}
	}

传递闭包

Wallshall算法

for(int k=0;k 
  

void wallshell() {
    for (int t = 0;t < n;t++) {
        for (int i = 0;i < n;i++) {
            for (int j = 0;j < n;j++) {
                m[i][j] = m[i][j] || (m[i][t] && m[t][j]);
            }
        }
    }
}

2)Dijkstra算法

单源最短路,O(V*E)

#define INF 0x3f3f3f3f
int vis[N],dis[N];
int m[N][N],n,t;//n个点,t条边;
void init(){
	memset(m,Inf,sizeof(m));
	for(int i=0;i 
  

堆优化,O(logV*E)

 
  

3)SPFA算法

可用于带有负权边的计算,若死循环则途中存在闭环。O(nm)

注:此算法采用链式前向星建图

int mark[MAXN];	//记录每个点如队列的次数
bool SPFA(int s){
    for(int i = 0;i < n;i ++){
        mark[i] = 0;dis[i] = INF;vis[i] = 0;
    }
    queue q;
    q.push(s);	//我们只需要判断负环,随便找一个起点就好
    dis[s] = 0;
    vis[s] = 1;	//入队列
    mark[s] ++;
    while(!q.empty()){
        int  u = q.front();
        q.pop();
        vis[u] = 0;	//出队列
        for(int i = head[u];i != -1;i = e[i].next){
            int v = e[i].to;
            if(dis[v] > dis[u] + e[i].w){
                dis[v] = dis[u] + e[i].w;
                if(!vis[v]){	//不在队列中的时候出队
                    q.push(v);mark[v] ++;vis[v] = 1;
                }
                //某点进入队列的次数超过N次则存在负环
                if(mark[v] >= n) return false;
            }
        }
    }
    return true;
}

3.DFS序

void dfs(int x,int pre,int d){//L,R表示一个子树的范围
    L[x]=++tot;
    dep[x]=d;
    for(int i=0;i 
  

4.拓扑排序

int in[N];
vector out[MAXN];
int topology(){
	queue q;
	//vector ans;
	for(int i=0;i 
  

//不方便使用vector时

bool topotopology(){
    for(i=0;i 
  

5.并查集

int root[N];
int n,m,q;
void init(){
	for(int i=1;i<=n;i++){
		root[i]=i;
	}
}
int find(int x){
	return x==root[x] ? x : root[x]=find(root[x]); 
}
void merge(int x,int y){
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy){
		root[fy]=fx;
	}
}

6.欧拉路与欧拉回路

判断一个图是否存在欧拉路

1.无向图:连通图中,若奇数数度节点个数为0或2,则存在欧拉路。

2.有向图:连通图中,若出度比入度大1的节点和入度比出度大1的节点都为1或0,则存在欧拉路。

判断一个图是否存在欧拉回路

1.无向图:连通图中,若每个顶点的度数都是偶数,则存在欧拉回路。

2.有向图:连通图中,若每个顶点的入度都等于出度,则存在欧拉回路。

Fleury算法

void dfs( int u ) {
    sta.push(u);
    for( register int i = head[u]; i; i = line[i].nxt ) {
        if( vis[i] ) continue;
        vis[i] = vis[ i ^ 1 ] = true;
        dfs( line[i].to );
        break;
    }
}

void fleury( int st ) {
    sta.push(st);
    while(!sta.empty() ) {
        int flag = 0, u = sta.top(); sta.pop();
        for( register int i = head[u]; i; i = line[i].nxt) {
            if( vis[i] ) continue;
            flag = 1; break;
        }
        if( !flag ) printf( "%d\n", u );
        else        dfs(u);
    }
}

7.二分图

最大匹配数:最大匹配的匹配边的数目

最小顶点覆盖数:选取最少的点,使任意一条边至少有一个端点被选择,满足该集合的点的数目

最大独立集数:选取最多的点,使任意所选两点均不相连,满足该集合的点的数目

最小边覆盖数:选取最少条路径,使得每个顶点属于至少是其中某条边的端点,满足该集合的边的数目。

定理1:二分图中,最大匹配数 = 最小点覆盖数 定理2:最大独立数+最小点覆盖数=顶点数 定理3:对于不存在孤立点的图,最小边覆盖数+最大匹配数= 顶点数

匈牙利算法

求最大匹配数

int girl[MAXN];
bool line[MAXN][MAXN],used[MAXN];
int n,m;

主函数:

bool find(int x){
	int ij;
	for (j=1;j<=m;j++){    //扫描每个妹子
		if (line[x][j]==true && used[j]==false){      
		//如果有暧昧并且还没有标记过(这里标记的意思是这次查找曾试图改变过该妹子的归属问题,但是没有成功,所以就不用瞎费工夫了)
			used[j]=true;
			if (girl[j]==0 || find(girl[j])) { 
				//名花无主或者能腾出个位置来,这里使用递归
				girl[j]=x;
				return true;
			}
		}
	}
	return false;
}

主程序:

for (i=1;i<=n;i++){
	memset(used,false,sizeof(used));
	if find(i) all+=1;
}

KM算法

求二分图最大权完美匹配

#include 
#include 
#include 
 
using namespace std;
#define MAXN 305;
#define INF 0x3f3f3f3f
 
int love[MAXN][MAXN];   // 记录每个妹子和每个男生的好感度
int ex_girl[MAXN];      // 每个妹子的期望值
int ex_boy[MAXN];       // 每个男生的期望值
bool vis_girl[MAXN];    // 记录每一轮匹配匹配过的女生
bool vis_boy[MAXN];     // 记录每一轮匹配匹配过的男生
int match[MAXN];        // 记录每个男生匹配到的妹子 如果没有则为-1
int slack[MAXN];        // 记录每个汉子如果能被妹子倾心最少还需要多少期望值
 
int N;
 
 
bool dfs(int girl)
{
    vis_girl[girl] = true;
 
    for (int boy = 0; boy < N; ++boy) {
 
        if (vis_boy[boy]) continue; // 每一轮匹配 每个男生只尝试一次
 
        int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy];
 
        if (gap == 0) {  // 如果符合要求
            vis_boy[boy] = true;
            if (match[boy] == -1 || dfs( match[boy] )) {    // 找到一个没有匹配的男生 或者该男生的妹子可以找到其他人
                match[boy] = girl;
                return true;
            }
        } else {
            slack[boy] = min(slack[boy], gap);  // slack 可以理解为该男生要得到女生的倾心 还需多少期望值 取最小值 备胎的样子【捂脸
        }
    }
 
    return false;
}
 
int KM()
{
    memset(match, -1, sizeof match);    // 初始每个男生都没有匹配的女生
    memset(ex_boy, 0, sizeof ex_boy);   // 初始每个男生的期望值为0
 
    // 每个女生的初始期望值是与她相连的男生最大的好感度
    for (int i = 0; i < N; ++i) {
        ex_girl[i] = love[i][0];
        for (int j = 1; j < N; ++j) {
            ex_girl[i] = max(ex_girl[i], love[i][j]);
        }
    }
 
    // 尝试为每一个女生解决归宿问题
    for (int i = 0; i < N; ++i) {
 
        fill(slack, slack + N, INF);    // 因为要取最小值 初始化为无穷大
 
        while (1) {
            // 为每个女生解决归宿问题的方法是 :如果找不到就降低期望值,直到找到为止
 
            // 记录每轮匹配中男生女生是否被尝试匹配过
            memset(vis_girl, false, sizeof vis_girl);
            memset(vis_boy, false, sizeof vis_boy);
 
            if (dfs(i)) break;  // 找到归宿 退出
 
            // 如果不能找到 就降低期望值
            // 最小可降低的期望值
            int d = INF;
            for (int j = 0; j < N; ++j)
                if (!vis_boy[j]) d = min(d, slack[j]);
 
            for (int j = 0; j < N; ++j) {
                // 所有访问过的女生降低期望值
                if (vis_girl[j]) ex_girl[j] -= d;
 
                // 所有访问过的男生增加期望值
                if (vis_boy[j]) ex_boy[j] += d;
                // 没有访问过的boy 因为girl们的期望值降低,距离得到女生倾心又进了一步!
                else slack[j] -= d;
            }
        }
    }
 
    // 匹配完成 求出所有配对的好感度的和
    int res = 0;
    for (int i = 0; i < N; ++i)
        res += love[ match[i] ][i];
 
    return res;
}
 
int main()
{
    while (~scanf("%d", &N)) {
 
        for (int i = 0; i < N; ++i)
            for (int j = 0; j < N; ++j)
                scanf("%d", &love[i][j]);
 
        printf("%d\n", KM());
    }
    return 0;
}        

8.Tarjan算法

求强连通分量

int dfn[MAXN],low[MAXN],scc[MAXN],stk[MAXN];  //stk是栈的模拟
int index,sccnum,top;
void tarjan(int root){
	if(dfn[root]) return ;
	dfn[root]=low[root]=++index;
	stk[++top]=root;
	for(int i=head[root];i!=-1;i=e[i].next){
		int v=e[i].to;
		if(!dfn[v]){  //如果结点v未访问过
			tarjan(v);
			low[root]=min(low[root],low[v]);
		}
		else if(!scc[v]){  //如果还在栈内
			low[root]=min(low[root],dfn[v]);
		}
	}
	if(low[root]==dfn[root]){  //后代不能找到更浅的点
		sccnum++;
		while(true){
			int x=stk[top--];
			scc[x]=sccnum;
			if(x==root) break;
		}
	}
}

int dfn[MAXN],low[MAXN],scc[MAXN],stk[MAXN];  //stk是栈的模拟
int index,sccnum,top;
int num[MAXN];  //统计每个强连通分量有几个结点
void tarjan(int root){
	if(dfn[root]) return ;
	dfn[root]=low[root]=++index;
	stk[++top]=root;
	for(int i=head[root];i!=-1;i=e[i].next){
		int v=e[i].to;
		if(!dfn[v]){  //如果结点v未访问过
			tarjan(v);
			low[root]=min(low[root],low[v]);
		}
		else if(!scc[v]){  //如果还在栈内
			low[root]=min(low[root],dfn[v]);
		}
	}
	if(low[root]==dfn[root]){  //后代不能找到更浅的点
		sccnum++;
		while(true){
			int x=stk[top--];
			scc[x]=sccnum;
			num[sccnum]++;
			if(x==root) break;
		}
	}
}

9.LCA(最近公共祖先)

初始化

void init(){
    cnt = 1;
    for(int i=0;i 
  

需要并查集,主函数逆向建树

int root[N],vis[N],to[N],cnt;
int find(int x){
	if(root[x]==x) return x;
	else return root[x]=find(root[x]);
}
int dfs(){
	int a=1,b=1;
	root[1]=find(1);
	int i;
	for(i=1;i!=root[1];i=to[i]){
		vis[i]=a;
		a++;
	}
	vis[root[1]]=a;
	for(i=2;1;i=to[i]){
		if(vis[i]){
			a=vis[i];
			break;
		}
		b++;
	}
	if(a==b) return 0;
	else if(a 
  

10.最小生成树

点数n,边数m Prim算法O(n^2) Kruscal算法O(mlogm) 稠密图推荐使用Prim算法 稀疏图推荐使用Kruskal算法

Prim算法

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

  1. 图的所有顶点集合为VV;初始令集合u={s},v=V−uu={s},v=V−u;

  2. 在两个集合u,vu,v能够组成的边中,选择一条代价最小的边(u0,v0)(u0,v0),加入到最小生成树中,并把v0v0并入到集合u中。

  3. 重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。

由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息,:

Kruskal算法

此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。

  1. 把图中的所有边按代价从小到大排序;

  2. 把图中的n个顶点看成独立的n棵树组成的森林;

  3. 按权值从小到大选择边,所选的边连接的两个顶点ui,viui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。

  4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。

六、数论

a 被 b 整除,a是大的那个数(一般来讲),记为:b|a

1.快速幂

思想:

求pow(a,b)时,将b化为二进制考虑,从而大幅减少时间复杂度。快速幂的时间复杂的为O(log2n)一般的幂乘运算为O(n)。

不取模:

//不取模,a的b次幂
long long dpow(long long a,long long b){
    long long ans=1;
    while(b){
        if(b&1) ans=ans*a;
        a=a*a;
        b>>=1;
    }
    return ans;
}

求后n位:(取模)

取模运算可求得运算结果后n位:

//取模
long long dpow(long long a,long long b,long long mod){
    long long ans=1;
    while(b){
        if(b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans%mod;
}

求前n位:

求对数运算可求得运算结果前n位:

double t=b*log10(1.0*a);//求以10位底数的指数 
double r=t-(int)t;//求指数的小数部分 
int result=(int)(pow(10,n+r));//求前n位

2.快速乘

inline long long qmul(long long a, long long b, long long mod){
	long long res = 0;
	while( b > 0 ){
		if( b&1 ) res = (res + a) % mod;
		a = ( a + a ) % mod;
		b >>= 1;
	}
	return res;
}

使用long double优化:

inline long long qmul(long long x, long long y, long long mod){
    return ( x * y - (long long) ( (long double) x / mod*y )*mod + mod ) % mod;     
}

3.素数

欧拉筛

#define MAXN 10000005
bool isprime[MAXN];
void prime(){
    memset(isprime,true,sizeof(isprime));//初始化都是素数
    isprime[1]=isprime[0]=false;
    for (long long  i=2; i<=MAXN; i++) {
        if (isprime[i]) {
        	//如果i是素数,让i的所有倍数都不是素数
            for(long long int j = i*i; j <= MAXN; j += i){
                isprime[j] = false;
            }
        }
	}
}

欧拉函数

long long eular[MAXN];
void initeular(){
    for(int i=2;i 
  

$$
φ(n)=n*(1-1/p_1)*(1-1/p_2)*……*(1-1/p_n)
$$

long long eular(long long n){
    long long ans = n;
    for(int i=2; i*i <= n; ++i){
        if(n%i == 0){
            ans = ans/i*(i-1);
            while(n%i == 0)
                n/=i;
        }
    }
    if(n > 1) ans = ans/n*(n-1);
    return ans;
}

米勒-拉宾素数检验

需调用快速幂和快速乘

bool Miller_Rabin(ll num){
    if(num == 2) return true;  //2为质数
    if(!(num&1)||num<2) return false;//筛掉偶数和小于2的数
    ll s = 0,t = num-1;  //流程中的s和t,2的s次方*t = num-1
    while(!(t&1)){         //当t为偶数的时候,可以继续分解
        s++;
        t>>=1;
    }
    for (int i = 1; i <= 10; i++) {    //进行十次测试即可得到比较准确的判断
        ll a = rand()%(num-1)+1;  //流程中的随机整数a,在1到num-1之间
        ll x = dpow(a,t,num);        //x为二次探测的解
        for(int j = 1;j <= s;j++){      //x平方s次可以得到a的num-1次方
            ll test = qmul(x,x,num); //test为x平方后对num取模
            if(test == 1 && x != 1 && x != num-1) return false;   //如果平方取模结果为1,但是作为解的x不是1或者num-1,说明num不是质数,返回
            x = test;
        }
        if(x != 1) return false;        //费马小定理作最后检测,a的num-1次方对num取模不等于1,一定不是质数
    }
    return true;                          //腥风血雨后仍坚持到最后,基本就是真正的质数了
}

4.分数规划

0/1分数规划

给定一系列整数a1,a2,……,an以及b1,b2,……,bn

求解一组xi(1 <= i <= n,xi = 0或1)使得下式最大化

$$
∑[1…n](ai*xi)/∑[1…n](bi*xi)
$$

解法:

在值域[L,R]内二分mid,判断是否存在一组解xi使得

$$
∑[1…n](ai*xi)/∑[1…n](bi*xi)>=mid
$$

若是则说明二分值 mid 比能求得的最大值小,令L = mid;

否则说明二分值 mid 大于能求得的最大值,令R = mid;

直到二分值达到精度要求。

判断方法:

$$
若ai-mid*bi<0则令xi=0,否则xi=1
$$

int a[maxn],b[maxn];
double tt[maxn];
d ans;
int check(double x){
	double sum=0;
	for(int i=1;i<=n;++i)
	tt[i]=a[i]-x*b[i];//每组ai-mid*bi
	sort(tt+1,tt+1+n);
	for(int i=n;i>=k+1;--i) sum+=tt[i];//检查前n-k组的和
	return sum>=0;
}

主函数:

double L=0,R=1,mid;
		while(R-L>1e-4)//注意精度要求
		{
			mid=(L+R)/2;
			if(check(mid))L=mid;
			else R=mid;
		}

5.GCD(最大公约数)

辗转相除法(欧几里得算法)

int gcd(int a,int b){
	return b? gcd(b,a%b):a;
}

LCM(最小公倍数)

long long lcm=a/gcd(a,b)*b;

裴蜀定理

x、y为任意的整数

$$
若gcd(a,b)=d,则ax+by=k*d,且一定存在x、y使k=1
$$

即:

$$
若ax+by=d有解,则d一定是gcd(a,b)的倍数
$$

$$
推论:方程ax+yb=1,只有当整数a,b互质时,方程才有整数解。
$$

扩展欧几里得

扩展欧几里得算法是用在已知a,b求解一组整数解x,y,使其满足裴蜀等式:

$$
ax+by=gcd(a,b)=d
$$

int exgcd(int a,int b,long long &x,long long &y){
    if(b==0) return x=1,y=0,a;
    	//d的值实际上就是gcd(a,b),如果不需要的话可以不求    
    int d=exgcd(b,a%b,y,x);
    return y-=a/b*x,d;
}

求逆元:

long long inv(long long a,long long m){
    long long x,y;
    long long d=exgcd(a,m,x,y);
    return d==1 ?(x+m)%m:-1;//不互质就没有逆元
}

6.模与同余

模的四则运算

$$
(a + b) \% n = (a \% n + b \% n) \% n\\ (a - b) \% n = (a \% n - b \% n) \% n\\ (a * b) \% n = (a \% n * b \% n) \% n\\ a^b{\ }\%n=((a\%n)^b){\ }\%n{\ }(可用于降幂)
$$

同余

$$
如果(a-b)被m整除,那么记作a≡b(mod{\ }{\ }m),即a与b对模m同余
$$

费马小定理

$$
如果p是质数,a为自然数,gcd(a,p)=1,则a^{p-1}≡1(mod{\ }{\ }p)
$$

二次探测定理

$$
p是质数,0 $$

7.乘法逆元

int inv[MAXN];
inv[0] = inv[1] = 1;
    for (int i = 2; i <= n; i ++) {
        inv[i] = (1LL * (p - p / i) * inv[p % i]) % p;
    }

求组合数

既求某个数的阶乘的逆元

inv[i] 相当于是除以 i 的阶乘

ll fac[MAXN], inv[M];
fac[0] = inv[0] = 1;
for(int i = 1; i <=maxn; i++) fac[i] = fac[i - 1] * i % mod;
inv[maxn] = pow(fac[maxn], mod - 2);
for(int i = maxn - 1; i > 0; i--) inv[i] = inv[i + 1] * (i + 1) % mod;

7.中国剩余定理

求x的整数解:m_1、m_2、……、m_n互质

$$
x≡r_1(mod{\ }{\ }m_1)\\ x≡r_2(mod{\ }{\ }m_2)\\ ⋅⋅⋅\\ x≡r_n(mod{\ }{\ }m_n)
$$

long long mod[N],rmd[N],M = 1;//mod[i]即为mi,rmd表示余数
void exgcd(long long a,long long b,long long &x,long long &y){
    if(b == 0) { x = 1,y = 0; return;}
    exgcd(b,a%b,y,x);
    y = y- a/b*x;
}
long long crt(int n){
	long long ans;
	for (int i = 0;i < n;i++) {
        M*=mod[i];
    }
    for (int i = 0;i < n;i++) {
        long long Mi = M / mod[i],inv,y; // Mi为所有模数乘积除以第i个模数,inv为 Mi在模 mi意义下的逆元
        exgcd(Mi, mod[i], inv, y);
        inv = inv % mod[i];
        ans = (ans + rmd[i] * Mi * inv) % M;
    }
    return (ans+M)%M;
}

思路:

$$
令M=m_1{\ }×{\ }m_2{\ }×⋯×{\ }m_n=∏m_i\\ 定义M_i=M/m_i,∀i∈\{1,2,⋯,n\}\\ 再设t_i为M_i对模m_i的逆元,即M_it_i≡1(mod{\ }{\ }m_i)\\ 构造特解x_0=∑r_iM_it_i,故通解为x=x_0+k·M
$$

扩展中国剩余定理

在m_1,m_2,……,m_n不互质的情况下求解x

long long mod[N],rmd[N];
long long excrt(int n){
	//ans表示前i-1个方程式的特解,M为前i-1个方程式的模数的最小公倍数(i从2开始)
	long long ans = rmd[0],M = mod[0],k,tmp;
    for(int i = 1;i < n;i++){
        long long mi = mod[i];
		long long res = ((rmd[i] - ans)%mi + mi)%mi;	//res=r2-r1,ans=r1
        long long gcd = exgcd(M,mi,k,tmp);
        if(res % gcd != 0) return -1;
        k = qmul(k,res/gcd,mi);
        ans += k * M;
        M = mi /gcd * M;	//M=lcm(M,mi)
        ans = (ans%M+M)%M;
    }
    return ans;
}

思路:

$$
将两个同余式合并,即x=r_1+t_1m_1=r_2+t_2m_2\\ 则有k_1p_1-k_2p_2=(r_2-r_1)/d,其中d=gcd(m_1,m_2)\\ 当且仅当 d | (r_2-r_1)时有解,因此x的一个特解x^*=r_1+k_1m_1\\ x在一组等价类内的通解为x^*+lcm(m_1,m_2)\\ 因此n个同余式的x的解为x=ans+M
$$

七、数学

1.组合数与排列数

组合数

组合恒等式

$$
C(n,m)=C(n,n-m)=C(n-1,m-1)+C(n-1,m)
$$

打表:

long long c[MAXN][MAXN];	//c[huge][small]
void getc(){
	c[0][0]=1;
	for(int i=1;i<=n;i++){
		c[i][0]=1,c[i][1]=i;
		for(int j=1;j<=m;j++){
			c[i][j]=c[i-1][j-1]+c[i-1][j];
			c[i][j]%=MOD;
		}
	}
}

卢卡斯定理

const ll N = 5010;
ll fac[N * N];
void init() {
	fac[0] = 1;
	for (int i = 1; i < N * N; i++) {
		fac[i] = (1LL * i * fac[i - 1]) % mod;
	}
}
ll getc(ll a, ll b) {
	return (ll)fac[a] * pow(fac[a - b], mod - 2) % mod * pow(fac[b], mod - 2) % mod;
}
ll Lucas(ll n, ll m) {
	if (n < mod && m < mod)
		return getc(n, m);
	if (m == 0)
		return 1;
	return getc(n % mod, m % mod) * Lucas(n / mod, m / mod) % mod;
}

放球模型

1.n个球相同,m个盒子不同,盒子不可空,方案数为:

$$
C(n-1,m-1)
$$

2.n个球相同,m个盒子不同,盒子可空,方案数为:

$$
C(n+m-1,m-1)
$$

3:n个球相同,m个盒子相同,盒子不可空。(数的拆分)

$$
ans=dp[n][m]=dp[n-1][m-1]+dp[n-m][m]
$$

4:n个球相同,m个盒子相同,盒子可空。

$$
ans=dp[n][1]+...+dp[n][min(n,m)]
$$

2.Striling数

第一类斯特林数

表示将n个不同的元素构成m个圆排列的数目

第二类斯特林数

表示将n个不同的元素分成m个集合的方案数

集合内不考虑次序

递推式:

$$
S(n,m)=m*S(n-1,m)+S(n-1,m-1);
$$

//#define MOD 1000000007  //注意取模
long long S[MAXN][MAXN];	//从1到 n,从1到 m 
void getS(){
	for(int i=0;i<=m;i++) S[0][i]=0;
	for(int i=1;i<=n;i++){
		S[i][1]=1;
		for(int j=2;j<=m;j++){
			S[i][j]=j*S[i-1][j]+S[i-1][j-1];
			S[i][j]%=MOD;
		}
	}
}

性质

放球模型

(1)n个不同的球,放入m个无区别的盒子,不允许盒子为空。

方案数:

$$
S(n,m)
$$

这个跟第二类Stirling数的定义一致。

(2)n个不同的球,放入m个有区别的盒子,不允许盒子为空。

方案数:

$$
m!*S(n,m)
$$

因盒子有区别,乘上盒子的排列即可。

(3)n个不同的球,放入m个无区别的盒子,允许盒子为空。

方案数:

枚举非空盒的数目便可。

(4)n个不同的球,放入m个有区别的盒子,允许盒子为空。

①方案数:

同样可以枚举非空盒的数目,注意到盒子有区别,乘上一个排列系数。

②既然允许盒子为空,且盒子间有区别,那么对于每个球有m种选择,每个球相互独立。有方案数:

3.整数拆分

将整数 j 划分为 i 个数的方案数为:

$$
f(i,j)=f(i,j−i)+f(i−1,j−1) (放入一个1或所有数大小+1)
$$

拓展1:若划分整数需两两不同:

不能连续放入两个1而不进行所有数+1操作,即放入1前一次操作只能是+1

$$
f(i,j)=f(i,j−i)+f(i−1,j−i)
$$

再拓展:如果只能用 ≤x 的数:

$$
f ( i , v ) − = f ( i − 1 , v − ( x + 1 ) )
$$

拓展2:全部拆分为奇数的方案数与两两不同的方案数相等。

拓展3:拆成若干小于等于k的数等价于拆成小于等于k个任意大小的数。

unsigned long long dynamic_GetPartitionCount(int n, int k){
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
		{
			if(j==1|| i==1)
			{
				dp[i][j]=1;
			}
			else 
			{
				if(j==i)
					dp[i][j]=dp[i][j-1]+1;
				else if((i-j) 
  

五边形数

第n个五边形数

$$
f(n)=n(3n-1)/2
$$

五边形生成函数

$$
g(x)=x(2x+1)/(1-x)^3=x+5x^2+12x^3+22x^4+……
$$

欧拉函数

五边形定理

整数划分的生成函数

4.切比雪夫距离

曼哈顿距离转切比雪夫距离

将一个点的坐标(x,y)转化成(x+y,x-y),则原来的曼哈顿距离等于现在的切比雪夫距离:

$$
∣x1−x2∣+∣y1 −y2∣ = max(∣x1'−x2'∣,∣y1' −y2'∣)
$$

在切比雪夫坐标系中的点(x,y)转换到曼哈顿坐标系就为((x+y)/2,(x-y)/2)

5.因数分解

因数分解

vector get_divisors(int x){
    vector res;
    for (int i = 1; i <= x / i; i ++ )
        if (x % i == 0){
            res.push_back(i);
            if (i != x / i) res.push_back(x / i);
        }
    sort(res.begin(), res.end());
    return res;
}

质因数分解

void divide(int x){
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            int s = 0;
            while (x % i == 0) x /= i, s ++ ;
            cout << i << ' ' << s << endl;
        }
    if (x > 1) cout << x << ' ' << 1 << endl;
    cout << endl;
}

6.线性基

线性基是一个数的集合,并且每个序列都拥有至少一个线性基,取线性基中若干个数异或起来可以得到原序列中的任何一个数。

线性基三大性质

  1. 原序列里面的任意一个数都可以由线性基里面的一些数异或得到

  2. 线性基里面的任意一些数异或起来都不能得到 0

  3. 线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的

线性基的构造

设数组 d 表示序列 a 的线性基,下标从 0 开始算。(设 x`2 为 x 的二进制数)

void add(ll x){
    for(int i=60;i>=0;i--){
        if(x&(1ll< 
  

据此,我们可以得到一个关于d数组的性质:若 d[i] 不为 0,则 d[i]`2 的第 i+1 位一定为 1 ,并且 d[i]`2的最高位就是第 i + 1 位。 如何求在一个序列中,取若干个数,使得它们的异或和最大?

ll solve(){
    ll ans=0;
    for(int i=60;i>=0;i--)//记得从线性基的最高位开始
    if((ans^d[i])>ans) ans^=d[i];
    return ans;
 }   

如何求在一个序列中,取若干个数,使得它们的异或和最小?

若序列中有元素不能插入线性基,则答案为 0 ,否则为最小的 d[i]。

从一个序列中取任意个元素进行异或,如何求能异或出的所有数字中第 k 小的那个?

只要使提供出来的 1 可以和 k`2 的每一位上的 1 对应,那么求出来的 ans 一定就是第 k 小的。

void work(){	//处理线性基
	for(int i=1;i<=60;i++)
	for(int j=1;j<=i;j++)
	if(d[i]&(1ll<<(j-1)))d[i]^=d[j-1];
}
ll k_th(ll k){
	if(k==1&&tot 
  

线性基的删除操作

给一个序列,有三种操作,一是往序列中插入一个数,二是删除这个序列中的一个数,三要求你维护这个序列的线性基。

重点是删除操作,如果要删除的数 x 在线性基外,那么直接删掉即可,问题是假如它在线性基内,把他删掉之后可能序列中其他的数可以填进来。

没有在线性基中的数,一定是因为线性基中的若干个数可以异或得到他,那么可以记录一下不在线性基中的数都是由线性基中的哪些数异或得到的,那么每一个线性基外的数对应一个集合 S ,这个集合内就是线性基中那些异或起来可以得到他的数。

假如线性基外的某一个数的 S 中包含 x ,那么就不需要删除 x ,把这个数删除即可。

假如 x 不被线性基外的任何一个数的 S 包含,那么就另外造一个集合 P ,记录线性基中这个数插入进来的时候异或过哪些数。然后找到线性基中最小的并且 P PP 包含 x xx 的数,让他异或线性基中其他包含 x xx 的数即可(包括自己,自己异或完自己后自己这一位就变成了 0 00),这样就能消除 x xx 在线性基中的影响(事实上就等价于用最小的数代替了它)。

9.

八、博弈论

公平组合游戏

若一个游戏满足:

  • 由两名玩家交替行动

  • 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关

  • 游戏中的同一个状态不可能多次抵达,游戏以玩家无法行动为结束,且游戏一定会在有限步后以非平局结束

则称该游戏为一个公平组合游戏。

有向图游戏

给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。

任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。 转化为有向图游戏,也称绘制了它的博弈状态图(简称博弈图或游戏图)。

这样,对于组合游戏中的每一次对弈(每一局游戏),我们都可以将其抽象成游戏图中的一条从某一顶点到出度为 0 的点的路径。

先手必胜与先手必败

先手必胜状态 : 先手行动以后,可以让剩余的状态变成必败状态 留给对手(下一步是对手(后手)的局面)。

先手必败状态 : 不管怎么操作,都达不到必败状态,换句话说,如果无论怎么行动都只能达到一个先手必胜状态留给对手,那么对手(后手)必胜,先手必败。

即,

先手必胜状态:可以走到某一个必败状态

先手必败状态:走不到任何一个必败状态

定理

定理 2.1: 没有后继状态的状态是必败状态。

定理 2.2: 一个状态是必胜状态当且仅当存在至少一个必败状态为它的后继状态。

定理 2.3: 一个状态是必败状态当且仅当它的所有后继状态均为必胜状态。

若博弈图是一个有向无环图,则通过这三个定理,我们可以在绘出博弈图的情况下用 O ( N + M ) 的时间(其中 N 为状态种数,M 为边数)得出每个状态是必胜状态还是必败状态。

必胜点和必败点

必败点(P点) :前一个(previous player)选手将取胜的点称为必败点

必胜点(N点) :下一个(next player)选手将取胜的点称为必胜点

(1) 所有终结点是必败点(P点)

(2) 从任何必胜点(N点)操作,至少有一种方法可以进入必败点(P点)

(3)无论如何操作, 从必败点(P点)都只能进入必胜点(N点)

有向图的核 给定一张DAG图 < V , E >,如果 V 的一个点集 S 满足:

S 是独立集(集合内的点互不相通)

集合 V − S 中的点都可以通过一步到达集合 S 中的点( V−S 指 S 在 V 中的补集)

则称 S 是图 V 的一个核。

结论: 核内节点对应 SG 组合游戏的必败态

因为 Alice 当前的棋子在 S 中,由于 S 是独立集,也就意味着 Alice 只能将棋子从 S 移动到 V − S

而 Bob又可以通过一步移动将棋子从 V − S 移动到了 S,这样 Alice 就好像是被支配了一样,被迫把棋子移动到了没有出度的必败节点,Alice 必败,Bob必胜!

1.尼姆博弈

给定 N 堆物品,第 i 堆物品有 Ai 个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。 结论:定义 Nim和 为 a1 ⊕ a2 ⊕ . . . ⊕ an 当且仅当 Nim和 为 0 时,该状态为先手必败状态,否则该状态为先手必胜状态

$$
⊕ 表示异或,相同为0不同为1
$$

2.SG函数

f[]:可以取走的石子个数 sg[]:0~n的SG函数值 vis[]:mex{}

int f[N],sg[N],vis[N];     
void getsg(int n,int m){	//一堆石子有n个,m种取法 
    memset(sg,0,sizeof(sg));
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        for(int j=0;j 
  

九、计算几何

1.判断点坐标是否在多边形内部

PNPoly算法

参数nvert 代表多边形有几个点。
浮点数testx, testy代表待测试点的横坐标和纵坐标,
*vertx,*verty分别指向储存多边形横纵坐标数组的首地址。

bool pnpoly(int nvert,float *vertx,float *verty,float testx,float testy){
    int i, j;
    bool c = 0;
    for (i = 0, j = nvert-1; i < nvert; j = i++) {
        if ( ( (verty[i]>testy) != (verty[j]>testy) ) &&
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
            c = !c;
    }
    return c;
}

2.判断两条线段是否相交

若a × b,叉积是正数,a 在b 的顺时针方向;负数, b 在a 的顺时针方向; 0的话, 模相等且共线

补充:拉格朗日公式:

$$
a × (b × c) = b(a·c) - c (a·b)
$$

$$
(a × b) × c = b(a·c) - a(b·c)
$$

ab与cd线段相交的判定:

1.快速排斥

if(min(a.x,b.x)<=max(d.x,c.x)&&min(c.x,d.x)<=max(a.x,b.x)
			&&min(a.y,b.y)<=max(d.y,c.y)&&min(c.y,d.y)<=max(a.y,b.y))

2.跨立实验

cd × cb 与 cd × ca 异号 && ba × bd 与 ba × bc 异号

(若有一方为0也可)

#define getx(ax,ay,bx,by) ax*by-ay*bx

3.计算多边形面积

 
  

4.半径为r的圆能覆盖的做多的点

头文件

#include
#include 
#include 
#include 

函数主体

#define N 320
#define eps 1e-6
#define r 1.0
struct node{
	double x,y;
}c[N];

double dis(node p1,node p2){
    return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}

node getc(node p1,node p2){
	node mid = node{(p1.x+p2.x)/2,(p1.y+p2.y)/2};
    double angle = atan2(p1.x-p2.x,p2.y-p1.y);
    double d = sqrt(r*r-pow(dis(p1,mid),2));
    node tmp={mid.x+d*cos(angle),mid.y+d*sin(angle)};
    return tmp;
}

主函数

int ans=1;
		for(int i=0;i 2.0*r) continue;
				
				node center=getc(c[i],c[j]);
				int cnt = 0;
            	for(int k=0;k 
  

5.两直线夹角

double VectorAngle(double v1x, double v1y, double v2x, double v2y){
  double ret = 0.0;
  double l1, l2;
  double err = 0.00001;
  l1 = sqrt(v1x * v1x + v1y * v1y);
  l2 = sqrt(v2x * v2x + v2y * v2y);
  if((l1 > err) && (l2 > err))
  {
    ret = acos((v1x * v2x + v1y * v2y) / (l1 * l2));
  }
  return ret;
}

十、杂项

1.INF

#define INF 0x3f3f3f3f

2.goto语句

//执行后走到标签处
goto label;

//标签写法:
label:

3.读取一行整数

int m[110],num=0,x;
string s;
getline(cin,s);
stringstream str(s);
while(str>>x){
	m[num++]=x;
}//数组长度为num;

4.大数运算

1)高精度算法

高精度加法

// C = A + B, A >= 0, B >= 0
vector add(vector &A, vector &B){
    if (A.size() < B.size()) return add(B, A);

    vector C;
    int t = 0;
    for (int i = 0; i < A.size(); i ++ )
    {
        t += A[i];
        if (i < B.size()) t += B[i];
        C.push_back(t % 10);
        t /= 10;
    }

    if (t) C.push_back(t);
    return C;
}

高精度减法

// C = A - B, 满足A >= B, A >= 0, B >= 0
vector sub(vector &A, vector &B){
    vector C;
    for (int i = 0, t = 0; i < A.size(); i ++ )
    {
        t = A[i] - t;
        if (i < B.size()) t -= B[i];
        C.push_back((t + 10) % 10);
        if (t < 0) t = 1;
        else t = 0;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

高精度乘低精度

// C = A * b, A >= 0, b >= 0
vector mul(vector &A, int b){
    vector C;

    int t = 0;
    for (int i = 0; i < A.size() || t; i ++ )
    {
        if (i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();

    return C;
}

高精度除以低精度

// A / b = C ... r, A >= 0, b > 0
vector div(vector &A, int b, int &r){
    vector C;
    r = 0;
    for (int i = A.size() - 1; i >= 0; i -- )
    {
        r = r * 10 + A[i];
        C.push_back(r / b);
        r %= b;
    }
    reverse(C.begin(), C.end());
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

2)Java大数类

举例:卡特兰数

import java.math.BigInteger;
import java.util.*;
public class Text {
    static Scanner in = new Scanner(System.in);
    public static void main(String[] args) {
        while(in.hasNext()){
            int n=in.nextInt();
            BigInteger[] num=new BigInteger[105];
            num[0]=num[1]=new BigInteger("1");
            BigInteger a,b;
            for (int i = 2; i <=n; i++) {
                a=new BigInteger(Integer.toString(4*i-2));
                b=new BigInteger(Integer.toString(i+1));
                num[i]=num[i-1].multiply(a).divide(b);
            }
            System.out.println(num[n]);
        }
    }
}
public void testBasic() {
	BigInteger a = new BigInteger("13");
	BigInteger b = new BigInteger("4");
	int n = 3;
		//1.加
	BigInteger bigNum1 = a.add(b);			//17
		//2.减
	BigInteger bigNum2 = a.subtract(b);		//9
		//3.乘
	BigInteger bigNum3 = a.multiply(b);		//52
		//4.除
	BigInteger bigNum4 = a.divide(b);		//3
		//5.取模(需 b > 0,否则出现异常:ArithmeticException("BigInteger: modulus not positive"))
	BigInteger bigNum5 = a.mod(b);			//1
		//6.求余
	BigInteger bigNum6 = a.remainder(b);	//1
		//7.平方(需 n >= 0,否则出现异常:ArithmeticException("Negative exponent"))
	BigInteger bigNum7 = a.pow(n);			//2197
		//8.取绝对值
	BigInteger bigNum8 = a.abs();			//13
		//9.取相反数
	BigInteger bigNum9 = a.negate();		//-13
}

3)long long 拼接

举例:poj 3181 Dollar Dayz整数划分

从1-k这k个整数,有多少种组合方法使得和为n?

1-k这k个数可以无限使用。

const int MAXN=1100;
const ll inf=1000000000000000000LL;
 
ll a[MAXN];
ll b[MAXN];
 
int main()
{
    int n,k;
    while(scanf("%d%d",&n,&k)!=EOF){
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        b[0]=1;
        for(int i=1;i<=k;i++){
            for(int j=i;j<=n;j++){
                a[j]=a[j]+a[j-i]+(b[j]+b[j-i])/inf;
                b[j]=(b[j]+b[j-i])%inf;
            }
        }
        if(a[n]==0){
            printf("%I64d\n",b[n]);
        }else{
            printf("%I64d%018I64d\n",a[n],b[n]);
        }
    }
    return 0;
}

5.快读与快写

1)快读

int read(){
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}

2)快写

void write(int x) {
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}

6.斐波那契数列

打表:O(n)

int fib[N];
void initfib(){
   fib[1] = 1;
   fib[2] = 1;
   for(int i=3; i<=N; i++)
       fib[i] = (fib[i - 2] + fib[i - 1]);
}

滚动数组递推

int fib(int n){
    int dp[3] = {1, 1};
    if (n <= 1) return dp[n];

    for (int i = 2; i <= n; ++i)
        dp[i % 3] = dp[(i - 1) % 3] + dp[(i - 2) % 3];

    return dp[n % 3];
}

求第n个斐波那契数

递归:O(logn)

int fib(int n){
    if (n == 0 || n == 1) return 1;
    int** mat = recur(n - 1);
    return mat[0][1] + mat[1][1];
}

矩阵幂运算

思路:

1)快速幂

a^n = (a^(n/2))^2 * a^(n%2)

2)矩阵乘法

//二维矩阵的乘法
int** mult(int** m1, int** m2){
    int** res = new int*[2];
    for (int i = 0; i < 2; ++i) res[i] = new int[2];

    res[0][0] = m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0];
    res[0][1] = m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1];
    res[1][0] = m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0];
    res[1][1] = m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1];

    return res; 
}

矩阵快速幂的主体

//其结果为,被[1,1]矩阵相乘
int** recur(int x){
    if (x == 0){
        int** res = new int*[2];
        for (int i = 0; i < 2; ++i) res[i] = new int[2];
        res[0][0] = res[1][1] = 1;
        res[0][1] = res[1][0] = 0;
        return res;
    }
    if (x == 1){
        int** res = new int*[2];
        for (int i = 0; i < 2; ++i) res[i] = new int[2];
        res[0][1] = res[1][0] = res[1][1] = 1;
        res[0][0] = 0;
        return res;
    }
    int** half = recur(x / 2);
    return mult(mult(half, half), recur(x % 2));
}

7.二分查找

int binsearch(int *a,int n,int low,int high){
	int m=(high+low)/2;
	if(n==a[m]) return m+1;
	else if(n 
  

8.排序算法

快速排序

void quicksort(int l,int r){
	if(l>=r) return ;
	int x=a[(l+r)>>1],i=l-1,j=r+1;
	while(ix);
		while(a[--j] 
  

归并排序

调用:mergesort(a,0,n-1); //n-1的位置有元素

#define N 200010
int a[N],tmp[N];
//long long ans;
void mergesort(int a[],int l,int r){
	if(l>=r) return ;
	int mid=(l+r)>>1;
	mergesort(a,l,mid);
	mergesort(a,mid+1,r);
	
	int k=0,i=l,j=mid+1;
	while(i<=mid&&j<=r){
		if(a[i]<=a[j]) tmp[k++]=a[i++];
		else{
			tmp[k++]=a[j++];
			ans+=mid-i+1;
		}
	}
	while(i<=mid) tmp[k++]=a[i++];
    while(j<=r) tmp[k++]=a[j++];
	for(int i=0;i 
  

插入排序

void insertsort(int a[],int n){
	for(int i=1;i=0&&a[j]>key){
			a[j+1]=a[j];
			j--;
		}
		a[j+1]=key;
	}
}

冒泡排序

交换次数 = 逆序对的个数

void bubble_sort(int a[],int n){
	int i,t;
	for(t=0;ta[i+1]){
				int tmp=a[i];
				a[i]=a[i+1];
				a[i+1]=tmp;
			}
		}
	}
}

选择排序

void selection_sort(int a[],int n){
	for(int i=0;i 
  

9.int128

输入

inline __int128 read(){
    __int128 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;
}

输出

inline void print(__int128 x){
    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>9)
        print(x/10);
    putchar(x%10+'0');
}

10.结构体自定义操作符

struct node{
    int x,y;
    bool operator < (const node& o) const{
        return x 
  

11.高精度

高精度加法

string add(string a,string b)//只限两个非负整数相加
{
    string ans;
    int na[L]={0},nb[L]={0};
    int la=a.size(),lb=b.size();
    for(int i=0;ilb?la:lb;
    for(int i=0;i=0;i--) ans+=na[i]+'0';
    return ans;
}

高精度减法

string sub(string a,string b){	//只限大的非负整数减小的非负整数
    string ans;
    int na[L]={0},nb[L]={0};  
    int la=a.size(),lb=b.size();  
    for(int i=0;ilb?la:lb;  
    for(int i=0;i0)  ;lmax++;  
    for(int i=lmax-1;i>=0;i--) ans+=na[i]+'0';  
    return ans;  
}

高精度乘法

string mul(string a,string b){	//高精度乘法a,b,均为非负整数  
    string s;  
    int na[L],nb[L],nc[L],La=a.size(),Lb=b.size();	//na存储被乘数,nb存储乘数,nc存储积  
    fill(na,na+L,0);fill(nb,nb+L,0);fill(nc,nc+L,0);	//将na,nb,nc都置为0  
    for(int i=La-1;i>=0;i--) na[La-i]=a[i]-'0';	//将字符串表示的大整形数转成i整形数组表示的大整形数  
    for(int i=Lb-1;i>=0;i--) nb[Lb-i]=b[i]-'0';  
    for(int i=1;i<=La;i++)  
        for(int j=1;j<=Lb;j++)  
        nc[i+j-1]+=na[i]*nb[j];	//a的第i位乘以b的第j位为积的第i+j-1位(先不考虑进位)  
    for(int i=1;i<=La+Lb;i++)  
        nc[i+1]+=nc[i]/10,nc[i]%=10;	//统一处理进位  
    if(nc[La+Lb]) s+=nc[La+Lb]+'0';	//判断第i+j位上的数字是不是0  
    for(int i=La+Lb-1;i>=1;i--)  
        s+=nc[i]+'0';	//将整形数组转成字符串  
    return s;  
}

12.矩阵

求矩阵行列式

int det(int n, int a1[MAXN][MAXN]){
    int b[MAXN][MAXN];
	int sum=0;          //b保存当前n*n阶行列式a的余子式
    if(n==1) return a1[0][0];     //n为1时结束递归
    for(int i=0; i 
  

求伴随矩阵

void adjoint_matrix(int n, int a[MAXN][MAXN]){   
    for(int i=0; i 
  

矩阵可逆的条件是|A|!=0,矩阵求逆:

$$
A* = A^{-1}/|A|
$$

13.异或

$$
若a异或b=c,则a异或c=b;
$$

14.回文

一个长度为n的字符串,每一位可以填1~k中的一个数,则一共有k^((n+1)/2)种排列方式

十一、函数库

Math

int abs(int i)  返回整型参数i的绝对值
double fabs(double x)  返回双精度参数x的绝对值
long labs(long n)  返回长整型参数n的绝对值
​
​
double exp(double x)  返回指数函数e^x的值
double log(double x)  返回logex的值
double log10(double x) 返回log10x的值
double pow(double x,double y) 返回x^y的值
double pow10(int p) 返回10^p的值
​
​
double sqrt(double x) 返回+√x的值
​
​
double acos(double x) 返回x的反余弦arccos(x)值,x为弧度
double asin(double x) 返回x的反正弦arcsin(x)值,x为弧度
double atan(double x) 返回x的反正切arctan(x)值,x为弧度
double cos(double x) 返回x的余弦cos(x)值,x为弧度
double sin(double x) 返回x的正弦sin(x)值,x为弧度
double tan(double x) 返回x的正切tan(x)值,x为弧度
​
​
double hypot(double x,double y) 返回直角三角形斜边的长度(z),
  x和y为直角边的长度,z^2=x^2+y^2
​
​
double ceil(double x) 返回不小于x的最小整数
double floor(double x) 返回不大于x的最大整数
​
​
int rand() 产生一个随机数并返回这个数
​
​
double atof(char *nptr) 将字符串nptr转换成浮点数并返回这个浮点数
double atol(char *nptr) 将字符串nptr转换成长整数并返回这个整数
double atof(char *nptr) 将字符串nptr转换成双精度数,并返回这个数,错误返回0
int atoi(char *nptr) 将字符串nptr转换成整型数, 并返回这个数,错误返回0
long atol(char *nptr) 将字符串nptr转换成长整型数,并返回这个数,错误返回0

你可能感兴趣的:(算法,c++)