板子总和+

国庆板子

前言

对拍模板bat

@echo off
:loop
D:/LSNU/codeforces/code/MakeData.exe
D:/LSNU/codeforces/code/A.exe
D:/LSNU/codeforces/code/force.exe
fc AC.txt WA.txt
if not errorlevel 1 goto loop
pause
:end

说明
板子总和+_第1张图片

模板

//#pragma comment(linker,"/STACK:1024000000,1024000000")
//#pragma GCC optimize(2)
//#pragma GCC target ("sse4")
#include
//typedef long long ll;
#define ull         unsigned long long
//#define int       __int128
#define int         long long
#define F           first
#define S           second
#define endl        "\n"//<
#define eps         1e-6
#define base        131
#define lowbit(x)   (x&(-x))
#define db          double
#define PI          acos(-1.0)
#define inf         0x3f3f3f3f
#define MAXN        0x7fffffff
#define INF         0x3f3f3f3f3f3f3f3f
#define ferma(a,b)  pow(a%b,b-2)
#define mod(x)      (x%mod+mod)%mod
#define pb          push_back
#define decimal(x)  cout << fixed << setprecision(x);
#define all(x)      x.begin(),x.end()
#define rall(x)      x.rbegin(),x.rend()
#define memset(a,b) memset(a,b,sizeof(a));
#define IOS         ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
template<typename T> inline T fetch(){T ret;cin >> ret;return ret;}
template<typename T> inline vector<T> fetch_vec(int sz){vector<T> ret(sz);for(auto& it: ret)cin >> it;return ret;}
template<typename T> inline void makeUnique(vector<T>& v){sort(v.begin(), v.end());v.erase(unique(v.begin(), v.end()), v.end());}
template<typename T> inline T max_(T a,T b){if(a>b)return a;return b;}
template<typename T> inline T min_(T a,T b){if(a<b)return a;return b;}
void file()
{
#ifdef ONLINE_JUDGE
#else
    freopen("D:/LSNU/codeforces/duipai/data.txt","r",stdin);
    freopen("D:/LSNU/codeforces/duipai/WA.txt","w",stdout);
#endif
}
signed main()
{
    IOS;
    file();



    return 0;
}

避免万能头文件

#include     //STL通用算法
#include      //STL位集容器
#include 
#include 
#include 
#include 
#include      //复数类
#include 
#include 
#include 
#include 
#include       //STL双端队列容器
#include     //异常处理类
#include 
#include    //STL定义运算函数(代替运算符)
#include 
#include       //STL线性列表容器
#include        //STL 映射容器
#include 
#include       //基本输入/输出支持
#include     //输入/输出系统使用的前置声明
#include 
#include      //基本输入流
#include      //基本输出流
#include       //STL队列容器
#include        //STL 集合容器
#include     //基于字符串的流
#include       //STL堆栈容器    
#include     //标准异常类
#include    //底层输入/输出支持
#include      //字符串类
#include      //STL通用模板类
#include      //STL动态数组容器
#include 
#include 

基础算法

关于加速器问题:
ios::sync_with_stdio(false);
是C++的输入输出流(iostream)是否兼容C的输入输出(stdio)的开关。

因为C++中的std :: cin和std :: cout为了兼容C,保证在代码中同时出现std :: cin和scanf或std :: cout和printf时输出不发生混乱,所以C++用一个流缓冲区来同步C的标准流。通过std :: ios_base :: sync_with_stdio函数可以解除这种同步,让std :: cin和std :: cout不再经过缓冲区,自然就节省了许多时间。

副作用:当设置为false时,cin就不能和scanf,sscanf, getchar, fgets等同时使用。

据说,endl用”\n”代替和cout使用,也可节约时间。
cin.tie(0);
因为std :: cin默认是与std :: cout绑定的,所以每次操作的时候(也就是调用”<<”或者”>>”)都要刷新(调用flush),这样增加了IO的负担,通过tie(nullptr)来解除std :: cin和std :: cout之间的绑定,来降低IO的负担使效率提升。

不AC各种情况

TLE:
1:多组测试案例,输入到文本结束
2:多组测试案例,数组一定要清空
3:1左移移动一定要1LL(交互题死过一次)
MLE:
1:每个函数是否有返回值。(线段树死过一次)

WA:
1:定义变量:注意要一个全局跟一个局部相同,意想不到得错误
2:顶点数一定要开双倍

double
避免卡精度:能用加减乘,绝对不用除法

Codeblocks编译器问题
1:数组越界,任然可以计算,不会报错
2:int函数无返回值,任然可以返回,不会报错。

卡常

1:i++ 换成++i
2:函数尽量手撸的,例如被max过
3:函数用inline
4:理论int比long long快,有的时候有起效。被n3卡过
5:尽量用if,不用? :三目运算符

再不过,,还不明白吗,你的算法烂透了,洗吧脸也没用,奇迹不可能发生了

交互题

前提:自带刷新缓冲流:<< >> endl,本人都已经关闭了,打开其中一个即可。
1:屏蔽cin.tie(0);,保证std::cinsd::cout绑定:<< or >>具有flush效果。
2:格式要求严格,空白符不能多也不能少

高精度

快速乘法,防止爆精度,同时也可以观察是否爆了精度

注意:不能万能的,比如1e15*1e15就暴了,还是注意精度把握和算法的弱点
注意多了一个logn

const int mod=1e15;
int mul(int a,int b)
{
    int res=0;
    while(b)
    {
        if(b&1)
            res=(res+a)%mod;
        a=(a*2)%mod;
        b>>=1;
    }
    return res;
}

快速幂,主要用于是否爆了long long,幂次方很恐怖(CF被高精度坑过一次)

int pow(int a,int b)
{
    int ans=1;
    while(b)
    {
        int te=ans;
        if(b&1)
            ans=ans*a;
        if(te>ans)
        {
            ans=INF;
            break;
        }
        a=a*a;
        b>>=1;
    }
    return ans;
}

快读(一定不要关闭同步流


template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}

快输(高精度,__int128,本地无法编译)

如果编译失败,记得用最新的编译器,最好各种编译器试一遍,CF上C17-64BIT,牛客C14就够了

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

PY

num=int(input())///输入整型,任何大
s=input()//输入字符串
ans=""
n=len(s)//计算字符串长度
for i in range(n):
    if s[i]=='(':
        ans+="**"
    ans+=s[i]
print(eval(ans))///特别函数,好用


基础算法

一般ACM或者笔试题的时间限制是1秒或2秒。
在这种情况下,C++代码中的操作次数控制在 1e7 为最佳。

下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择:

1:n≤30, 指数级别, dfs+剪枝,状态压缩dp
2:n≤1e2 => O(n3),floyd,dp
3:n≤1e3 => O(n2),O(n2logn),dp,二分
4:n≤1e4 => O(n∗√n),块状链表
5:n≤1e5 => O(nlogn) => 各种sort,线段树、树状数组、set/map、heap、dijkstra+heap、spfa、求凸包、求半平面交、二分
6:n≤1e6 => O(n)O, 以及常数较小的 O(nlogn) 算法 => hash、双指针扫描、并查集,kmp、AC自动机,常数比较小的 O(nlogn)O(nlogn) 的做法:sort、树状数组、heap、dijkstra、spfa
7:n≤1e7 => O(n),双指针扫描、kmp、AC自动机、线性筛素数
8:n≤1e9 => O(√n),判断质数
9:n≤1e18 => O(logn),最大公约数

1MB=1024KB
1KB=1024B
1B=8bit

Int =48bit
Int a[1e6]=1e6
48bit=1e6b4=1e3kb4=1mb4

位运算

a+b=a⊕b+2∗ (a&b)

bitset用法

定义:

#include 
bitset<4> a;    // 长度为4,全为零
bitset<4> a("1001");    // 长度为4,1001

用法
a.size()    // 返回位数
a.count()   // 返回1的个数
a.any()     // 返回是否有1
a.none()    // 返回是否没有1
a.set()     // 重置为1
a.set(pos)
a.reset()   // 重置为0
a.reset(pos)
a.set(pos)  // 将pos下标设为1,(从低位开始计算)
a.flip()    // 全部取反
a.flip(pos)
a.to_ulong()    // 转化为unsigned long
a.to_ullong()   // 转化为unsigned longlong
a.to_string()   // 转化为string 

单调栈

维护某一个点比它小于(等于)的第一个数
将一个元素插入单调栈时,为了维护栈的单调性,需要在保证将该元素插入到栈顶后整个栈满足单调性的前提下弹出最少的元素。
例如,栈中自顶向下的元素为1,3,5,10,30,50 ,插入元素 20 时为了保证单调性需要依次弹出元素 1,3,5,10,操作后栈变为 20,30,50 。

insert x
while !sta.empty() && sta.top()<x
    sta.pop()
sta.push(x)

单调队列

维护区间最值

set平衡树二分

Set二分,只能得到迭代器,不能相减获取索引,毕竟不是线性结构

    multiset<int>se;
    auto it=se.lower_bound(it.l);///一定要用上面的
    auto jt=lower_bound(se.begin(),se.end(),it.l);///T过
struct node
{
    int value,pos;
    bool operator<(const node &b)const
    {
        return value<b.value;
    }
};
set<node>se;
signed main()
{
    IOS;
    // file();
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int x,value=INF,pos;
        cin>>x;
        auto it=se.lower_bound({x,0});
        if(it!=se.end())
            value=(*it).value-x,pos=(*it).pos;
        if(it!=se.begin())
        {
            it--;
            int num1=x-(*it).value,num2=(*it).pos;
            if(num1<=value)
                value=num1,pos=num2;
        }
        if(i!=1)
            cout<<value<<" "<<pos<<endl;
        se.insert({x,i});
    }
    return 0;
}

三分

