资讯详情

算法【板子】

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

一、动态规划

1.背包DP

n件价值vi重量wi将容量为m的背包放入物品中

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

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

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

memset(dp,INF,sizeof(dp));  dp[0]=0;  for(int i=0;i<n;i  ){   for(int j=M;j>=v[i];j--){    dp[j]=min(dp[j],dp[j-v[i]] w[i]);   }  }  long long i,ans;  for(i=0;i<M;i  ) if(dp[i]<=t) ans=i;  cout<<ans<<endl;

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

二进制优化

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

for(int i=0;i<n;i  ){   int tv,tw,tm;   cin>>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<cnt; i  ){   for(int j=t; j>=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;d<w;d  ){ ///模拟余数     head=tail=1; //初值     for(int j=0;j<=(n-d)/w;j  ){     //(n-d)/w指目前有多少余数      int o=f[j*w d]-v*j;///进入队列的值                     while(q[tail-1]<=o&&head<tail)tail--;///弹出队尾                     q[tail]=o;//进队      p[tail  ]=j;//下标                     while(j-p[head]>num&&head<tail)head  ;///弹出队头                     f[j*w d]=max(f[j*w d],q[head] v*j);//找最大值     }    }   }

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<<ans<<endl; }

区间DP

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 第一个字符                 int replace = dp[i - 1][j - 1]   1; // A 替换成 B 第j个字符                 dp[i][j] = min(replace, min(insert1, insert2));             }   }  }  cout<<dp[a.size()][b.size()]<<endl;

for(int len=1;len<n;len  ){   for(int l=1;l len<=n;l  ){    int r = l len;        dp[l][r]=INF;    for(int k=l;k<r;k  ){     dp[l][r]=min(dp[l][r],dp[l][k] dp[k 1][r] s[r]-s[l-1]);    }   }  }  cout<<dp[1][n]<<endl;

2.数位DP

获取x,单独存储在a[i]中

int solve(int x){     int pos=0;     while(x){         a[pos  ]=x;         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<<j)-1<=n;i++)
            st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}

查询:

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

二、搜索

1.回溯算法

1) 回溯的实现

递归

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

void backtrack(int t){
	if(t>n) output(x);   //叶子节点,输出结果,x是可行解
	else for(int i=0;i<k;i++){   //当前节点的所有子节点
		x[t]=value[i];   //每个子节点的值赋值给x 
		
		//满足约束条件和限界条件
		if(constraint(t)&&bound(t)) backtrack(t+1);
	}
}

递推

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

void backtrack(){
	int t=1;
	while(t){
		if(exitsubnode(t)){   //若当前节点的存在子节点  
			for(int i=0;i<k;i++){  //遍历当前节点的所有子节点
				x[t]=value[i];
				//满足边界条件和限界条件
				if(constraint(t)&&bound(t)){
					//如果在节点t处得到了一个解  
					if(solution(t)) output(x);//输出可行解
					else t++;  //继续向下搜索
				}
			}
		}
		else t--;   //若不存在子节点,则返回上一层
	}
}

2) 子集树和排列树

子集树

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

void backtrack(int t){
	if(t>n) output(x);
	else for(int i=0;i<k;i++){
		x[t]=value[i];
		if(constraint(t)&&bound(t)) backtrack(t+1);
	}
}

排列树

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

void backtrack(int t){
	if(t>n) output(x);
	else for(int i=t;i<n;i++){
		swap(x[t],x[i]);
		if(constraint(t)&&bound(t)) backtrack(t+1);
		swap(x[t],x[i]);
	}
}

2.BFS(广度优先搜索)

/**

  • 广度优先搜索

  • @param Vs 起点

  • @param Vd 终点

    */

bool BFS(Node& Vs, Node& Vd){  
    queue<node> 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

三、数据结构

vector<int> 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<o.y;
        return x<o.x;
    }
};

泛型:

template<typename T>
struct node{
	T x,y;
	node(T x=0,y=0):x(x),y(y){}
}
node<T> operator + (const node<T>& A,const node<T>& B){
	return node<T>(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对象
vector<int> vec;

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

//使用下标访问结点
cout<<vec[0];

//在第i个元素前插入某元素
vec.insert(vec.begin(),a);	//在头部插入
vec.insert(vec.begin()+i,a);

//删除第i+1个元素
vec.erase(vec.begin()+i);
//删除[i,j-1]的全部元素(下标从i开始,到j结束,不包括j)
vec.erase(vec.begin()+i,vec.end()+j);

//返回集合的大小
vec.size();

//清空集合
vec.clear();

2.Map

#include<map>

map<char,int> s;

s.size();

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

//插入
使用下标访问不存在的元素,将会在map容器中添加一个新的元素;
使用下标访问存在的元素,将会覆盖map容器中的该元素

//删除
if (s.find(key) != mymap.end()) s.erase (key);
s.erase(0);   //删除key为0的元素
s.erase(s.begin());    //删除迭代器指向的位置元素

// 查询map是否为空
bool empty();

// 查询map中键值对的数量
size_t size();

// 查询map所能包含的最大键值对数量,和系统和应用库有关。
// 此外,这并不意味着用户一定可以存这么多,很可能还没达到就已经开辟内存失败了
size_t max_size();

// 查询关键字为key的元素的个数,在map里结果非0即1
size_t count( const Key& key ) const; //

//map插入pair类型
s.in

3.Set

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

set<int> 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<set<int>::const_iterator,set<int>::const_iterator> pr;
pr = s.equal_range(3);
cout<<"第一个大于等于 3 的数是 :"<<*pr.first<<endl;  //输出3
cout<<"第一个大于 3的数是 : "<<*pr.second<<endl;  //输出4

//删除
erase(iterator)  ,删除定位器iterator指向的值
erase(first,second),删除定位器first和second之间的值
erase(key),删除键值key_value的值

//查找
find(key)  ,返回给定值值得定位器,如果没找到则返回end()。
count(key) f
lower_bound(key) ,返回第一个大于等于key_value的定位器
upper_bound(key),返回最后一个大于等于key_value的定位器

/*
s.insert(key)有返回值:pair<set<int>::iterator,bool>
if (s.insert(key).second){
	set<int>::iterator p = s.insert(key).first;
	//则*p==key;
}
//若存在返回false,不存在返回true

*/

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

4.Stack

1)stack类

//头文件
#include<stack>

//栈的声明
stack<char> 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<int> s;
	int i=0;
	long long ans=0,tmp;
	while(i<n){
		if(s.empty()||h[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>

//声明队列
queue<int> q;

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

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

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

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

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

2)优先队列 :priority_queue类

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

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

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

基础方法:

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

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

自定义类算子写法:

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

bool operator < (const T&t1,const T&t2){
	return t1.n<t2.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<m-1;i++){
		while(trail>=head&&h[i]<=v[trail].h) trail--;
		v[++trail].h=h[i],v[trail].p=i+1;
	}
	for(;i<n;i++){
		while(t>=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<m-1;i++){
		while(trail>=head&&h[i]>=v[trail].h) trail--;
		v[++trail].h=h[i],v[trail].p=i+1;
	}
	for(;i<n;i++){
		while(trail>=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<int> 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 <list>

list<int>lst1;			//创建空list
list<int> lst2(5);		//创建含有5个元素的list
list<int>lst3(3,2);		//创建含有3个元素的list
list<int>lst4(lst2);	//使用lst2初始化lst4
list<int>lst5(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<int>()) 合并两个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<int>::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.树

树的重心:

定义:

1)二叉树

结构体写法

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

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

二叉树的先序遍历

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

二叉树的中序遍历

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

二叉树的后序遍历

void proorder(TreeNode* root, vector<int>& 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<n-1;i++){
			cin>>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);
}

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]<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 <iostream>
using namespace std;
int main(){
	string str1="hello";
	cout<<str1.compare("helloo")<<endl;//返回-1; 
    cout<<str1.compare("hello")<<endl;//返回0 ; 
	cout<<str1.compare("hell")<<endl;//返回1; 
} 

                //char数组的比较
                
#include <iostream>
#include <cstring>
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<<strcmp(str1,str2)<<endl;//返回1; 
    cout<<strcmp(str1,str3)<<endl;//返回-1; 
	cout<<strcmp(str1,str4)<<endl;//返回0. 
} 

字符串截取

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<n;i++){
		hash=(hash*BASE+s[i])+PRIME;
	}
	return hash;
}

取模

#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<n;i++){
		hash=(hash*BASE+s[i])%mod+PRIME;
	}
	return hash%mod;
}

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<slen){
		if(j==-1||s[i]==p[j]){
			i++;
			j++;
		}
		else{
			j=nxt[j];
		}
		if(j==plen){
			j=0;
			ans++;
			//cout<<i-plen<<endl;
			//i--;
			//j=nxt[plen-1];
		}
	}

4.Manacher算法

求回文子串

#include<bits/stdc++.h>
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;i<ls;i++){
		x=0;
		for(int j=0;j<lt;j++){
			int tmp=dp[j];
			if(s[i]==t[j]) dp[j]=x+1;
			else if(dp[j-1]>dp[j]) dp[j]=dp[j-1];
			x=tmp;
		}
	}
}

主函数

init();
cout<<dp[lt-1]<<endl;

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

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