三分的性质:一个区间最多一个递增区间段,一个区间最多一个递减区间段
板子总和+_第2张图片

    int l=0,r=1e9;
    while(r-l>10)
    {
        int midl=l+(r-l)/3;
        int midr=r-(r-l)/3;
        int numl=check(midl),numr=check(midr);
        if(numl>numr)
            l=midl;
        else
            r=midr;
    }
    int ans=INF;
    for(int i=l;i<=r;i++)
        ans=min(ans,check(i));
    cout<<ans<<endl;

数被整除

板子总和+_第3张图片

重载无序STL容器

本质非基本类型,不存在hash函数,无法进行hash

class Myclass
{
public:
    int first;
    int second;

    // 重载等号,判断两个Myclass类型的变量是否相等
    bool operator== (const Myclass &other) const
    {
        return first == other.first && second == other.second;
    }
};

// 实现Myclass类的hash函数
namespace std
{
    template <>
    struct hash<Myclass>
    {
        size_t operator()(const Myclass &k) const
        {
            ///自己手写hash方法
            return k.first+k.second;
        }
    };
}

List用法,双向链表

list 由双向链表(doubly linked list)实现而成,元素也存放在堆中,每个元素都是放在一块内存中,他的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随机存取变得非常没有效率,因此它没有提供 [] 操作符的重载。但是由于链表的特点,它可以很有效率的支持任意地方的插入和删除操作。
优势:删除与添加常数操作,相对vector是线性得。
1 容量函数
容器大小:lst.size();
容器最大容量:lst.max_size();
更改容器大小:lst.resize();
容器判空:lst.empty();

2 添加函数
头部添加元素:lst.push_front(const T& x);
末尾添加元素:lst.push_back(const T& x);
任意位置插入一个元素:lst.insert(iterator it, const T& x);
任意位置插入 n 个相同元素:lst.insert(iterator it, int n, const T& x);
插入另一个向量的 [forst,last] 间的数据:lst.insert(iterator it, iterator first, iterator last);

3 删除函数
头部删除元素:lst.pop_front();
末尾删除元素:lst.pop_back();
任意位置删除一个元素:lst.erase(iterator it);
删除 [first,last] 之间的元素:lst.erase(iterator first, iterator last);
清空所有元素:lst.clear();

4 其他函数
多个元素赋值:lst.assign(int nSize, const T& x); // 类似于初始化时用数组进行赋值
交换两个同类型容器的元素:swap(list&, list&); 或 lst.swap(list&);
合并两个列表的元素(默认升序排列):lst.merge();
在任意位置拼接入另一个list:lst.splice(iterator it, list&);
删除容器中相邻的重复元素:lst.unique();

螺旋矩阵n2

long long index(long long y, long long x, long long n)
{
    long long mid = (n + 1) / 2;
    long long p = max(abs(x - mid), abs(y - mid));
    long long ans = n * n - (1 + p) * p * 4;
    long long sx = mid + p, sy = mid + p;
    if (x == sx && y == sy) {
        return ans;
    } else {
        if (y == sy || x == sx - 2 * p) {
            return ans + abs(x - sx) + abs(y - sy);
        } else {
            return ans + 8 * p - abs(x - sx) - abs(y - sy);
        }
    }
}

开根号

n m \sqrt[m]{n} mn

pow(n,1.0/m)

退火模拟

随机概率算法,我觉得玄学,朋友说99%过,反正默认了吧

void SA()
{
    double T=3000;
    double D=0.99551748;
    double cur_ans=ans;
    while(T>1e-9)
    {
        int l=rand()%n,r=rand()%n;
        swap(a[l],a[r]);

        double now=cal();
        ans=min(ans,now);
        double dif=now-cur_ans;
        if(dif<0)///当前ans不断逼近minimize
            cur_ans=now;
        else if(exp(-dif/T)*RAND_MAX>rand())///指数增长接受更bad cur_ans
            cur_ans=now;
        else
            swap(a[l],a[r]);
        T*=D;///降温
    }
}

卡时,骗分神器

void fire()
{
    clock_t s;
    s=clock();
    double usedtime=0;
    while(usedtime<0.75)
    {
        cal();
        usedtime=double(clock()-s)/CLK_TCK;
       // cout<
       //CLK_TCK=1e6
    }
}

数论

欧几里得算法证明 O( logn )

gcd

求A和B的最大公约数
假设X为最大公约数
间接条件:X | A X | B

假设A >= B
建立方程式A + KB = C = A % B
∵ X | A X | KB
∴ X | A + KB
∴ X | C
∴ GCD(A,B)== GCD(B, A%B)
然后我们就可以不断的往下辗转相除

求GCD(B,C)
B和C最大公约数也是X
何时是个头
GCD(KX , X )其实这里能被整除,就已经知道求到GCD了
再往下
GCD (X , 0)

扩展欧几里得 O( logn )

exgcd

前提条件:d==gcd(a,b)
问题:ax+by=d,求x和y的通解

那么我们先建立一个方程组
A:ax1 + by1= d ==gcd(a,b)
B:bx2 + a%by2= d ==gcd(b,a%b)

B方程式展开:bx2 + ( a - a / b * b ) * y2=d
括号打开再合并:ay2 + b * ( x2 - a / b * y2 ) = d
所以:
x1=y2
y1=x2 - a / b * y2
不断递归下去到头

ax + 0*y = gcd(a,0);
所以x=1,y=0;

ax+by=c
如果 d | c 一定有解,否则一定无解

注意1:
exgcd求得是c == d的x,y
x= x * c/d, y = y * c/d;
这里才是正式的ax+by=c的x与y的一组解。

注意2:
exgcd求到得x,可能为负数,所以不断得加最小系数k1和减去k2
a * ( x + k1 )+ b * ( y - k2 )=d
展开后,保证a * k1= =b * k2 = = lcm(a,b)
所以 k1= lcm(a,b) / a , k2 = lcm(a,b)/b;
然后加成正数就行,但是不是最小正数,x>=k1,就得不断减去k1;

第一步:先求exgcd的,x,y
第二步:看是否需要加系数k1,k2

int exgcd(int a,int b,int &x1,int &y1)///求到c==gcd的一个x与y的解
{
    if(b==0)
    {
        x1=1,y1=0;
        return a;
    }
    int x2,y2;
    int gcd=exgcd(b,a%b,x2,y2);
    x1=y2,y1=x2-a/b*y2;
    return gcd;
}
x=(x%k1+k1)%k1;///这一步才是最小的正整数x,x>=k1,要降,x<0,要增加。

多项式

在数学中,由若干个单项式相加组成的代数式叫做多项式(若有减法:减一个数等于加上它的相反数)。多项式中的每个单项式叫做多项式的项,这些单项式中的最高项次数,就是这个多项式的次数。其中多项式中不含字母的项叫做常数项。

本原多项式

板子总和+_第4张图片
高斯引理:本原多项式的乘积还是本原多项式。
(证明就算了吧,我不配)

组合数

n个不同物品的排列数A(n,n)=A!(原理:加法原理+乘法原理)
公式:A(n,n)=n*(n-1)(n-2)···(n-n+1)=A!
变形一下:
在这里插入图片描述

从n个不同物品取m个的排列数:A(n,m)
公式:A(n,m)=n*(n-1)*(n-2)···(n-m+1);

从n个不同物品取m个的组合数:C(n,m)
公式:C(n,m)=A(n,m)/A(m,m)=n!/(n-m)!/m!;
这里提一下A(n,m)=P(n,m),只不过P是老版教材的用法
在这里插入图片描述
C n m = n ! m ! ∗ ( n − m ) ! C^m_n=\frac{n!}{m!*(n-m)!} Cnm=m!(nm)!n!
递推公式:
C n m = C n − 1 m + C n − 1 m − 1 C^m_n=C^m_{n-1}+C^{m-1}_{n-1} Cnm=Cn1m+Cn1m1
性质1:
C n m = C n n − m C^m_n=C^{n-m}_n Cnm=Cnnm
性质2:
C n + m + 1 m = ∑ i = 0 m C n + i i C^m_{n+m+1}=\sum^m_{i=0}{C^i_{n+i}} Cn+m+1m=i=0mCn+ii
性质3:
C n m ∗ C m r = C n r ∗ C n − r m − r C^m_n *C^r_m=C^{r}_n *C^{m-r}_{n-r} CnmCmr=CnrCnrmr
性质4:(二项式定理)
∑ i = 0 n C n i = 2 n \sum^n_{i=0}{C^i_n}=2^n i=0nCni=2n
性质5:(n为奇数,产生偶数个)
C n 0 − C n 1 + C n 2 − C n 3 … … C n n = 0 C^0_n-C^1_n+C^2_n-C^3_n……C^n_n=0 Cn0Cn1+Cn2Cn3Cnn=0
性质6:(根据性质4和性质5,并且m为奇数)
C n 0 + C n 2 + C n 4 … … = C n 1 + C n 3 + C n 5 … … = 2 n − 1 C^0_n+C^2_n+C^4_n……=C^1_n+C^3_n+C^5_n……=2^{n-1} Cn0+Cn2+Cn4=Cn1+Cn3+Cn5=2n1
性质7:
C n + m r = C n 0 ∗ C m r + C n 1 ∗ C m r − 1 … … + C n r ∗ C m 0 C^r_{n+m}=C^0_n*C^r_m+C^1_n*C^{r-1}_m……+C^r_n*C^0_m Cn+mr=Cn0Cmr+Cn1Cmr1+CnrCm0
性质8:
m ∗ C n m = n ∗ C n − 1 m − 1 m*C^m_n=n*C^{m-1}_{n-1} mCnm=nCn1m1
性质9:
∑ i = 1 n C n i ∗ i = n ∗ 2 n − 1 \sum^n_{i=1}{C^i_n*i}=n*2^{n-1} i=1nCnii=n2n1
性质10:
∑ i = 1 n C n i ∗ i 2 = n ∗ ( n + 1 ) ∗ 2 n − 2 \sum^n_{i=1}{C^i_n*i^2}=n*(n+1)*2^{n-2} i=1nCnii2=n(n+1)2n2
性质11:
∑ i = 0 n ( C n i ) 2 = C 2 n n \sum^n_{i=0}{(C^i_n)^2}=C^n_{2n} i=0n(Cni)2=C2nn
卢卡斯定理
C n m % p = C n / p m / p ∗ C n % p m % p % p , 无 脑 递 归 就 行 C^m_n \%p=C^{m/p}_{n/p}*C^{m\%p}_{n\%p} \%p,无脑递归就行 Cnm%p=Cn/pm/pCn%pm%p%p

杨辉三角——数据n<=1e3,时间空间复杂度O(1e6)

#define int long long
const int Clen=2e3+5;
const int mod=998244353;
int C[Clen][Clen];//C[总范围][小范围]
void getC()//数据要预处理
{
    for(int i=0;i<Clen;i++)
    {
        for(int j=0;j<=i;j++)
        {
            if(j==0||j==i)
                C[i][j]=1;
            else
                C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
        }
    }
}

LUCAS定理(留一手,大杀器)

Lucas定理是用于处理特大组合数取模的定理
通常用于解决阶乘无法解决的问题。
板子总和+_第5张图片

int Lucas(int n, int m)
{
   if(m==0)
       return 1;
   return C(n%mod,m%mod)*Lucas(n/mod,m/mod)%mod;
}

费马小定理——n<=1e6

(乘法逆元+快速幂+阶乘预处理)
阶乘能处理的范围,乘法逆元就能搞定,解决了除法求模问题

struct  Combinatorics
{
    vector<int>A;
     Combinatorics(int n)
    {
        A.resize(n,1);
        for(int i=1;i<n;i++)
            A[i]=A[i-1]*i%mod;
    }
    int pow(int a,int b)
    {
        int ans=1;
        while(b)
        {
            if(b&1)
                ans=ans*a%mod;
            a=a*a%mod;
            b>>=1;
        }
        return ans;
    }
    int C(int n,int m)
    {
        if(n<m|m<0)
            return 0;
        int ans=A[n]*ferma(A[n-m],mod)%mod*ferma(A[m],mod)%mod;
        return ans;
    }
};

插空法(处理不相邻问题)

插空法就是对于解决 某几个元素要求不相邻 的问题时,先将其他元素排好,再将所指定的不相邻的元素插入它们的间隙或两端位置。首要特点就是不相邻。

例题1:
把1,2,3,4,5组成没有重复数字且数字 1,2不相邻的五位数,则所有不同排法有多少种?
解析:本题直接解答较为麻烦,因为可先将 3,4,5三个元素排定,共有A(3,3)种排法,然后再将 1,2插入四个空位共有A(4,2)种排法,故由乘法原理得,所有不同的五位数有A(3,3)*A(4,2)=72种。

捆绑法(处理相邻问题)

所谓捆绑法,指在解决对于某几个元素要求相邻问题时,先整体考虑,将相邻元素视作一个整体参与排序,然后再单独考虑这个整体内部各元素间顺序。注意:其首要特点是相邻,其次捆绑法一般都应用在不同物体的排序问题中。
例题3:
6个不同的球放到5个不同的盒子中,要求每个盒子至少放一个球,一共有多少种方法?
解答:根据题目要求,则其中一个盒子必须得放 2 个,其他每个盒子放 1 个球,所以从 6 个球中挑出 2 个球看成一个整体,则有C(6,2),这个整体和剩下 4 个球放入 5 个盒子里,则有A(5,5)。方案数:C(6,2)*A(5,5)=1800。

隔板法(处理相同元素+不同分组每组至少存在Si个)

基本题型
基本题型为:n个相同元素,不同个m组,每组至少有一个元素;则只需在 n 个元素的n-1 个间隙中放置 m-1 块隔板把它隔成 m 份,求共有多少种不同方法?

其解题思路为:将 n 个相同的元素排成一行, n 个元素之间出现了( n-1 )个空档,现在我们用( m-1 )个 “档板 ”插入( n-1 )个空档中,就把 n 个元素隔成有序的 m 份,每个组依次按组序号分到对应位置的几个元素(可能是 1 个、2 个、 3 个、 4 个、 ….),这样不同的插入办法就对应着 n 个相同的元素分到 m 组的一种分法,这种借助于这样的虚拟 “档板 ”分配元素的方法称之为插板法。

例题4:
共有 10 完全相同的球分到 7 个班里,每个班至少要分到一个球,问有几种不同分法?
解析:我们可以将 10 个相同的球排成一行, 10 个球之间出现了 9 个空隙,现在我们用 6 个档板 ”插入这 9个空隙中,就 “把 10 个球隔成有序的 7 份,每个班级依次按班级序号分到对应位置的几个球(可能是 1 个、2 个、 3 个、 4 个),这样,借助于虚拟 “档板 ”就可以把 10 个球分到了 7 个班中。
基本题型的变形
(1)变形1:有 n 个相同的元素,要求分到 m 组中,问有多少种不同的分法?
解题思路:这种问题是允许有些组中分到的元素为 “0”,也就是组中可以为空的。对于这样的题,我们就首先将每组都填上 1 个,这样所要元素总数就 m 个,问题也就是转变成将( n+m )个元素分到 m 组,并且每组至少分到一个的问题,也就可以用插板法来解决。

例题:有 8 个相同的球放到三个不同的盒子里,共有C(7,2)种不同方法 。

解答:题目允许盒子有空,则需要每个组添加 1 个,则球的总数为 8+3 ×1=11,此题就有 C(10 ,2) =45(种)分法了。

(2)变形2:有 n 个相同的元素,要求分到 m 组,要求各组中分到的元素至少某个确定值 S( s>1,且每组的 s值可以不同) ,问有多少种不同的分法?

解题思路: 这种问题是要求组中分到的元素不能少某个确定值 s,各组分到的不是至少为一个了。 对于这样的题,我们就首先将各组都填满,即各组就填上对应的确定值 s 那么多个,这样就满足了题目中要求的最起码的条件,之后我们再分剩下的球。这样这个问题就转变为上面提到的变形1的问题了,也就可以用插板法来解决。

排列组合问题编辑
排列组合问题从解法看,大致有以下几种:
(1)有附加条件的排列组合问题,大多需要分类讨论的方法,注意分类时应不重不漏;
(2)排列与组合的混合型问题,用分类加法或分步乘法计数原理解决;
(3)元素相邻,可以看作是一个整体的方法;
(4)元素不相邻,可以利用插空法;
(5)间接法,把不符合条件的排列与组合剔除掉;
(6)穷举法,把不符合条件的所有排列或组合一一写出来

埃式筛

埃式筛----O(nlog log n)
核心:枚举每个数所有因子

const int N=6.5e4+5;
int a[N];
bool prime[N];///判断该位置是否为素数(素数==ture 非素数=false)
void get_prime()
{
    for(int i=2; i<N; i++) ///埃式筛原理
    {
        if(a[i])
            continue;
        prime[i]=true;
        for(int j=i; j<N; j+=i)
            a[j]=true;
    }
}

欧拉筛----O(n)

核心:
确定为合数:根据最小质因子确定的(仅仅确定一次)——O(2n)
第一个n遍历,第二个n是确定合数
避免确定合数两次,第二次i%it==0得break;
举例:8以内质数存在2 3 5,我们只需要确定2
8为合数,3*8不用,因为不是由最小合数确定的。后面,就更不用举例了,最小合数都是2了,

bool a[MAN];//该位置是否为素数
int prime[MAN];//存的值全为素数
int digit=0;
void get_prime()
{
    for(int i=2;i<MAN;i++)
    {
        if(a[i]==0)
            prime[++digit]=i;
        for(int j=1;prime[j]*i<MAN;j++)
        {
            a[i*prime[j]]=1;
            if(i%prime[j]==0)
                break;
        }
    }
}

数论分块

∑ i = 1 n n / i \sum_{i=1}^n n/i i=1nn/i
一般问题:n=1e12,O( 2 n 2\sqrt n 2n

    long long sum=0,n;
    cin>>n;
    for(int l=1,r;l<=n;l=r+1)
    {
        r=n/(n/l);
        sum+=(r-l+1)*(n/l);
    }
    cout<<sum<<endl;

矩阵快速幂

板子总和+_第6张图片
斐波那契数列前n项和
f [ n ] = f [ n − 1 ] + f [ n − 2 ] f[n]=f[n-1]+f[n-2] f[n]=f[n1]+f[n2]
p r e [ n ] = p r e [ n − 1 ] + f [ n ] pre[n]=pre[n-1]+f[n] pre[n]=pre[n1]+f[n]
p r e [ n ] = f [ n + 2 ] − 1 pre[n]=f[n+2]-1 pre[n]=f[n+2]1

1:单位矩阵
2:初始矩阵

const int mod=998244353;
struct matrix
{
    int G[2][2];
};
matrix mul(matrix a,matrix b)
{
    matrix ans;
    memset(ans.G,0);
    for(int i=0; i<2; i++)
        for(int j=0; j<2; j++)
            for(int k=0; k<2; k++)
                ans.G[i][j]=(ans.G[i][j]+a.G[i][k]*b.G[k][j]%mod)%mod;
    return ans;
}
matrix pow(matrix a,int b)
{
    matrix ans;
    ans.G[0][0]=1,ans.G[0][1]=0;
    ans.G[1][0]=0,ans.G[1][1]=1;
    while(b)
    {
        if(b&1)
            ans=mul(ans,a);
        a=mul(a,a);
        b>>=1;
    }
    return ans;
}

SG函数

const int N=1e6+5;
bool vis[N];
void sg(int n)
{
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=10;j++)///枚举先手可进行的
        {
            ///如果先手后,剩余的一定为fail,则true
            if((1<<j)<=i&&vis[i-(1<<j)]==0)
            {
                vis[i]=true;
                break;
            }
        }
    }
    for(int i=1;i<=n;i++)
        cout<<vis[i]<<" ";
    cout<<endl;
}