int a[MAXN],dp[MAXN],n,ans;
void lis(){
	ans=0;
	for(int i = 0; i<n; i++){
        dp[i] = 1;
        for(int j = 0; j < i; j++){
            if(a[j] < a[i])
                dp[i] = max(dp[i], dp[j] + 1);
        }
        ans = max(dp[i], ans);
    }
}

时间复杂度: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<n; i++){
        int j = lower_bound(g, g+n, arr[i]) - g;
		ans=max(ans,j+1);
        g[j] = arr[i];
    }
    return ans;
}

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]<rk[j];	//比前一半
	int ri=(i+k<=n ? rk[i+k] : -1);
	int rj=(j+k<=n ? rk[j+k] : -1);
	return ri<rj;	//比后一半
}
void getsa(int n,char *str){
	//str应从1开始
	for(int i=1;i<=n;i++)
		sa[i]=i,rk[i]=str[i];	//开始长度为1的rk
	for(int k=1;k<=n;k++){	//k是当前比的后缀长度
		sort(sa+1,sa+1+n,cmp);
		rk2[sa[1]]=1;
		for(int i=2;i<=n;i++)
			rk2[sa[i]]=rk2[sa[i-1]];
		for(inr i=1;i<=n;i++)
			rk[i]=rk2[i];	//更新rk
	}
}
void getht(int n,char *str){
	for(int i=1;i<=n;i++)
		rk[sa[i]]=i;
	int h=0;	//先算出每个后缀的rank
	int h=0;	//前一次算的两个后缀的lcp
	ht[1] = 0;
	for(int i=1;i<=n;i++){
		int j=sa[rk[i]-1];	//i后缀的前一名
		if(h>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]<s[i]) break;
	char tmp=s[i];
	s[i]=s[j],s[j]=tmp;
	sort(s.begin()+i+1,s.end(),cmp);
	return s;
}

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

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]<s[i+1]){
			judge=true;
			break;
		}
	}
	for(j=n-1;j>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)

多源最短路

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<n;k++){
	    	for(int i=0;i<n;i++){
	    		if(m[j][i]==1&&j!=i) for(int k=0;k<n;k++){
					if(m[i][k]) m[j][k]=1;
				}
			}
		}

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)

单源最短路,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<n;i++){
		m[i][i]=0;
	}
}

void dijkstra(int u){
	for(int i=0;i<n;i++){
		dis[i]=m[u][i];
		vis[i]=0;
	}
	vis[u]=1;
	for(int j=0;j<n-1;j++){
		int mn=INF,tmp;
		for(int i=0;i<n;i++){
			if(!vis[i]&&dis[i]<mn){
				mn=dis[i];
				tmp=i;
			}
		}
		vis[tmp]=1;
		for(int i=0;i<n;i++){
			if(m[tmp][i]+dis[tmp]<dis[i]){
				dis[i]=m[tmp][i]+dis[tmp];
			}
		}
	}
}

堆优化,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<int> 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<e[x].size();i++){
        int y=e[x][i];
        if(y==pre)continue;
        dfs(y,x,d+1);
    }
    R[x]=tot;
}

4.拓扑排序

int in[N];
vector<int> out[MAXN];
int topology(){
	queue<int> q;
	//vector<int> ans;
	for(int i=0;i<n;i++){
		if(in[i]==0) q.push(i);
	}
	while(!q.empty()){
		int f=q.front();
		q.pop();
		//ans.push_back(f);
		for(int i=0;i<out[f].size();i++){
			int x=out[f][i];
			in[x]--;
			if(in[x]==0) q.push(x);
		}
	}
	
}

//不方便使用vector时

bool topotopology(){
    for(i=0;i<n;i++){
       for(j=0;j<n;j++){
          if(in[j]==0){
            ans=j;
            vis[cnt++]=j;
            in[j]--;
            break;
           }
        }
        for(j=0;j<n;j++)
            if(out[ans][j]) in[j]--;
    }
    if(cnt==n) return true;
    else return false;
}

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.二分图

最大匹配的匹配边的数目

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

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

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

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

匈牙利算法

求最大匹配数

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 <iostream>
#include <cstring>
#include <cstdio>
 
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<N;i++){
		root[i]=i;
		vis[i]=0;
	}
}

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

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<b) return 1;
	else return 2;
	//return i;
}

10.最小生成树

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

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点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中最小代价边信息,:

此算法可以称为“加边法”,初始最小生成树边数为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<MAXN;i++){
        if(!euler[i])
        for(int j=i;j<MAXN;j+=i){
            if(!euler[j]) euler[j]=j;
            euler[j]=euler[j]/i*(i-1);
        }
    }
}

$$ φ(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.分数规划

给定一系列整数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;
}

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<x<p,则x^2≡1(mod.p)的解为x=1,x=p-1 $$

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.中国剩余定理

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个的盒子,盒子为空。

①方

标签: th矩形电连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台