高精度

大数整型+ - * % 板子随便用

string delete_leading0(string a)//处理前导0
{
    while(a.size()>1&&a[0]=='0')
        a.erase(a.begin());
    return a;
}
int cmp(string a,string b)//字符串比较大小
{
    a=delete_leading0(a);
    b=delete_leading0(b);
    int len_a=a.size(),len_b=b.size();
    if(len_a<len_b||(len_a==len_b&&a<b))
        return -1;
    if(len_a>len_b||(len_a==len_b&&a>b))
        return 1;
    return 0;
}
string add(string a,string b)//大数加法
{
    a=delete_leading0(a);
    b=delete_leading0(b);
    if(a.size()<b.size())
        swap(a,b);
    string ans;
    reverse(a.begin(),a.end());
    reverse(b.begin(),b.end());
    int len_a=a.size(),len_b=b.size(),sum=0;
    for(int i=0; i<len_a; i++)
    {
        if(i<len_b)
            sum+=a[i]+b[i]-'0'*2;
        else
            sum+=a[i]-'0';
        ans.push_back(sum%10+'0');
        sum/=10;
    }
    if(sum)
        ans.push_back(sum+'0');
    reverse(ans.begin(),ans.end());
    return ans;
}
string sub(string a,string b)//大数减法
{
    int flag=0;
    if(cmp(a,b)<0)
        swap(a,b),flag=1;
    for(int i=a.size()-1,j=b.size()-1; i>=0; i--,j--)
    {
        a[i]=a[i]-(j>=0?b[j]-'0':0);
        if(a[i]<'0')
        {
            a[i]+=10;
            a[i-1]--;
        }
    }
    while(a.size()>1&&a[0]=='0')
        a.erase(a.begin());
    if(flag)
    {
        reverse(a.begin(),a.end());
        a.push_back('-');
        reverse(a.begin(),a.end());
    }
    return a;
}
int bignum_mod(string a,int mod)//大数求余
{
    int sum=0,len=a.size();
    for(int i=0; i<len; i++)
        sum=(sum*10+a[i]-'0')%mod;
    return sum;
}
string mul(string a,string b)//大数乘法
{
    a=delete_leading0(a);
    b=delete_leading0(b);
    int la=a.size(),lb=b.size();
    vector<int> ans(la+lb+1);
    string s;
    for(int i=0; i<la; i++)
        for(int j=0; j<lb; j++)
            ans[i+j+1]+=(a[i]-'0')*(b[j]-'0');
    for(int i=la+lb-1; i>0; i--)
    {
        ans[i-1]+=ans[i]/10;
        ans[i]%=10;
    }
    for(int i=0; i<=la+lb-1; i++)
    {
        if(i==0&&ans[i]>0)
            s.push_back(ans[i]+'0');
        if(i)
            s.push_back(ans[i]+'0');
    }
    s=delete_leading0(s);
    return s;
}

备用运算符号重载

struct Big
{
    string str;
    Big(string _str="0")
    {
        str=_str;
    }
    void input()
    {
        cin>>str;
    }
    void output()
    {
        cout<<str<<endl;
    }
    bool operator==(const Big &b)const
    {
        return cmp(str,b.str)?false:true;
    }
    bool operator<(const Big &b)const
    {
        return cmp(str,b.str)<0?true:false;
    }
    bool operator>(const Big &b)const
    {
        return cmp(str,b.str)>0?true:false;
    }
    Big operator+(const Big &b)const
    {
        return add(str,b.str);
    }
    Big operator*(const Big &b)const
    {
        return mul(str,b.str);
    }
    Big operator-(const Big &b)const
    {
        return sub(str,b.str);
    }
};

欧拉函数

巨佬博客学习
单点筛[1,n]与n互质的个数
O ( n ) ϕ ( n ) O(\sqrt{n})\phi{(n)} On ϕ(n)

int get_phi(int  x){
    int res=x;
    for(int i=2;i*i<=x;i++)
    {
        if(x%i==0){
            res=res-res/i;
            while(x%i==0) x/=i;
        }
    }
    if(x!=1) res=res-res/x;
    return res;
}

埃式筛欧拉函数 O ( n l o g n ) O(nlogn) Onlogn

struct aishi_phi///nlogn
{
    vector<int>p;
    aishi_phi(int n)
    {
        p.resize(n);
        for(int i=1;i<n;i++)
            p[i]=i;
        for(int i=2;i<n;i++)
        {
            if(p[i]==i)
            {
                for(int j=i;j<n;j+=i)
                    p[j]=p[j]/i*(i-1);
            }
        }
    }
};

欧拉筛欧拉函数 O ( n ) O(n) On

struct euler_phi
{
    vector<int>p,prime,vis;
    euler_phi(int n)
    {
        p.resize(n+1);
        vis.resize(n+1);
        p[1]=1;
        for(int i=2;i<=n;i++)
        {
            if(!vis[i])
            {
                prime.pb(i);
                p[i]=i-1;
            }
            for(auto it:prime)
            {
                if(it*i>n)
                    break;
                vis[it*i]=true;
                if(i%it==0)
                {
                    p[i*it]=p[i]*it;
                    break;
                }
                else
                    p[i*it]=p[i]*p[it];
            }
        }
    }
};

莫比乌斯

∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) = = p r i m e ] \sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)==prime] i=1nj=1m[gcd(i,j)==prime] max(n,m)<=1e7
init O ( n ) O(n) On
solve O ( n ) O(\sqrt{n}) O(n )
如果 ∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) = = 1 ] \sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)==1] i=1nj=1m[gcd(i,j)==1]

/*for (int i=1; i<=cnt; i++)
        for (int j=1; prime[i]*j
    for (int i=1; i<N; i++)
        sum[i]=sum[i-1]+mu[i];
const int N=1e7+5;
int mu[N];
int flag[N],prime[N],cnt,f[N],sum[N];
void init()
{
    mu[1]=1;
    for (int i=2; i<N; i++)
    {
        if (!flag[i])
            prime[++cnt]=i,mu[i]=-1;
        for (int j=1; j<=cnt&&i*prime[j]<N; j++)
        {
            flag[i*prime[j]]=1;
            if (i%prime[j]==0)
                break;
            mu[i*prime[j]]=-mu[i];
        }
    }
    for (int i=1; i<=cnt; i++)
        for (int j=1; prime[i]*j<N; j++)
            f[j*prime[i]]+=mu[j];
    for (int i=1; i<N; i++)
        sum[i]=sum[i-1]+f[i];
}
int solve(int a,int b)
{
    int ans=a*b;
    if (a>b)
        swap(a,b);
    for (int l=1,r=0; l<=a; l=r+1)
    {
        r=min(a/(a/l),b/(b/l));
        ans-=(sum[r]-sum[l-1])*(a/l)*(b/l);
    }
    return ans;
}

min_25筛

真正用法:求解积性函数 f ( x ) f(x) f(x)前缀和。
挖个坑,以后再补

时间复杂度:O( n 3 4 l o g n \frac{n^{\frac{3}{4}}}{logn} lognn43)
非线性筛1e10质数前缀和, p r i m e prime prime 数组存1e10数组所有质数。0.05s计算出来,记得每次使用初始化

typedef long long ll;
struct Min25 {

    static const int N=1e6+10;
    ll prime[N], id1[N], id2[N], flag[N], ncnt, m;

    ll g[N], sum[N], a[N], T, n;

    inline int ID(ll x) {
        return x <= T ? id1[x] : id2[n / x];
    }

    inline ll calc(ll x) {
        return x * (x + 1) / 2 - 1;
    }

    inline ll f(ll x) {
        return x;
    }
    inline void Init() {
        memset(prime, 0, sizeof(prime));
        memset(id1, 0, sizeof(id1));
        memset(id2, 0, sizeof(id2));
        memset(flag, 0, sizeof(flag));
        memset(g, 0, sizeof(g));
        memset(sum, 0, sizeof(sum));
        memset(a, 0, sizeof(a));
        ncnt = m = T = n = 0;
    }
    inline void init() {
        T = sqrt(n + 0.5);
        for (int i = 2; i <= T; i++) {
            if (!flag[i]) prime[++ncnt] = i, sum[ncnt] = sum[ncnt - 1] + i;
            for (int j = 1; j <= ncnt && i * prime[j] <= T; j++) {
                flag[i * prime[j]] = 1;
                if (i % prime[j] == 0) break;
            }
        }
        for (ll l = 1; l <= n; l = n / (n / l) + 1) {
            a[++m] = n / l;
            if (a[m] <= T) id1[a[m]] = m; else id2[n / a[m]] = m;
            g[m] = calc(a[m]);
        }
        for (int i = 1; i <= ncnt; i++)
            for (int j = 1; j <= m && (ll)prime[i] * prime[i] <= a[j]; j++)
                g[j] = g[j] - (ll)prime[i] * (g[ID(a[j] / prime[i])] - sum[i - 1]);
    }

    inline ll solve(ll x) {
        if (x <= 1) return x;
        return n = x, init(), g[ID(n)];
    }

}a;

高斯消元(处理线性方程组)

const int N=1e2+5;
double a[N][N],ans[N];
signed main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n+1;++j)
            cin>>a[i][j];
    for(int i=1;i<=n;++i) //枚举列(项)
    {
        int maxn=i;
        for(int j=i+1;j<=n;++j) //选出该列最大系数
            if(fabs(a[j][i])>fabs(a[maxn][i]))
                maxn=j;
        for(int j=1;j<=n+1;++j) //交换
            swap(a[i][j],a[maxn][j]);
        if(!a[i][i])//最大值等于0则说明该列都为0,肯定无解
        {
            puts("No Solution");
            return 0;
        }
        for(int j=1;j<=n;++j) //每一项都减去一个数(即加减消元)
        {
            if(i==j)
                continue;
            double temp=a[j][i]/a[i][i];
            for(int k=i+1; k<=n+1; ++k)
                a[j][k]-=a[i][k]*temp;
        }
    }
    decimal(2);
    for(int i=1;i<=n;i++)
    {
        ans[i]=a[i][n+1]/a[i][i];
        cout<<ans[i]<<" \n"[i==n];
    }
    return 0;
}

数据结构

树状数组

单点修改-区间修改

注意1:记得空间初始化为n就可以了,内部自动开辟n+1空间

struct BIT
{
    int n;
    vector<int>vec;
    BIT(int len=0)
    {
        n=len;
        vec.resize(n+1,0);
    }
    int pre(int x)
    {
        int sum=0;
        while(x)
            sum+=vec[x] , x-=lowbit(x);
        return sum;
    }
    void add(int x,int v)
    {
        while(x<=n)
            vec[x]+=v , x+=lowbit(x);
    }
    int segment_sum(int l,int r)
    {
        return pre(r)-pre(l-1);
    }
}; 

区间修改_单点查询(查询的单点增量)

struct BIT
{
    int n;
    vector<int>vec;
    BIT(int len=0)
    {
        n=len;
        vec.resize(n+1);
    }
    int pre(int pos)
    {
        int sum=0;
        while(pos)
            sum+=vec[pos],pos-=lowbit(pos);
        return sum;
    }
    void add(int pos,int value)
    {
        while(pos<=n)
            vec[pos]+=value,pos+=lowbit(pos);
    }
    void range_add(int l,int r,int value)
    {
        add(l,value),add(r+1,-value);
    }
};

板子总和+_第7张图片

区间修改+区间查询

struct Bit
{
    vector<int>sum1,sum2;
    int n;
    Bit(int x=0)
    {
        n=x;
        sum1.resize(n+1);
        sum2.resize(n+1);
    }
    void add(int p,int x)
    {
        for(int i=p;i<=n;i+=lowbit(i))
            sum1[i]+=x,sum2[i]+=x*p;
    }
    void range_add(int l,int r,int x)
    {
        add(l,x),add(r+1,-x);
    }
    int ask(int p)
    {
        int res=0;
        for(int i=p;i;i-=lowbit(i))
            res+=(p+1)*sum1[i]-sum2[i];
        return res;
    }
    int range_ask(int l,int r)
    {
        return ask(r)-ask(l-1);
    }
}; 

在这里插入图片描述
在这里插入图片描述

sum1[i]=d[i] ,sum2[i]=d[i]∗i

线段树

单点修改

注意1:add(0,0,n,x,v),以后起点都更改为0了,方便算pos=0的位置
注意2:sum(0,0,n,x,y)区间和[x,y]
注意3:记得空间初始化为n就可以了,内部自动开辟4n空间
注意4:求和,求max/min,修改up跟res,还有如果l==r,s[p]如何更新

struct SegmentTree
{
    vector<int>vec;
    SegmentTree(int n=0){vec.resize(n<<2,inf);sum(0,0,n,0,0);}
    void up(int p){vec[p]=min(vec[p*2+1],vec[p*2+2]);}
    void add(int p,int l,int r,int x,int v)
    {
        if(l==r)
        {
            vec[p]=v;
            return ;
        }
        int mid=(l+r)>>1;
        if(x<=mid)
            add(p*2+1,l,mid,x,v);
        else
            add(p*2+2,mid+1,r,x,v);
        up(p);
    }
    int sum(int p,int l,int r,int x,int y)
    {
        if(x<=l&&r<=y)
            return vec[p];
        int mid=(l+r)>>1,res=inf;
        if(x<=mid)
            res=min(res,sum(p*2+1,l,mid,x,y));
        if(mid<y)
            res=min(res,sum(p*2+2,mid+1,r,x,y));
        return res;
    }
};

区间更新

struct SegmentTree
{
    vector<int>s,col;
    SegmentTree(int x)
    {
        s.resize(x*4);
        col.resize(x*4);
    }
    void up(int p)
    {
        s[p]=max(s[p*2],s[p*2+1]);
    }
    void down(int p,int l,int r)
    {
        if(col[p])
        {
           /// int mid=(l+r)/2;
            s[p*2]=col[p];
            s[p*2+1]=col[p];
            col[p*2]=col[p];
            col[p*2+1]=col[p];
            col[p]=0;
        }
    }
    void modify(int p,int l,int r,int x,int y,int v)
    {
        if(x<=l&&r<=y)
        {
            s[p]=v;
            col[p]=v;
            return ;
        }
        down(p,l,r);
        int mid=(l+r)/2;
        if(x<=mid)
            modify(p*2,l,mid,x,y,v);
        if(y>mid)
            modify(p*2+1,mid+1,r,x,y,v);
        up(p);
    }
    int query(int p,int l,int r,int x,int y)
    {
        if(x<=l&&r<=y)
            return s[p];
        down(p,l,r);
        int mid=(l+r)/2,res=0;
        if(x<=mid)
            res=max(res,query(p*2,l,mid,x,y));
        if(y>mid)
            res=max(res,query(p*2+1,mid+1,r,x,y));
        return res;
    }
}; 

板子总和+_第8张图片

ST(nlogn

一种可重复贡献的数据结构,满足结合律。
类似问题:区间最值,区间按位与,区间按位或

倍增RMQ
第一步:构造RMQ,变量为n。
第二步:记得ST表值初始化为inf/-inf,以及2^0倍赋值和转移方程的max/min修改

struct RMQ
{
    vector<vector<int> >ST;
    RMQ(int n=0)
    {
        ST.resize(n+1,vector<int>(21,inf));
        for(int i=1;i<=n;i++)
            ST[i][0]=pre[i];
        for(int i=1;(1<<i)<=n;i++)
            for(int j=1;j+(1<<i)-1<=n;j++)
                ST[j][i]=min(ST[j][i-1],ST[j+(1<<i-1)][i-1]);
    }
    int query(int l,int r)
    {
        int k=log2(r-l+1);
        return min(ST[l][k],ST[r-(1<<k)+1][k]);
    }
};

分块

const int N=5e4+5;
int a[N],n,m,num,block;
int l[N],r[N],belong[N],tag[N];
void init()
{
    block=sqrt(n);
    num=ceil(n*1.0/block);
    for(int i=1;i<=num;i++)
        l[i]=(i-1)*block+1,r[i]=i*block;
    r[num]=n;
    for(int i=1;i<=n;i++)
        belong[i]=(i-1)/block+1;
}

莫队( n n n\sqrt n nn

离线(特别注意卡常,一般处理1e5,1e6直接上BIT

const int N=1e6+5;
int a[N],cnt[N],belong[N],block,sum=0,ans[N];
struct node
{
    int l,r,pos;
    bool operator<(const node &b)const
    {
          return belong[l]==belong[b.l]?r<b.r:belong[l]<belong[b.l];
    }
} s[N];
void add(int pos)
{
    if(!cnt[a[pos]])
        ++sum;
    ++cnt[a[pos]];
}
void del(int pos)
{
    --cnt[a[pos]];
    if(!cnt[a[pos]])
        --sum;
}

进行分块

block=sqrt(n);
    for(int i=1; i<=n; ++i)
        belong[i]=(i-1)/block+1;

双指针移动

    int l=1,r=0;
	for(int i=0; i<m; ++i)
    {
        int ql=s[i].l,qr=s[i].r;
        while(l<ql)
            del(l++);
        while(l>ql)
            add(--l);
        while(r<qr)
            add(++r);
        while(r>qr)
            del(r--);
        ans[s[i].pos]=sum;
    }

卡常优化

1:移动指针常数优化,大概200ms

        while(l < ql)
            sum -= !--cnt[a[l++]];
        while(l > ql)
            sum += !cnt[a[--l]]++;
        while(r < qr)
            sum += !cnt[a[++r]]++;
        while(r > qr)
            sum -= !--cnt[a[r--]];

2:玄学奇偶性排序,大概200ms

return (belong[l]^belong[b.l])?belong[l]<belong[b.l]:((belong[l]&1)?r<b.r:r> b.r); 

玄学

	bool operator<(const node& b)
    {
        if(belong==b.belong)
            return r<b.r;
        return l<b.l;
    }

3:快读,快输优化

int read()
{
    int res = 0;
    char c = getchar();
    while(!isdigit(c))
        c = getchar();
    while(isdigit(c))
        res = (res << 1) + (res << 3) + c - 48, c = getchar();
    return res;
}
void printi(int x)
{
    if(x / 10)
        printi(x / 10);
    putchar(x % 10 + '0');
}

4:开O2

主席树(可持久化维护区间权值和)

const int N=5e5+5;
namespace zhuxi
{
    struct node
    {
        int left,right,value;
    }s[N*40];
    int cnt=0,root[N];
    void modify(int l,int r,int pre,int &now,int value)
    {
        s[++cnt]=s[pre];
        now=cnt;
        s[cnt].value++;
        if(l==r)
            return ;
        int mid=(l+r)>>1;
        if(value<=mid)
            modify(l,mid,s[pre].left,s[now].left,value);
        else
            modify(mid+1,r,s[pre].right,s[now].right,value);
    }
    int query(int l,int r,int L,int R,int k)
    {
        if(l==r)
            return l;
        int mid=(l+r)>>1;
        int temp=s[s[R].left].value-s[s[L].left].value;
        if(k<=temp)
            return query(l,mid,s[L].left,s[R].left,k);
        else
            return query(mid+1,r,s[L].right,s[R].right,k-temp);
    }
}

DP

最长上升子序列(LIS)——O(nlogn

定义
最长上升子序列是:
1:只包含ai的子序列
2:满足j 3:子序列长度最长
朴素算法O(n^2) dp+二分(nlogn)
在这里插入图片描述

dp数组维护的是以pos位置结尾最小可能的值,如果要打印最长上升子序列路径,开辟新数组path,倒着找合法序列,正序寻找就会出现
1 2 4 6(2 8 6 7)这种不合法序列。虽然满足所有ai j

注意:严格lis:lower_bound,非严格lis:upper_bound

        vector<int>lis;
        for(int j=i;j<n+i;j++)
        {
            auto it=upper_bound(all(lis),a[j]);
            if(it==lis.end())
                lis.pb(a[j]);
            else
                lis[it-lis.begin()]=a[j];
        }

LCS(最长公共序列)——O(n^2)

枚举一下就穿上裤子就走人
板子总和+_第9张图片
板子总和+_第10张图片

for(int i=1; i<=len1; ++i)
{
    for(int j=1; j<=len2; ++j)
    {
        if(s[i-1]==t[j-1])
            dp[i][j]=dp[i-1][j-1]+1;
        else
            dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
    }
}
cout<<dp[len1][len2]<<endl;

区间DP——O(n3

一般问题
把相邻符合条件的合并,来获得最优解

概念

区间类型动态规划是线性动态规划的拓展,它在分阶段划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。(例:f[i][j]=f[i][k]+f[k+1][j])

区间类动态规划的特点:

合并:即将两个或多个部分进行整合。
特征:能将问题分解成为两两合并的形式。
求解:对整个问题设最优值,枚举合并点,将问题分解成为左右两个部分,最后将左右两个部分的最优值进行合并得到原问题的最优值。

板子总和+_第11张图片
DP(n^3) DP+四边形不等式优化(n^2)
一般数据500还能跑跑,如果1e3基本凉了,要优化到O(n^2),要用到四边形不等式优化。暂时不会,再等等吧

cin>>n;
memset(dp,inf);///初始化dp数组
for(int i=1; i<=n; ++i)
    cin>>a[i],pre[i]=pre[i-1]+a[i],dp[i][i]=0;
for(int len=1; len<n; ++len) ///枚举区间长度
{
    for(int i=1; i+len<=n; ++i) ///枚举区间起点
    {
        int j=i+len;///根据起点得到合法终点
        for(int k=i; k<j; ++k) ///枚举最优分割点
            dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
        dp[i][j]+=pre[j]-pre[i-1];
    }
}
cout<<dp[1][n]<<endl;

树形DP——O(m)

算法核心
出发点:点对至少需要两个点。
枚举每条边的u和v,u是v父节点,v向下的最大直径+cost+u向上的最大直径
向下的最大直径,就是看最大跟次大,但是每次先加入当前点值,保证每次直径存在尾点权
向上的最大直径,u结点先更新点权跟向上最大简单路径,再更新v的最大直径点权和
子树计数

问题:直接DFS,统计根子树最优解,然后根据题意变换
换根
fat结点更新u结点子结点:dp[fat]=ans[fat]-max(dp[u],0);
老方法:更新根节点:ans[u]=dp[u]+max(dp[fat],0);
板子总和+_第12张图片

vector<int>G[N];
int a[N],dp[N],ans;
void dfs(int u,int fat)
{
    dp[u]=a[u];
    for(auto v:G[u])
    {
        if(v!=fat)
        {
            dfs(v,u);
            dp[u]+=max(0,dp[v]);
        }
    }
    ans=max(ans,dp[u]);
}

换根-任意一点能到达的最远距离

const int N=5e5+5;
vector<pair<int,int> >G[N];
int dp[N][3],pot[N];
void dfs(int u,int fat)
{
    for(auto it:G[u])
    {
        int v=it.F,cost=it.S;
        if(v==fat)
            continue;
        dfs(v,u);
        if(dp[u][0]<=dp[v][0]+cost)
        {
            dp[u][1]=dp[u][0];
            dp[u][0]=dp[v][0]+cost;
            pot[u]=v;
        }
        else if(dp[u][1]<dp[v][0]+cost)
            dp[u][1]=dp[v][0]+cost;
    }
}
void dfs1(int u,int fat)
{
    for(auto it:G[u])
    {
        int v=it.F,cost=it.S;
        if(v==fat)
            continue;
        if(pot[u]==v)
            dp[v][2]=cost+max(dp[u][1],dp[u][2]);
        else
            dp[v][2]=cost+max(dp[u][0],dp[u][2]);
        dfs1(v,u);
    }
}

状压DP——O(3n

15 or 16可以枚举子集,以上就不行了
枚举子集
例如:1011
子集1010 1001 1000 0011 0010 0001
然后xor ,就可以得到子集的补集,合并取最小即可
板子总和+_第13张图片
枚举子集

    for(int i=1;i<(1<<n);i++)
    {
        dp[i]=get(i);
        for(int j=i;j;j=(j-1)&i)
            dp[i]=min(dp[i],dp[j]+dp[j^i]);
    }
    cout<<dp[ (1<<n)-1 ]<<endl;

概率DP

概率顺着推,期望逆着推
完成的期望为概率的倒数

高维前缀和(sosdp)—— O(nlogn)

子集前缀和

    for(int i=0;i<22;i++)
        for(int j=0;j<(1<<22);j++)
            if(j&(1<<i))
                dp[j]+=dp[j^(1<<i)];

板子总和+_第14张图片

数位DP

状态考虑完全,都加入的dp数组中,参考YKW模板
注意:有的时候,把limit加入dp中,每次清空dp更快,有的时候,取消limit,一次清空更快。

int dp[20][2][2][unknow],a[20];
int dfs(int pos,bool limit,bool lead,int state)
{
    if(pos==0)///边界条件
        return true or false;
    if(limit==0&&dp[pos][limit][lead][state]!=-1)
        return dp[pos][limit][lead][state];
    int maxn=(limit?a[pos]:9);
    int ans=0;
    for(int i=0;i<=maxn;i++)
    {
        if(lead&&i==0)
            ans+=dfs(pos-1,limit&&i==maxn,true,state);
        else///确定状态的改变
            ans+=dfs(pos-1,limit&&i==maxn,false,state);
    }
    if(limit==0)
        dp[pos][limit][lead][state]=ans;
    return ans;
}
int query(int x)
{
    ///这里不 容易TLE——memset(dp,-1);
    int pos=0;
    while(x)
        a[++pos]=x%10,x/=10;
    return dfs(pos,true,true,0);///开始有限制,有前导0
}
///输入样例之前清空dp

图论

链式前向星

自己呢,总是忘了,head为0,cnt=0,直接用

const int N=1e5+5;
const int M=3e6+5;
struct node
{
    int v,next,w;
    bool operator<(const node& b)const
    {
        return w<b.w;
    }
}e[M];
int cnt=0,head[N],vis[N],dis[N];
void add(int from,int to,double w)
{
    e[++cnt].next=head[from];
    e[cnt].v=to;
    e[cnt].w=w;
    head[from]=cnt;
}

最小生成树

克鲁斯卡尔算法(Kruskal算法)——时间复杂度O(eloge)
特性:稀疏图和记录路径
普里姆算法——时间复杂度O(n^2)
最小生树是否唯一问题:(次小生成树问题)
朴素算法(m^2)
1:暴力删除最小生树上的
2:判断权值和以及是否是一棵树
优化后,理论(mlogm)跑一次克鲁斯卡尔即可
边权相等的边,边两点不同的并查集的边数<=所在不同的并查集个数-1
保证最小生成树唯一

最小生成树:所有边权之和最小

瓶颈生成树:定义无向图G,G的瓶颈生成树是一棵 “ 树上最大边权值 edge 在G的所有生成树中最小 ” 的生成树,
这样的生成树可能不止一棵。瓶颈生成树的值为树上最大边权值 edge

结论:
最小生成树一定是瓶颈生成树
瓶颈生成树不一定时最小生成树

暂时理解:最小瓶颈生成树——瓶颈生成树一个意思

最短路

Dijkstra:适用于权值为非负的图的单源最短路径,朴素算法O(n2)用斐波那契堆的复杂度O(E+VlgV)
解决单源最短路径问题常用Djkstra算法,用于计算一个顶点到其他所有 顶点的最短路径。Djkstra 算法的主要特点是以起点为中心,逐层向外扩展,每次都会取一个最近点继续扩展,直到取完所有点为止。

BellmanFord:适用于权值有负值的图的单源最短路径,并且能够检测负圈,复杂度O(VE)

SPFA:适用于权值有负值,且没有负圈的图的单源最短路径,论文中的复杂度O(kE),k为每个节点进入Queue的次数,且k一般<=2,但此处的复杂度证明是有问题的,其实SPFA的最坏情况应该是O(VE).

板子总和+_第15张图片
先给出结论:
(1)当权值为非负时,用Dijkstra。
(2)当权值有负值,且没有负圈,则用SPFA,SPFA能检测负圈,但是不能输出负圈。
(3)当权值有负值,而且可能存在负圈,则用BellmanFord,能够检测并输出负圈。
(4)SPFA检测负环:当存在一个点入队大于等于V次,则有负环,后面有证明。
严格最短路是否唯一问题:(次严格最短路问题)
两次dijkstra,然后枚举


dis1[u]+dis+dis2[v]=dis1[n]
dis1[v]+dis+dis2[u]=dis1[n]

满足则标记为严格最短路必经路径

floyd暴力求最短路–O(n3)

const int INF=0x3f3f3f3f;
const int N=500;
int G[N][N];
void floyd(int n)//图的最短路路径,枚举一遍
{
    for(int k=0;k<n;k++)
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                G[i][j]=min(G[i][j],G[i][k]+G[k][j]);
}

Disjkstra算法

O(n2

int dis[N],vis[N],G[N][N],t,n;
void dijkstra(int u)
{
    memset(dis,inf);
    dis[u]=0;
    int v=u,minn;
    vis[v]=true;
    for(int i=1; i<=n; ++i)
    {
        minn=inf;
        for(int j=1; j<=n; ++j)
        {
            if(!vis[j]&&dis[j]<=minn)
                v=j,minn=dis[j];
        }
        vis[v]=true;
        for(int j=1; j<=n; ++j)
        {
            if(!vis[j])
                dis[j]=min(dis[j],dis[v]+G[v][j]);
        }
    }
}

O(eloge+n)
int dis[N],vis[N],V[N][N],m,n;
vector<int>G[N];
void dijkstra(int x)
{
    memset(dis,inf);
    dis[x]=0;
    priority_queue<pii>pri;
    pri.push({0,x});
    while(!pri.empty())
    {
        pii u=pri.top();
        pri.pop();
        if(vis[u.S])
            continue;
        vis[u.S]=true;
        for(auto v:G[u.S])
        {
            if(!vis[v]&&dis[u.S]+V[u.S][v]<dis[v])
            {
                dis[v]=dis[u.S]+V[u.S][v];
                pri.push({-dis[v],v});
            }
        }
    }
}

LCA

倍增LCA

第一步:dfs跑父结点(1倍祖先)-O(n)
第二步:预处理跑ST表-all倍增祖先- O(n*logn
第三步:LCA查询 单次O(logn) 多次O(mlogn

const int N=3e5+5;			///应用:1:两点之间的距离	2:最近公共祖先
int fat[N][21],h[N];
vector<int>G[N];
void dfs(int x)
{
    for(auto v:G[x])
    {
        if(v==fat[x][0])
            continue;
        h[v]=h[x]+1; fat[v][0]=x;
        dfs(v);
    }
}
int lca(int x,int y)
{
    if(h[x]<h[y]) swap(x,y);
    for(int i=20;i>=0;i--)
        if( (h[x]-h[y])>>i )
            x=fat[x][i];
    if(x==y)
        return x;
    for(int i=20;i>=0;i--)
        if( fat[x][i]!=fat[y][i] )
            x=fat[x][i],y=fat[y][i];
    return fat[x][0];
}
int dis(int x,int y)
{
    return h[x]+h[y]-h[lca(x,y)]*2;
}
for(int i=1;i<=20;i++)
        for(int j=1;j<=n;j++)
            fat[j][i]=fat[ fat[j][i-1] ][i-1];
       

倍增时间戳优化(链式前向星TLE再考虑,常数级优化)

const int N=5e5+5;
int in[N],out[N],fat[N][21],cnt=0,dis[N];
vector<int>G[N];
void dfs(int u)
{
    in[u]=++cnt;
    for(auto v:G[u])
    {
        if(v==fat[u][0])
            continue;
        dis[v]=dis[u]+1;
        fat[v][0]=u;
        for(int i=1;i<=20;i++)
            fat[v][i]=fat[fat[v][i-1]][i-1];
        dfs(v);
    }
    out[u]=++cnt;
}
bool ok(int u,int v)
{
    return in[u]<=in[v]&&out[v]<=out[u];
}
int lca(int u,int v)
{
    if(dis[u]>dis[v])
        swap(u,v);
    if(ok(u,v))
        return u;
    for(int i=20;i>=0;--i)
        if(!ok(fat[u][i],v)&&fat[u][i])
        u=fat[u][i];
    return fat[u][0];
}

树剖LCA

第一步:dfs跑Size depp fat -O(n)
第二步:dfs1跑top 轻重链-O(n)
第三步:LCA查询 单次O<=(logn) 比倍增更快
记住:卡常数老老实实链式前向星
Size 子树结点个数(包括本身) top 重链顶端结点
deep 结点的深度 fat父结点 son 当前结点的重儿子

const int N=5e5+5;
int Size[N],top[N],deep[N],fat[N],son[N];
vector<int>G[N];
void dfs(int u)
{
    Size[u]=1;
    deep[u]=deep[fat[u]]+1;
    for(auto v:G[u])
    {
        if(v==fat[u])
            continue;
        fat[v]=u;
        dfs(v);
        Size[u]+=Size[v];
        if(Size[v]>Size[son[u]])
            son[u]=v;
    }
}
void dfs1(int u)
{
    if(!top[u])
    top[u]=u;
    if(son[u])
    {
        top[son[u]]=top[u];
        dfs1(son[u]);
    }
    for(auto v:G[u])
        if(v!=fat[u]&&v!=son[u])
            dfs1(v);
}

int lca(int u,int v)
{
    while(top[u]!=top[v])
    {
        if(deep[top[u]]<deep[top[v]])
            swap(u,v);
        u=fat[top[u]];
    }
    return deep[u]<deep[v]?u:v;
}

二分图最大匹配

匈牙利算法O(VE)
HK算法O(sqrt(V)E)
KM算法
概念
1.最大独立点集:
在二分图中,选最多的点,使得任意两个点之间没有直接边连接。
最大独立集= 最小边覆盖 = 总点数- 最大匹配 (条件:在二分图中)

2.最小边覆盖:
在二分图中,求最少的边,使得他们覆盖所有的点,并且每一个点只被一条边覆盖。
最小边覆盖=图中的顶点数-(最小点覆盖数)该二分图的最大匹配数(条件:在二分图中)

3.最小点覆盖:
在二分图中,求最少的点集,使得每一条边至少都有端点在这个点集中。
最小点覆盖 = 最大匹配 (条件:在二分图中)

匈牙利算法(核心:建图——>AC)
第一步:建立关系图,只需要构建一条边即可:
第二步:枚举每个男朋友
第三步:DFS寻找是否可以分配一个小老婆

时间复杂度:邻接矩阵O(最坏n^3) 邻接表O(n*m)
空间复杂度:邻接矩阵O(n^2) 邻接表O(n+m)

邻接矩阵(n^3)

const int N=1e3+5;
int G[N][N],vis[N],g[N],ans=0;///G存关系图	vis是否访问	g存妹子的对象,没有对象就0
bool find(int x,int n)
{
    for(int i=1;i<=n;i++)///扫描所有妹子
    {
        if(G[x][i]==true&&vis[i]==false)///A:是否有暧昧关系 B:是否访问
        {
            vis[i]=true;
            if(g[i]==0||find(g[i],n))///A:名花无主 B:能腾出位置,就递归
            {
                g[i]=x;
                return true;
            }
        }
    }
    return false;
}
for(int i=1; i<=p; i++)///跑一遍男孩子
{
    memset(vis,0);//每次找对象,要清空
    if(find(i,n))
        ans++;
}

邻接表(n*m)

const int N=3e3+5;///记得每次len ,g,G初始化
bool vis[N];
int g[N];
vector<int>G[N];
bool dfs(int u)
{
    for(auto v:G[u])
    {
        if(vis[v])
            continue;
        vis[v]=true;
        if(!g[v]||dfs(g[v]))
        {
            g[v]=u;
            return true;
        }
    }
    return false;
}

网络流

在普通情况下, DINIC算法时间复杂度为O(V2E)
在二分图中, DINIC算法时间复杂度为O(√VE)

注意:由于0起点,head清空-1,deep清空0
第一步:链式前项星建立边权
第二步:BFS寻找s到t的增广路,更新deep深度
第三步:DFS增加流量

const int N=2e5+5;
struct node
{
    int next,to,dis;
} edge[N];
int head[N],pos=-1,deep[N];
void add_edge(int from,int to,int dis)
{
    edge[++pos].next=head[from];
    edge[pos].to=to;
    edge[pos].dis=dis;
    head[from]=pos;
}
bool bfs(int s,int t)///分层求depp深度
{
    memset(deep,0);
    queue<int>que;
    deep[s]=1;
    que.push(s);
    while(!que.empty())
    {
        int now=que.front();
        que.pop();
        for(int i=head[now]; i!=-1; i=edge[i].next)//dis在此处用来做标记 是正图还是返图
        {
            int v=edge[i].to,dis=edge[i].dis;
            if(!deep[v]&&dis)
            {
                deep[v]=deep[now]+1;
                que.push(v);
            }
        }
    }
    return (deep[t]?true:false);
}
///limit为s到t的最小边权,会被不断约束缩小
int dfs(int now,int t,int limit)///dfs寻找多条增广路,增加流量,可反悔,
{
    if(!limit || now==t)
        return limit;
    int flow=0,f;
    for(int i=head[now]; i!=-1; i=edge[i].next)
    {
        int v=edge[i].to,dis=edge[i].dis;
        if(deep[v]==deep[now]+1&&(f=dfs(v,t,min(limit,dis))))
        {
            flow+=f;
            limit-=f;
            edge[i].dis-=f;
            edge[i^1].dis+=f;
            if(!limit)
                break;
        }
    }
    return flow;
}
int Dinic(int s,int t)
{
    int ans=0;
    while(bfs(s,t))
        ans+=dfs(s,t,inf);
    return ans;
}

强连通分量——tarjan算法

第一步:枚举所有dfn为0跑tarjan
第二步:模板tarjan内部缩点
第三步:重新构图DAG

const int N=2e5+5;
int dfn[N],low[N],pos=0,col[N],col_cnt=0,cnt[N];
stack<int>sta;
set<int>insta;
vector<int>G[N];
void tarjan(int u)
{
    dfn[u]=low[u]=++pos;
    sta.push(u);
    insta.insert(u);
    for(auto v:G[u])
    {
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(insta.count(v))
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        col_cnt++;
        int k;
        do
        {

            k=sta.top(),sta.pop();
            insta.erase(k);
            col[k]=col_cnt,cnt[col_cnt]++;
        }while(u!=k);
    }
}

树上启发式合并(dsu on tree)——O(nlogn)

第一步:dfs(1,0),构造重儿子
第二步:dfs2(1,0,1);暴力跑

const int N=1e5+5;
vector<int>G[N];
int Size[N],son[N],col[N],cnt[N];
int ans[N],sum=0,maxn=0;
void dfs(int u,int fat)///构造重儿子
{
    Size[u]++;
    for(auto v:G[u])
    {
        if(v==fat)
            continue;
        dfs(v,u);
        Size[u]+=Size[v];
        if(Size[v]>Size[son[u]])
            son[u]=v;
    }
}
void add(int u,int fat,int val,int tag)
{
    cnt[col[u]]+=val;
    if(cnt[col[u]]>maxn)
        sum=col[u],maxn=cnt[col[u]];
    else if(cnt[col[u]]==maxn)
        sum+=col[u];
    for(auto v:G[u])
    {
        if(v==fat||v==tag)
            continue;
        add(v,u,val,tag);
    }
}
void dfs2(int u,int fat,int opt)
{
    for(auto v:G[u])
    {
        if(v==fat||v==son[u])
            continue;
        dfs2(v,u,0);///暴力处理轻边
    }
    if(son[u])///处理重儿子subtree
        dfs2(son[u],u,1);
    add(u,fat,1,son[u]);///u的substree除了重儿子的substree
    ans[u]=sum;
    if(!opt)///清除所有subtree of u的信息
        add(u,fat,-1,0),sum=0,maxn=0;
}

字符串

回文字符串

1:动态规划–O(n^2)

string longestPalindrome(string s)
{
    const int n = s.size();
    bool dp[n][n];
    memset(dp, 0, sizeof(dp));
 
    int maxlen = 1;     //保存最长回文子串长度
    int start = 0;      //保存最长回文子串起点
    for(int i = 0; i < n; ++i)
    {
        for(int j = 0; j <= i; ++j)
        {
            if(i - j < 2)
            {
                dp[j][i] = (s[i] == s[j]);
            }
            else
            {
                dp[j][i] = (s[i] == s[j] && dp[j + 1][i - 1]);
            }
 
            if(dp[j][i] && maxlen < i - j + 1)
            {
                maxlen = i - j + 1;
                start = j;
            }
        }
    }
 
    return s.substr(start, maxlen);
}

2:中心扩展法–O(n^2)

string longestPalindrome(string &s)
{
    const int len = s.size();
    int maxlen = 1;
    int start = 0;
    for(int i = 0; i < len; i++)//求长度为奇数的回文串
    {
        int j = i - 1, k = i + 1;
        while(j >= 0 && k < len && s.at(j) == s.at(k))
        {
            if(k - j + 1 > maxlen)
            {
                maxlen = k - j + 1;
                start = j;
            }
            j--;
            k++;
        }
    }
    for(int i = 0; i < len; i++)//求长度为偶数的回文串
    {
        int j = i, k = i + 1;
        while(j >= 0 && k < len && s.at(j) == s.at(k))
        {
            if(k - j + 1 > maxlen)
            {
                maxlen = k - j + 1;
                start = j;
            }

            j--;
            k++;
        }
    }
    return s.substr(start, maxlen);
}

3:马拉车算法(Manacher)–O(n)

struct Manacher
{
    string s,s_new;
    vector<int>p;
    Manacher(string str)
    {
        for(auto it:str)///这步可以省略
            s+=it;
        s_new+="$#";
        for(auto it:str)
            s_new+=it,s_new+='#';
        int len=s_new.size();
        p.resize(len);
    }
    int get()
    {
        int len=s_new.size(),max_len=-1;
        int id,mx=0;
        for(int i=1; i<len; i++)
        {
            if (i < mx)///2*id-i与mx-i的含义
                p[i]= min(p[2 * id - i], mx - i);
            else
                p[i]=1;
            while (s_new[i - p[i]] == s_new[i + p[i]])///边界有'$'与'\0',不可能越界
                p[i]++;
            if (mx < i + p[i])///不断更新mx与id,希望走的更远,提高效率
            {
                id = i;
                mx = i + p[i];
            }
            max_len = max(max_len, p[i] - 1);
        }
        return max_len;
    }
};

4:哈希+二分(写出来没马拉车方便,自己讨厌二分)

KMP——核心:循环节+前缀与后缀的关系

Next数组统计的是最大的前缀与后缀相同长度,不包括本身,同时也是上一循环节的位置。
k=next[k],回到更小的公共字符串,保证前缀和后缀相同。

如果匹配的最大后缀有循环节,后缀和前缀减少一个循环节再匹配,

如果匹配的最大后缀无循环节,都是0000状态,直接k=-1,无法找到更小的非空公共字符串

为何k=next[k],这么的巧妙,因为当时next[pos],值开始增加的时候,就是循环节开始的时候,每个位置的值是上一循环节的位置。

样例:abcabcabce

struct Kmp
{
    vector<int>Next;///Next数组统计的是最大的前缀与后缀相同长度
    Kmp(string &p){
        int len=p.size();
        int k=-1,j=0;
        Next.pb(-1);
        while(j<len-1){
            if(k==-1||p[k]==p[j]){
                k++,j++;
                Next.pb(k);
            }
            else{
                k=Next[k];
            }
        }
    }
    int search(string &s,string &p){
        int i=0,j=0,cnt=0;
        int lens=s.size(),lenp=p.size();
        while(i<lens&&j<lenp){
            if(j==-1||s[i]==p[j])
                i++,j++;
            else
                j=Next[j];
        }
        if(j==lenp)
            return i-j;
        return -1;
    }
};

Exkmp

next数组:t 与 t 的每一个后缀的 LCP 长度
extend数组:t 与 s 的每一个后缀的 LCP 长度
a:最大区间起始位置
p:最大区间终点位置
情况1:在区间内,直接获取LCP
情况2:在区间外,不断判断LCP+1

struct exkmp
{
    vector<int>extend,Next;
    exkmp(string &s,string &t)
    {
        extend.resize((int)s.size());
        Next.resize((int)t.size());
    }
    void get_next(string &t)
    {
        int len=t.size(),a=1,p;
        Next[0]=len;
        while(a<len&&t[a]==t[a-1])
            a++;
        Next[1]=a-1;
        a=1;
        for(int i=2;i<len;i++)
        {
            p=a+Next[a]-1;
            if((i-1)+Next[i-a]<p)
                Next[i]=Next[i-a];
            else
            {
                int j=max(p-i+1,0);
                while(i+j<len&&t[i+j]==t[j])
                    j++;
                Next[i]=j;
                a=i;
            }
        }
    }
    void get_exkmp(string &s,string &t)
    {
        int lens=s.size(),lent=t.size();
        int a=0,p=0;
        int len=min(lens,lent);
        while(p<len&&s[p]==t[p])
            p++;
        extend[0]=p;
        for(int i=1;i<lens;i++)
        {
            p=a+extend[a]-1;
            if((i-1)+Next[i-a]<p)
                extend[i]=Next[i-a];
            else
            {
                int j=max(p-i+1,0);
                while(i+j<lens&&j<lent&&s[i+j]==t[j])
                    j++;
                extend[i]=j;
                a=i;
            }
        }
    }
};

哈希

struct Hash///base131 or 13331
{
    vector<ull>pre,suf,p;
    Hash(string str)
    {
        int n=str.size();
        pre.resize(n+2);
        suf.resize(n+2);
        p.resize(n+2);
        p[0]=1;
        for(int i=1,j=n;i<=n;i++,j--)
        {
            pre[i]=pre[i-1]*base+str[i-1]-'a'+1;
            suf[j]=suf[j+1]*base+str[j-1]-'a'+1;
            p[i]=p[i-1]*base;
        }
    }
    ull hash_pre(int l,int r)
    {
        return pre[r]-pre[l-1]*p[r-l+1];
    }
    ull hash_suf(int l,int r)
    {
        return suf[l]-suf[r+1]*p[r-l+1];
    }
};

Trie

注意开辟空间==所有字符串长度之和
一般处理字符串前缀个数,或者经典的最大异或问题-贪心去做

int son[N*32][2],pos=0;
void insert(int num)
{
    int p=0;
    for(int i=30;i>=0;i--)
    {
        int &t=son[p][num>>i&1];
        if(!t)
            t=++pos;
        p=t;
    }
}
int query(int num)
{
    int sum=0,p=0;
    for(int i=30;i>=0;i--)
    {
        if(son[p][num>>i&1^1])
            sum+=1<<i,p=son[p][num>>i&1^1];
        else
            p=son[p][num>>i&1];
    }
    return sum;
}

AC自动机

第一步:建立字典树
第二步:BFS构建fail
第三步:文本串在多模式串查询

const int N=1e6+5;
int trie[N][32],fail[N],p=0,cnt[N];
void insert(string &str)
{
    int pos=0;
    for(auto it:str)
    {
        int &tag=trie[pos][it-'a'];
        if(!tag)
            tag=++p;
        pos=tag;
    }
    cnt[pos]++;
}
void get_fail()
{
    queue<int>que;
    for(int i=0;i<26;i++)
    {
        int tag=trie[0][i];
        if(tag)
            fail[tag]=0,que.push(tag);
    }
    while(!que.empty())
    {
        int fat=que.front();
        que.pop();
        for(int i=0;i<26;i++)
        {
            int &son=trie[fat][i];
            if(son)
                fail[son]=trie[fail[fat]][i],que.push(son);
            else
                son=trie[fail[fat]][i];
        }
    }
}
int query(string &str)
{
    int ans=0,pos=0;
    for(auto it:str)
    {
        pos=trie[pos][it-'a'];
        for(int i=pos;i&&cnt[i];i=fail[i])
            ans+=cnt[i],cnt[i]=0;
    }
    return ans;
}

最后送自己一句话:时间不在于你拥有多少, 而在于怎样使用

你可能感兴趣的:(ACM板子,c++,c语言,开发语言)