NOIP常用算法模板

版权声明:本文为博主原创文章,未经许可不准转载 https://blog.csdn.net/u012783994/article/details/83240064

NOIP复赛快到了,于是我整理了一份算法模板,以防忘记。本人弱省OI蒟蒻,若有不正确的地方请指出

1.并查集算法

//并查集基本思想:将两个独立的集合合并到一坨(莫忘鸟要判断根节点是否相同)

#include <iostream> 

using namespace std;

int fa[10001]={0};

//找根节点

int findfa(int x) {

    if(x==fa[x]) return x;

    else {

       fa[x]=findfa(fa[x]);

       return fa[x];

    }

}

 

//判断根节点是否相同

bool judge(int x,int y) {

    if(findfa(x)==findfa(y)) return true;

    else return false;

}

 

//合并两个独立的集合(合并根节点)

void join(int x,int y) {

    int p1=findfa(x);

    int p2=findfa(y);

    if(p1!=p2) fa[p1]=p2;

}

 

int main() {

    int n,m,z,x,y;

    cin >> n >> m;

    fa[1]=1;

    for(int i=1;i<=n;i++) {

       fa[i]=i;//每个结点的根就是自己(独立的)

    }

    for(int i=1;i<=m;i++) {

       cin >> z >> x >> y;

       switch(z) {

           case 1:

              join(x,y);

              break;

           case 2:

              if(judge(x,y)) cout << "Y" << endl;

              else cout << "N" << endl;

              break;

           default:

              break;

       }

    }

    return 0;

}

 

2.二分查找算法

 

//二分查找思想:必须从一个单调序列中选取一个中位数为基准,要找的数比基准数小/大就向左/右找

#include <iostream>

 

using namespace std;

int array[50001];

 

//从小到大二分查找

int search(int n, int target) { 

    int low = 1, high = n, middle; 

    while(low <= high) {  //还冇找完

        middle = (low + high)/2;  //基准数

        if(target == array[middle]) {  //找到

            return middle; 

        } else if(target < array[middle]) {  //比此小向左找

            high = middle - 1; 

        } else if(target > array[middle]) {  //比此大向右找

            low = middle + 1; 

        } 

    } 

    return -1;  //一直找不到

}

 

//这是一个单调递增的序列

int main() {

    int n,p;

    cin >> n >> p;

    for(int i=1;i<=n;i++) cin >> array[i];

    cout << search(n,array[p]) << endl;

    return 0;

}

 

3.高精度四则运算

(1)加

#include <iostream>

#include <string>

#include <algorithm>

 

using namespace std;

 

string add(string a1,string b1) {

    string res;

    int a[10001]={0},b[10001]={0},c[10001]={0};

    int k=1;

    int plus=0;

    reverse(a1.begin(),a1.end());

    reverse(b1.begin(),b1.end());

    int lena=a1.length(),lenb=b1.length();

    for(int i=0;i<lena;i++) a[i+1]=a1[i]-'0';

    for(int i=0;i<lenb;i++) b[i+1]=b1[i]-'0';

    while(k<=lena || k<=lenb) {

       c[k]=a[k]+b[k]+plus;

       plus=c[k]/10;

       c[k]%=10;

       k++;

    }

    c[k]=plus;

    for(int i=k;i>=1;i--) res+=(char)(c[i]+'0');

    while(res[0]=='0') res.erase(res.begin(),res.begin()+1);

    if(res.empty()) return "0";

    else return res;

}

int main() {

    string a,b;

    cin >> a >> b;

    cout << add(a,b) << endl;

    return 0;

}

 

(2)减

#include <iostream>

#include <string>

#include <algorithm>

 

using namespace std;

 

inline void strswap(string &a,string &b) {

    string t;

    t=a;

    a=b;

    b=t;

}

 

inline bool alessb(string a,string b) {

    if(a.length()<b.length()) return true;

    else if(a.length()==b.length()) {

       if(a<b) return true;

       else return false;

    } else return false;

}

 

inline string minus1(string a1,string b1) {

    bool negative=false;

    string str;

    if(alessb(a1,b1)) {

       strswap(a1,b1);

       negative=true;

    }

    int a[20001],b[20001],c[20001];

    int lena=a1.length(),lenb=b1.length();

    int k=1;

    reverse(a1.begin(),a1.end());

    reverse(b1.begin(),b1.end());

    for(int i=0; i<lena; i++) a[i+1]=a1[i]-'0';

    for(int i=0; i<lenb; i++) b[i+1]=b1[i]-'0';

    while(k<=lena || k<=lenb) {

       if(a[k]-b[k]<0) {

           a[k+1]--;

           a[k]+=10;

       }

       c[k]=a[k]-b[k];

       k++;

    }

    for(int i=k-1; i>=1; i--) str+=(char)(c[i]+'0');

    while(str[0]=='0') str.erase(str.begin(),str.begin()+1);

    if(str.empty()) return "0";

    else {

       if(negative) return "-"+str;

       else return str;

    }

}

 

int main() {

    string a,b;

    cin >> a >> b;

    cout << minus1(a,b) << endl;

    return 0;

}

 

(3)

#include <iostream>

#include <string>

#include <algorithm>

 

using namespace std;

 

string mul(string a1,string a2) {

    int a[10001]={0},b[10001]={0},c[10001]={0};

    string result;

    int plus=0;

    reverse(a1.begin(),a1.end());

    reverse(a2.begin(),a2.end());

    int lena=a1.length(),lenb=a2.length();

    for(int i=0;i<lena;i++) a[i+1]=a1[i]-'0';

    for(int i=0;i<lenb;i++) b[i+1]=a2[i]-'0';

    for(int i=1;i<=lena;i++) {

       plus=0;

       for(int j=1;j<=lenb;j++) {

           c[i+j-1]+=(a[i]*b[j]+plus);

           plus=c[i+j-1]/10;

           c[i+j-1]%=10;

       }

       c[i+lenb]=plus;//注意每次错位相乘乘完后要进位

    }

    for(int i=lena+lenb;i>=1;i--) result+=((char)(c[i]+'0'));

    while(result[0]=='0') result.erase(result.begin(),result.begin()+1);

    if(result.empty()) return "0";

    else return result;

}

 

int main() {

    string a,b;

    cin >> a >> b;

    cout << mul(a,b) << endl;

    return 0;

}

 

(4)高精除低精

//高精度除以低精度

#include <iostream>

#include <string>

#include <algorithm>

 

using namespace std;

 

typedef long long LL;

struct Info {

    string result;

    LL rest;

};

 

inline Info divide(string a1,LL d) {

    LL a[20001],b[20001];

    string res="";

    LL rest=0;

    int len=a1.length();

    for(int i=0;i<len;i++) a[i+1]=a1[i]-'0';

    for(int i=1;i<=len;i++) {

       rest=rest*10+a[i];

       b[i]=rest/d;

       rest=rest%d;

    }

    for(int i=1;i<=len;i++) res+=(char)(b[i]+'0');

    while(res[0]=='0') res.erase(res.begin(),res.begin()+1);

    return {res,rest};

}

 

int main() {

    string a;

    LL b;

    cin >> a >> b;

    Info p = divide(a,b);

    cout << p.result << "......" << p.rest;

    return 0;

}

(5)高精除高精

//高精度除以高精度

#include <iostream>

#include <algorithm>

#include <string>

#pragma \

GCC optimize("O3")

 

using namespace std;

 

inline void strswap(string &a,string &b) {

    string t;

    t=a;

    a=b;

    b=t;

}

 

inline bool alessb(string a,string b) {

    if(a.length()<b.length()) return true;

    else if(a.length()==b.length()) {

       if(a<b) return true;

       else return false;

    } else return false;

}

 

inline string minus1(string a1,string b1) {

    bool negative=false;

    string str;

    if(alessb(a1,b1)) {

       strswap(a1,b1);

       negative=true;

    }

    int a[20001],b[20001],c[20001];

    int lena=a1.length(),lenb=b1.length();

    int k=1;

    reverse(a1.begin(),a1.end());

    reverse(b1.begin(),b1.end());

    for(int i=0; i<lena; i++) a[i+1]=a1[i]-'0';

    for(int i=0; i<lenb; i++) b[i+1]=b1[i]-'0';

    while(k<=lena || k<=lenb) {

       if(a[k]-b[k]<0) {

           a[k+1]--;

           a[k]+=10;

       }

       c[k]=a[k]-b[k];

       k++;

    }

    for(int i=k-1; i>=1; i--) str+=(char)(c[i]+'0');

    while(str[0]=='0') str.erase(str.begin(),str.begin()+1);//去除前导零

    if(str.empty() || str=="0") return "0";

    else {

       if(negative) return "-"+str;

       else return str;

    }

}

 

inline string add(string a1,string b1) {

    string res;

    int a[10001]={0},b[10001]={0},c[10001]={0};

    int k=1;

    int plus=0;

    reverse(a1.begin(),a1.end());

    reverse(b1.begin(),b1.end());

    int lena=a1.length(),lenb=b1.length();

    for(int i=0;i<lena;i++) a[i+1]=a1[i]-'0';

    for(int i=0;i<lenb;i++) b[i+1]=b1[i]-'0';

    while(k<=lena || k<=lenb) {

       c[k]=a[k]+b[k]+plus;

       plus=c[k]/10;

       c[k]%=10;

       k++;

    }

    c[k]=plus;

    for(int i=k;i>=1;i--) res+=(char)(c[i]+'0');

    while(res[0]=='0') res.erase(res.begin(),res.begin()+1);

    if(res.empty()) return "0";

    else return res;

}

 

inline string divide(string s1,string s2) {

    string cnt;

    cnt=add("0","0");

    while(true) {

       s1=minus1(s1,s2);

       if(s1[0]!='-') {

           cnt=add(cnt,"1");

           continue;

       } else break;

    }

    return cnt;

}

 

int main() {

    string s1,s2;

    cin >> s1 >> s2;

    cout << divide(s1,s2) << endl;

    return 0;

}

 

4.递推算法

 

//动态规划 递推 maxn[i][j]=a[i][j]+max(maxn[i+1][j],maxn[i+1][j+1])

#include <iostream>

#include <cstring>

#include <algorithm>

 

using namespace std;

int a[1001][1001];

int maxn[1001][1001];

int n;

int main() {

    cin >> n;

    for(int i=1;i<=n;i++)

       for(int j=1;j<=i;j++)

           cin >> a[i][j];

    memset(maxn,0,sizeof(maxn));

    //最下面一行的最大值来自于自己

    for(int i=1;i<=n;i++) maxn[n][i]=a[n][i];

    //动态规划递推

    for(int i=n-1;i>=1;i--) {

       for(int j=1;j<=i;j++) {

           maxn[i][j]=a[i][j]+max(maxn[i+1][j],maxn[i+1][j+1]);

       }

    }

    cout << maxn[1][1] << endl;

    return 0;

}

 

5.快速幂算法

 

//快速幂思路:将大于等于2的指数n分成两个n/2次方进行递归运算

#include <iostream>

 

using namespace std;

 

//求a的b次方快速幂

long long quickpow(int a,int b) {

    //递归边界莫忘鸟

    if(b==0) return 1;

    else if(b==1) return a;

    else {

       if(b%2==0) return quickpow(a,b>>1)*quickpow(a,b>>1);

       else return quickpow(a,b>>1)*quickpow(a,b>>1)*a;//注意不是偶数的指数需要再乘1次

    }

}

 

int main() {

    int a,b;

    cin >> a >> b;

    cout << quickpow(a,b) << endl;

    return 0;

}

 

6.动态规划01背包问题

//基本01背包:枚举背包容量再选取最优值

#include <iostream>

#include <algorithm>

 

using namespace std;

 

int main() {

    int f[101][1001];//f[i][j]表示采药i花费时间j的最大价值

    int t,m,time[101],value[101];

    cin >> t >> m;

    for(int i=1;i<=m;i++) {

       cin >> time[i] >> value[i];

    }

    for(int j=1;j<=t;j++) f[0][j]=0;//初始化蛮关键(第0号药无论有多少时间花费都不能采)

    for(int i=1;i<=m;i++) {

       for(int j=t;j>=1;j--) {//枚举背包容量大小(采药时间限制)

           if(j>=time[i]) {//没超过时间

              f[i][j]=max(f[i-1][j],

                  f[i-1][j-time[i]]+value[i]

              ); //采、不采之间选一个最大价值

           } else {//背包容量不够(超时)

              f[i][j]=f[i-1][j];//不采

           }

       }

    }

    cout << f[m][t] << endl;//输出采药m花费时间t所得到的最大总价值

    return 0;

}

 

7.最长上升子序列

 

//这其实就是个线性动规:dp[i]表示以a[i]结尾的最长上升/下降子序列的长度

 

#include <iostream>

#include <cstring>

#include <algorithm>

 

using namespace std;

//先求最长上升子序列,再求最长下降子序列

int main() {

    int height[101], n;

    int dp1[1001], dp2[1001];//dp1->最长上升子序列,dp2->最长下降子序列

    cin >> n;

    for (int i = 1; i <= n; i++) cin >> height[i];

    for(int i=1;i<=n;i++)

       dp1[i]=dp2[i]=1;

    for (int i = 1; i <= n; i++) {

       for (int j = 1; j < i; j++) {

           if (height[i] > height[j]) dp1[i] = max(dp1[i],dp1[j]+1);

       }

    }

    //倒着求最长下降子序列,其实就是求最长上升子序列

    for (int i = n; i >= 1; i--) {

       for (int j = n; j > i; j--) {

           if (height[i] > height[j]) dp2[i] = max(dp2[i], dp2[j] + 1);

       }

    }

    int maxlen = 1;//最多留下的人

    for (int i = 1; i <= n; i++) {

       //减1是因为中间有个同学重复算了一次

       maxlen = max(maxlen, dp1[i] + dp2[i] - 1);

    }

    cout << n - maxlen << endl;

    return 0;

}

 

8.区间动规

 

/*基本思路:设前i到j的最优值,枚举剖分(合并)点,将(i,j)分成左右两区间,分别求左右两边最优值。状态转移方程的一般形式:F(i,j)=Max{F(i,k)+F(k+1,j)+决策,k为划分点*/

 

/*

对于这一题需要化环为链

我们可以用化环为链的方法,具体的实现就是将这个环的单圈复制一遍.

 

举个例子,输入1、2、3、4、5;那么我们就复制成1、2、3、4、5、1、2、3、4、5;

 

当我们用DP算完后,我们从起点起,依次向后延伸环长度,你看是不是把环的每一种情况都列举到了。

 

然后其实就是一个简单的DP了。

 

比如说我们要求合并石子i--j的最佳方案,我们可以把 i----j 分为 i--k 与 k+1--j两段;

 

枚举k的取值在分别取最大最小值就行了。

 

PS:DP状态转移式:f[i][j]=max/min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]) (最大值最小值都适用)

 

注:+s[j]-s[i-1]是要把i--k与k+1--j合并时的得分

*/

#include <iostream>

#include <cstring>

 

#define INF 99999999

 

using namespace std;

 

int Max[201][201],Min[201][201];//Max/Min[i][j]表示从第i堆石头合并到第j堆最大/小的得分

int a[201]={0};//a[i]表示前i个石头数量和

 

int main() {

    int n;

    //预处理数据

    cin >> n;

    for(int i=1;i<=n;i++) {

       cin >> a[i];

       a[i+n]=a[i];

    }

    //化环为链

    for(int i=2;i<=2*n;i++) a[i]+=a[i-1];

    for(int i=1;i<=2*n;i++)

       for(int j=i+1;j<=2*n;j++)

           Min[i][j]=INF;

    memset(Max,0,sizeof(Max));

    for(int i=2*n-1;i>=1;i--) {

       for(int j=i+1;j<=2*n;j++) {//左右区间合并

           for(int k=i;k<=j-1;k++) {

              //Max(i,j)=max{Max(i,j),Max(i,k)+Max(k+1,j)+t[i][j]}

              Max[i][j]=max(Max[i][j],Max[i][k]+Max[k+1][j]+a[j]-a[i-1]);

              Min[i][j]=min(Min[i][j],Min[i][k]+Min[k+1][j]+a[j]-a[i-1]);

           }

       }

    }

    int maxn=0,minn=INF;

    //还要从环上每个顶点统计一遍

    for(int i=1;i<=n;i++) {

       maxn=max(maxn,Max[i][i+n-1]);

       minn=min(minn,Min[i][i+n-1]);

    }

    cout << minn << endl << maxn << endl;

    return 0;

}

 

9.坐标规则型动规

 

//基本思路:那在一个矩阵中给出一些规则,然后按规则去做某些决策

//DP关系式: F(i,j)=Max{f(i-1,k)}+决策

#include <iostream>

#include <algorithm>

 

using namespace std;

 

int bx,by,mx,my;

long long f[31][31]={0};//f[i][j]表示(0,0)到(i,j)的路径条数

long md[9][2]={{0,0},{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}};

bool cant[31][31]={false};

 

int main() {

    cin >> bx >> by >> mx >> my;

    for(int k=0;k<9;k++) {

       int nx=mx+md[k][0];

       int ny=my+md[k][1];

       if(nx>=0&&nx<=bx&&ny>=0&&ny<=by) cant[nx][ny]=true;

    }

    //起点冇被马包围

    if(!cant[0][0]) {

       f[0][0]=1;

       for(int i=0;i<=bx;i++) {

           for(int j=0;j<=by;j++) {

              if(!cant[i][j]) {

                  if(i!=0 && !cant[i-1][j]) f[i][j]+=f[i-1][j];

                  if(j!=0 && !cant[i][j-1]) f[i][j]+=f[i][j-1];

              }

           }

       }

       cout << f[bx][by] << endl;

    } else cout << 0 << endl;//否则冇得办法

    return 0;

}

 

 

10.线段树

//使用线段树查询一个区间的最值或和差

#include <iostream>

#include <algorithm>

 

using namespace std;

 

int segtree[400001];

int array[100001];

int m,n;

 

//构造区间最小数的线段树

//node:结点编号,start:左区间,end:右区间

inline void build(int node,int start,int end) {

    if(start==end) segtree[node]=array[start];//到了根节点填值

    else {

       int mid=(start+end)/2;

       build(node*2,start,mid);//构造左子树

       build(node*2+1,mid+1,end);//构造右子树

       segtree[node]=min(segtree[node*2],segtree[node*2+1]);//从左右子树回溯填入最小值千万莫忘鸟!

    }

}

 

//查询线段树

//node->节点编号,[begin,end]当前结点区间,[l,r]查询区间

inline int query(int node, int begin, int end, int left, int right) {

    int p1, p2;

    if (left > end || right < begin) return -1;//查询区间和要求的区间没有交集

    if (begin >= left && end <= right) return segtree[node];//当前节点区间包含在查询区间内

    /* 分别从左右子树查询,返回两者查询结果的较小值 */

    p1 = query(2 * node, begin, (begin + end) / 2, left, right);

    p2 = query(2 * node + 1, (begin + end) / 2 + 1, end, left, right);

    /*  返回需要的值  */

    if (p1 == -1) return p2;

    if (p2 == -1) return p1;

    return min(p1,p2);

}

 

int main() {

    cin >> m >> n;

    for(int i=1;i<=m;i++) cin >> array[i];

    build(1,1,m);

    for(int i=1;i<=n;i++) {

       int a,b;

       cin >> a >> b;

       cout << query(1,1,m,a,b) << " ";

    }

    return 0;

}

 

 

11.邻接表

//使用vector动态数组存储边的信息

#include <iostream>

#include <vector>

 

using namespace std;

 

const int MAX = 10000;

 

struct EdgeNode {

    int to;//指向的结点编号

    int w;//这两点间的权值

};

 

vector<EdgeNode> v[MAX];

 

int main() {

    EdgeNode e;

    int n,m,w;//n个顶点m条边 ,w权值

    int a,b;//a->b的顶点边

    ios::sync_with_stdio(false);

    cin >> n >> m;

    for(int i=0;i<m;i++) {

       cin >> a >> b >> w;

       v[a].push_back({b,w});

    }

    //遍历

    for(int i=1;i<=n;i++) {

       vector<EdgeNode>::iterator it;

       for(it=v[i].begin();it!=v[i].end();it++) {

           EdgeNode r = *it;

           cout << "Edge " << i << " to " << r.to << " is " << r.w << endl;

       }

    }

    return 0;

}

 

12.Sparse Table算法

//用于求区间最值

#include <cstdio>

#include <algorithm>

#include <cmath>

 

using namespace std;

 

int n,m;

int dp[200001][30];//dp[i][j]=[i,i+2^j-1]min

 

int query(int l,int r) {

    int k=log(r-l+1)/log(2);//区间长度为r-l+1的对数

    return max(dp[l][k],dp[r-(1<<k)+1][k]);//合并区间最小值

}

 

int main() {

    scanf("%d%d",&n,&m);

    for(int i=1;i<=n;i++) {

       scanf("%d",&dp[i][0]);

    }

    for(int i=1;i<=log(n)/log(2);i++) {//枚举log2(n)的数

       for(int j=1;j<=n-(1<<i)+1;j++) {//枚举1~n-(2^i-1)的数

           dp[j][i]=max(dp[j][i-1],dp[j+(1<<(i-1))][i-1]);

       }

    }

    for(int i=1;i<=m;i++) {

       int l,r;

       scanf("%d%d",&l,&r);

       printf("%d\n",query(l,r));

    }

    return 0;

}

 

13.SPFA(Bellman-Ford队列优化)

//注意要用邻接表实现,每次取未经过的点放入队首松弛以求最短路径

#include <iostream>

#include <fstream>

#include <algorithm>

#include <queue>

#include <vector>

 

#define FSTREAM 1

 

using namespace std;

 

struct Edge{

    int u,v,w;

};

const int inf = 1<<30;

int n,m;

queue<int> q;

vector<Edge> e;

int dis[10001];//dis[i]->1~i的权值

bool book[10001];//i号顶点是否在队列中

int first[10001],nxt[10001];//邻接表

 

int main() {

#if FSTREAM

    ifstream fin("spfa.in");

    ofstream fout("spfa.out");

    fin >> n >> m;

#else

    cin >> n >> m;

#endif

    fill(dis,dis+n+1,inf);

    dis[1]=0;

    fill(book,book+n+1,false);

    fill(first,first+n+1,-1);

    for(int i=0;i<m;i++) {

       int p1,p2,p3;

#if FSTREAM

       fin >> p1 >> p2 >> p3;

#else

       cin >> p1 >> p2 >> p3;

#endif

       e.push_back({p1,p2,p3});

       nxt[i]=first[p1];

       first[p1]=i;

    }

    //1号顶点入队

    q.push(1);

    book[1]=true;

    int k;//当前需要处理的队首顶点

    while(!q.empty()) {

       k=first[q.front()];

       while(k!=-1) {//搜索当前顶点所有边

           if(dis[e[k].v]>dis[e[k].u]+e[k].w) {

              dis[e[k].v]=dis[e[k].u]+e[k].w;//松弛

              if(!book[e[k].v]) {//不在队列中,加入队列

                  book[e[k].v]=true;

                  q.push(e[k].v);

              }

           }

           k=nxt[k];//继续下一个

       }

       book[q.front()]=false;

       q.pop();

    }

#if FSTREAM

    for(int i=1;i<=n;i++) {

       if(dis[i]!=inf) fout << dis[i] << " ";

       else fout << "INF ";

    }

    fin.close();

    fout.close();

#else

    for(int i=1;i<=n;i++) {

       if(dis[i]!=inf) cout << dis[i] << " ";

       else cout << "INF ";

    }

#endif

    return 0;

}

 

14.埃氏筛素数

#include <iostream>

#include <algorithm>

#include <cmath>

 

using namespace std;

 

const int MAX = 100001;

bool prime[MAX]={false};

 

inline void make_prime() {

    fill(prime,prime+MAX,true);

    prime[0]=prime[1]=false;

    int t=(int)sqrt(MAX*1.0);

    for(register int i=2;i<=t;i++) {

       if(prime[i]) {

           for(register int j=2*i;j<MAX;j+=i) {

              prime[j]=false;

           }

       }

    }

}

 

int main() {

    make_prime();

    for(register int i=0;i<=MAX;i++) if(prime[i]) cout << i << "\t";

    return 0;

}

 

15.欧拉筛素数

#include <iostream>

#include <vector>

#include <algorithm>

 

using namespace std;

const int MAX = 100001;

bool prime[MAX];

vector<int> v;

 

void fast_prime() {

    fill(prime,prime+MAX,true);

    prime[0]=prime[1]=false;

    for(int i=2;i<=MAX;i++) {

       if(prime[i]) v.push_back(i);

       for(int j=0;j<v.size() && i*v[j]<MAX;j++) {

           prime[i*v[j]]=false;

           if(i%v[j]==0) break;

       }

    }

}

 

int main() {

    fast_prime();

    for(int i=1;i<=MAX;i++) if(prime[i]) cout << i << "\t";

    return 0;

}

 

16.扩展欧几里得

思路

#include <iostream>

 

using namespace std;

 

//扩展欧几里德(x,y要传到main函数因此要加取址符)

int exgcd(int a,int b,int &x,int &y) {

    if(b==0) { //对于ax+by=gcd(a,b)

    //当 b=0 时 gcd(a,b)=a

    //必有解x=1 y=0

       x=1;

       y=0;

       return a;

    }

    int r=exgcd(b,a%b,x,y);

    int tmp=x;

    x=y;

    y=tmp-a/b*y;

    return r;

}

 

int main() {

    //求ax+by=1的最小x,y

    int a,b,x,y;

    cin >> a >> b;

    exgcd(a,b,x,y);

    cout << (x+b)%b << endl;

    return 0;

}

 

17.最小生成树

//先将边按权值从小到大排序,再添加边,用并查集判重

#include <iostream>

#include <vector>

#include <algorithm>

 

using namespace std;

 

struct Edge {

    int u,v,w;

};

vector<Edge> e;

int n,m;

 

bool cmp(Edge a,Edge b) {

    return a.w<b.w;

}

 

//并查集的作用是判断图是否联通

namespace BingChaJi {

    int father[10001];

    int findfa(int x) {

       if(father[x]==x) return x;

       return father[x]=findfa(father[x]);

    }

    bool join(int x,int y) {

       int p1=findfa(x);

       int p2=findfa(y);

       if(p1!=p2) {

           father[p2]=p1;

           return true;

       }

       return false;

    }

    void initbcj(int n) {

       for(int i=1;i<=n;i++) father[i]=i;

    }

}

 

using namespace BingChaJi;

 

int main() {

    int minans=0;

    int cnt=0;

    cin >> n >> m;

    for(int i=1;i<=m;i++) {

       int a,b,c;

       cin >> a >> b >> c;

       e.push_back({a,b,c});

    }

    sort(e.begin(),e.end(),cmp);

    //注意:选完n-1条边即可退出

    initbcj(n);

    for(int i=0;i<e.size();i++) {

       if(join(e[i].u,e[i].v)) {

           minans+=e[i].w;//不联通就选择

           cnt++;

       } 

       if(cnt==n-1) break;

    }

    cout << minans << endl;

    return 0;

}

 

18.图的割点

//割点,啊哈算法

#include <iostream>

#include <cstdio>

#include <algorithm>

using namespace std;

 

const int N = 105;

int e[N][N],num[N],low[N],flag[N];

int n,m,index,root;

 

void dfs(int cur,int father) {

    int child = 0;

    index++;//第几次被访问

    num[cur] = index;//当前顶点时间戳

    low[cur] = index;//当前顶点能访问到的最早的时间戳

    for(int i = 1; i <= n; i++) {

       if(e[cur][i] == 1) {

           if(num[i] == 0) { //没有被访问过

              child++;//该结点的儿子总数加1

              dfs(i,cur);

              low[cur] = min(low[cur], low[i]);//更新当前节点可以达到的最早顶点的时间戳

              //如果当前节点不是根节点的话,满足要求,,

              if(cur != root && low[i] >= num[cur])

                  flag[cur] = 1;

              if(cur == root && child == 2)//如果是根节点的话,需要满足有两个儿子

                  flag[cur] = 1;

           } else if(i != father) { //被访问过,更新时间戳

              low[cur] = min(low[cur],num[i]);

           }

       }

    }

}

int main() {

    int x,y;

    //n个点m条边

    scanf("%d%d",&n,&m);

    for(int i = 1; i <= n; i++)

       for(int j = 1; j <= n; j++)

           e[i][j] = 0;

    for(int i = 1; i <= m; i++) {

       scanf("%d%d",&x,&y);

       e[x][y] = e[y][x] = 1;

    }

    root = 1;

    dfs(1, root);

    for(int i = 1; i <= n; i++) {

       if(flag[i] == 1)

           printf("%d ",i);

    }

    return 0;

}

/*

6 7

1 4

1 3

4 2

3 2

2 5

2 6

5 6

*/

 

19.图的割边

#include <iostream>

#include <cstdio>

#include <algorithm>

using namespace std;

 

const int N = 105;

int e[N][N],num[N],low[N];

int n,m,index,root;

 

void dfs(int cur,int father) {

    index++;

    num[cur] = index;

    low[cur] = index;

    for(int i = 1; i <= n; i++) {

       if(e[cur][i] == 1) {

           if(num[i] == 0) { //没有被访问过

              dfs(i,cur);

              low[cur] = min(low[cur], low[i]);//更新当前节点可以达到的最早顶点的时间戳

              //如果当前节点不是根节点的话,满足要求,,

              if(low[i] > num[cur]) {

                  printf("%d-%d\n",cur,i);

              }

           } else if(i != father)

              low[cur] = min(low[cur],num[i]);

       }

    }

}

int main() {

    int x,y;

    scanf("%d%d",&n,&m);

 

    for(int i = 1; i <= n; i++)

       for(int j = 1; j <= n; j++)

           e[i][j] = 0;

    for(int i = 1; i <= m; i++) {

       scanf("%d%d",&x,&y);

       e[x][y] = e[y][x] = 1;

    }

 

    root = 1;

    dfs(1, root);

 

    return 0;

}

/*

6 6

1 4

1 3

4 2

3 2

2 5

5 6

*/

 

20.二分图最大匹配

#include <iostream>

#include <cstring>

 

using namespace std;

 

int n,m,match[201];

bool book[201]={false},e[201][201]={false};

 

//判断u能否和其他人匹配

bool dfs(int u) {

    for(int i=1;i<=m;i++) {

       if(!book[i]&&e[u][i]) {//没访问过

           book[i]=true;

           if(match[i]==0

           || dfs(match[i])) {//要么没匹配,要么让这个人能和其他人匹配

              match[i]=u;//更新匹配信息

              return true;

           }

       }

    }

    return false;

}

 

int main() {

    int s,p,cnt=0;

    cin >> n >> m;

    //读入牛栏匹配信息

    for(int i=1;i<=n;i++) {

       cin >> s;

       for(int j=1;j<=s;j++) {

           cin >> p;

           e[i][p]=true;

       }

    }

    //刚开始没有人匹配

    memset(match,false,sizeof(match));

    //每只奶牛匹配

    for(int i=1;i<=n;i++) {

       memset(book,false,sizeof(book));

       dfs(i);

    }

    //匹配的牛栏就计数

    for(int i=1;i<=m;i++) {

       if(match[i]) cnt++;

    }

    cout << cnt << endl;

    return 0;

}

21.图的最小环

//用floyd算法实现

#include <iostream>

 

using namespace std;

 

const int inf=99999999;

 

int main() {

    //floyd算法

    int e[101][101]= {0},a[101][101]= {0},k,i,j,n,m,t1,t2,t3,minn;

    while(cin >> n >> m) {

       minn=inf;

       for(i=1; i<=n; i++) {

           for(j=1; j<=n; j++) {

              e[i][j]=inf;

              a[i][j]=inf;

           }

       }

       for(i=1; i<=m; i++) {

           cin >> t1 >> t2 >> t3;

           if(e[t1][t2]>t3) {

              e[t1][t2]=t3;

              e[t2][t1]=t3;

              a[t1][t2]=t3;

              a[t2][t1]=t3;

           }

       }

       for(k=1; k<=n; k++) {

           for(i=1; i<=n; i++) {

              for(j=1; j<=n; j++) {

                  //环

                  if(e[i][j]+a[j][k]+a[k][i]<minn && i!=j && a[j][k]!=inf && a[k][i]!=inf) {

                     minn=e[i][j]+a[j][k]+a[k][i];

                  }

              }

           }

           //求i->j最短路径(floyd)

           for(i=1; i<=n; i++) {

              for(j=1; j<=n; j++) {

                  if(e[i][j]>e[i][k]+e[k][j] && i!=j && e[i][k]!=inf && e[k][j]!=inf) {

                     e[i][j]=e[i][k]+e[k][j];

                  }

              }

           }

       }

       if(minn!=inf) cout << minn << endl;

       else cout << "No Solution!" << endl;

    }

    return 0;

}

 

22.网络流

#include <iostream>

#include <queue>

#include <algorithm>

#include <cstring>

 

using namespace std;

 

int e[201][201],father[201],m,n,sum=0,inf=0x3fffffff;

bool flag[201];

 

 

//判断能否从1号点到达n号点

bool bfs() {

    int u;

    queue<int> q;

    for(int i=1; i<=n; i++)

       father[i]=-1;

    memset(flag,false,sizeof(flag));

    q.push(1);

    flag[1]=true;

    while(!q.empty()) {

       u=q.front();

       if(u==n) return true;//找到

       q.pop();

       for(int i=1; i<=n; i++) {

           if(e[u][i]>0&&flag[i]==false) {

              flag[i]=true;

              father[i]=u;

              q.push(i);

           }

       }

    }

    return false;

}

 

int main() {

    int u,mmin,t1,t2,t3;

    ios::sync_with_stdio(false);

    cin >> m >> n;//m->边数,n->交叉点数

    for(int i=1; i<=n; i++)

       for(int j=1; j<=n; j++)

           e[i][j]=0;

 

    for(int i=1; i<=m; i++) {

       cin >> t1 >> t2 >> t3;

       e[t1][t2]+=t3;

    }

 

    while(bfs()) {

       mmin=inf;

       u=n;

       while(u!=1) {

           mmin=min(mmin,e[father[u]][u]);//最小容量决定流量

           u=father[u];

       }

       sum+=mmin;

       u=n;

       while(u!=1) {

           e[father[u]][u]-=mmin;

           e[u][father[u]]+=mmin;

           u=father[u];

       }

    }

    cout << sum << endl;

    return 0;

}

 

23.LCA的tarjan解法

/*利用并查集优越的时空复杂度,我们可以实现LCA问题的O(n+Q)算法,这里Q表示询问的次数。Tarjan算法基于深度优先搜索的框架,对于新搜索到 的一个结点,首先创建由这个结点构成的集合,再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。其他的LCA询 问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先。之后继续搜索下一棵子树,直到当前结点的所 有子树搜索完。这时把当前结点也设为已被检查过的,同时可以处理有关当前结点的LCA询问,如果有一个从当前结点到结点v的询问,且v已被检查过,则由于 进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v 所在集合的祖先*/

#include <iostream>

#include <algorithm>

 

using namespace std;

int n,m,q;

const int maxn = 200001;

 

struct Edge {

    int u,v,w;

} ques[maxn];

struct Tree {

    int l,r,d;

    Tree() {

       l=r=d=0;

    }

} tr[maxn];

bool flag[maxn]= {false}; //是否访问过该点

struct Edge e[maxn],maxtree[maxn];

int cnt=0;

int from[maxn];

int ans[maxn];

int k=0;

 

//最大生成树

namespace MaxGenTree {

    //并查集

    namespace BingChaJi {

       int father[maxn];

       inline void initbcj() {

           for(int i=1; i<=2*n; i++) father[i]=i;

       }

       inline int findfa(int x) {

           if(x==father[x]) return x;

           else return father[x]=findfa(father[x]);

       }

       inline int merge(int x,int y) {

           int p1=findfa(x);

           int p2=findfa(y);

           if(p1!=p2) father[p2]=p1;

       }

    }

    using namespace BingChaJi;

    inline bool cmp(Edge &a,Edge &b) {

       return a.w>b.w;

    }

    inline void kruskal() {

       for(int i=1; i<=m; i++) {

           int u=e[i].u;

           int v=e[i].v;

           if(findfa(u)!=findfa(v)) {

              merge(u,v);

              maxtree[++k]=e[i];

           }

       }

    }

 

    inline void build() {

       //在最大生成树的每条边里搜索

       for(int i=1; i<=k; i++) {

           int u=findfa(maxtree[i].u);

           int v=findfa(maxtree[i].v);

           int w=maxtree[i].w;

           tr[n+i].l=u;

           tr[n+i].r=v;

           tr[n+i].d=w;

           merge(n+i,u);

           merge(n+i,v);

       }

    }

}

 

using namespace MaxGenTree;

 

void addedge(int u,int v,int w) {

    ques[++cnt].v=from[u];

    ques[cnt].u=v;

    ques[cnt].w=w;

    from[u]=cnt;

}

 

void LCA(int p) {

    flag[p]=true;

    for(int i=from[p]; i!=0; i=ques[i].v) {

       int v=ques[i].u;

       if(flag[v]) {

           ans[ques[i].w]=tr[findfa(v)].d;

       }

    }

    int ls=tr[p].l;//左子树

    int rs=tr[p].r;//右子树

    if(ls) {

       LCA(ls);

       merge(p,ls);

    }

    if(rs) {

       LCA(rs);

       merge(p,rs);

    }

}

 

int main() {

    ios::sync_with_stdio(false);

    cin >> n >> m;

    for(int i=1; i<=m; i++) {

       cin >> e[i].u >> e[i].v >> e[i].w;

    }

    cin >> q;

    for(int i=1; i<=q; i++) {

       int x,y;

       cin >> x >> y;

       addedge(x,y,i);

       addedge(y,x,i);

    }

    sort(e+1,e+m+1,cmp);

    fill(ans,ans+maxn,-1);

    initbcj();

    kruskal();//求最大生成树

    initbcj();

    build();//构建最大生成树

    initbcj();

    LCA(n+k);

    for(int i=1; i<=q; i++) cout << ans[i] << endl;

    return 0;

}

 

24.LCA的倍增解法

/*倍增做法:每次询问O(logN)

deep[i] 表示 i节点的深度, P[i,j] 表示 i 的 2^j 倍祖先

那么就有一个递推式子 P[i,j]=P[P[i,j-1],j-1]

这样子一个O(NlogN)的预处理求出每个节点的 2^k 的祖先

然后对于每一个询问的点对(a, b)的最近公共祖先就是:

先判断是否 deep[a] > deep[b] ,如果是的话就交换一下(保证 a 的深度小于 b 方便下面的操作),然后把b 调到与a 同深度, 同深度以后再把a, b 同时往上调(dec(j)) 调到有一个最小的j 满足P[a,j] != P[b,,j] (a b 是在不断更新的), 最后再把 a, b 往上调 (a = P[a,0], b = P[b,0]) 一个一个向上调直到a = b, 这时 a or b 就是他们的最近公共祖先*/

 

#include<iostream>

#include<cstdio>

#include<cstring>

#include<cmath>

using namespace std;

const int MAXN=1000001;

int n,m,root;

struct node

{

    int u;

    int v;

    int next;

}edge[MAXN];

int num=1;

int head[MAXN];

int deep[MAXN];

int f[MAXN][20];

void edge_add(int x,int y)

{

    edge[num].u=x;

    edge[num].v=y;

    edge[num].next=head[x];

    head[x]=num++;

}

void build_tree(int p)

{

    for(int i=head[p];i!=-1;i=edge[i].next)

    {

        int will=edge[i].v;

        if(deep[will]==0)

        {

            deep[will]=deep[p]+1;

            f[will][0]=p;

            build_tree(will);

        }

    }

}

void initialize_step()

{

    for(int i=1;i<=19;i++)

        for(int j=1;j<=n;j++)

            f[j][i]=f[f[j][i-1]][i-1];

}

int LCA(int x,int y)

{

    if(deep[x]<deep[y])swap(x,y);

    for(int i=19;i>=0;i--)

        if(deep[f[x][i]]>=deep[y]) x=f[x][i];

    if(x==y)return y;

    for(int i=19;i>=0;i--)

        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];

    return f[x][0];

}

 

void read(int & x)

{

    char c=getchar();x=0;

    while(c<'0'||c>'9')c=getchar();

    while(c>='0'&&c<='9')x=x*10+c-48,c=getchar();

}

 

int main(){

    read(n);read(m);read(root);

    for(int i=1;i<=n;i++)head[i]=-1;

    for(int i=1;i<=n-1;i++){

        int x,y;

        read(x);read(y);

        edge_add(x,y);

        edge_add(y,x);

    }

    deep[root]=1;

    build_tree(root);

    initialize_step();

    for(int i=1;i<=m;i++){

        int x,y;

        read(x);read(y);

        printf("%d\n",LCA(x,y));

    }

    return 0;

}

 

25.强联通分量tarjan

#include <iostream>

#include <vector>

#include <stack>

#include <algorithm>

 

using namespace std;

 

int n,m;

const int maxp = 10001;

vector<int> edge[maxp];

int num[maxp]={0};//深搜的访问次序

int low[maxp]={0};//能回溯到最早次序

int idx=0;//索引编号

stack<int> s;

bool instack[maxp];//记录每个点是否在栈中

int cnum=0;//强连通分量个数

vector<int> cr[maxp];//存储合点后的强连通分量

int innum[maxp];//每个点在第几号强连通分量中

int indeg[maxp];//每个强连通分量

 

void tarjan(int u) {

    idx++;

    num[u]=low[u]=idx;

    instack[u]=true;

    s.push(u);

    for(int i=0;i<edge[u].size();i++) {

       int v=edge[u][i];

       if(!num[v]) {

           tarjan(v);

           //取最先访问到的点编号

           low[u]=min(low[u],num[v]);

       } else {

           if(instack[v]) low[u]=min(low[u],num[v]);

       }

    }

    //开始合点

    if(num[u]==low[u]) {

       cnum++;

       int k;

       do {

           k=s.top();

           instack[k]=false;

           cr[cnum].push_back(k);

           innum[k]=cnum;

           s.pop();

       } while(k!=u);

    }

}

 

int main() {

    cin.sync_with_stdio(false);

    cin >> n >> m;

    for(int i=1;i<=m;i++) {

       int a,b;

       cin >> a >> b;

       edge[a].push_back(b);

    }

    for(int i=1;i<=n;i++) {

       //没访问过就开始tarjan合点

       if(!num[i]) tarjan(i);

    }

    for(int u=1;u<=n;u++) {

       for(int i=0;i<edge[u].size();i++) {

           int v=edge[u][i];

           //u,v不在一个强连通分量中

           if(innum[u]!=innum[v]) {

              indeg[innum[u]]++;

           }

       }

    }

    int f=0,nn=0;

    for(int i=1;i<=cnum;i++) {

       if(!indeg[i]) {

           f=i;

           nn++;

       }

    }

    //不能回退到起点

    if(nn!=1) cout << 0;

    else {

       int ans=0;

       for(int i=1;i<=n;i++) {

           if(innum[i]==f) ans++;

       }

       cout << ans;

    }

    return 0;

}

 

26.强连通分量kosaraju

//强连通分量kosaraju算法

/*思路:对一个图进行dfs,生成离开序列,然后将所有边反向,从

序列最大的点开始搜索,搜到的点即和该点在同一个强连通分量中

当这个点找不到其他点时,删除已搜索到的强连通分量,再从剩下

的离开序最大的点开始搜索

*/

#include <iostream>

#include <algorithm>

#include <vector>

 

using namespace std;

 

const int maxp=10001;

vector<int> edge[maxp],redge[maxp];//邻接表和反向邻接表

int cnt[maxp]={0};

int belg[maxp];//存储强连通分量,表示顶点i属于第belg[i]个强连通分量

int lnum[maxp];//结束时间标记,其中lnum[i]表示离开时间为i的顶点

bool flag[maxp];//访问标志

int n,m,index=0,color=0;//index离开时间

 

//生成离开序

void dfs1(int cur) {

    flag[cur]=true;

    for(int i=0;i<edge[cur].size();i++) {

       if(!flag[edge[cur][i]]) {

           dfs1(edge[cur][i]);

       }

    }

    //离开后记录编号

    lnum[++index]=cur;

}

 

//求第cur个点的强连通分量序号

void dfs2(int cur) {

    flag[cur]=true;

    belg[cur]=color;

    for(int i=0;i<redge[cur].size();i++) {

       if(!flag[redge[cur][i]]) dfs2(redge[cur][i]);

    }

}

 

void kosaraju() {//搜索强连通分量

    index=0;

    fill(flag,flag+maxp+1,false);

    for(int i=1;i<=n;i++) {

       if(!flag[i]) dfs1(i);

    }

    fill(flag,flag+maxp+1,false);

    color=0;

    //注意要从离开时间最大的开始搜

    for(int i=index;i>0;i--) {

       if(!flag[lnum[i]]) {

           color++;

           dfs2(lnum[i]);

       }

    }

}

 

int main() {

    cin >> n >> m;

    for(int i=1;i<=m;i++) {

       int a,b;

       cin >> a >> b;

       edge[a].push_back(b);

       redge[b].push_back(a);

    }

    kosaraju();

    for(int cur=1;cur<=n;cur++) {

       //还冇找到尽头,统计第belg[cur]个强连通分量大小

       for(int i=0;i<edge[cur].size();i++) {

           if(belg[cur]!=belg[edge[cur][i]]) cnt[belg[cur]]++;

       }

    }

    int f=0,num=0;

    for(int i=1;i<=color;i++) {

       if(!cnt[i]) {

           f=i;

           num++;

       }

    }

    if(num>1) cout << 0 << endl;

    else {

       int ans=0;

       for(int i=1;i<=n;i++) {

           if(f==belg[i]) ans++;//统计强连通分量个数

       }

       cout << ans << endl;

    }

    return 0;

}

 

27.常用的位运算

(1)判断2的整数幂:if((n&(n-1))==0 && n!=0 && n!=1) //为0则表示是2的整数幂

(2)判断奇偶性:if(n&1)

(3)取绝对值:/* n>>31 取得n的符号,若n为正数,

n>>31等于0,若n为负数,n>>31等于-1

若n为正数 n^0=0,数不变,若n为负数有n^-1

 需要计算n和-1的补码,然后进行异或运算,

结果n变号并且为n的绝对值减1,再减去-1就是绝对值 */

 

inline int absint(int n) {

    return (n>>31 == 0) ? n : (-n);

}

(4)取两个数最小值:return y^((x^y)&-(x<y));

(5)判断两个数是否同号: if((x^y) >= 0),满足条件即同号

(6)交换两个数:

inline void myswap(int &a,int &b) {

    a^=b;

    b^=a;

    a^=b;

}

(7) 获取一个固定位 (假设为右边数第n位)的值

Tmp=x&(1<<(n-1))

获取多个固定位(假设从右边数第n位开始,获取k位)

Tmp=x&((pow(2,k)-1)<<(n-1))

(8)把一个或多个固定位置0(假设从右边数第n位开始,置零k位)

X&=(~((pow(2,k)-1)<<(n-1)))

(9)把一个或多个固定位取反(假设从右边数第n位开始,取反k位)

Tmp=x^((pow(2,k)-1)<<(n-1))

(10) 得到n位全为1的数 (1<<n)-1

(11)将第i位改成1 x=x|(1<<(i-1))

(12)将2进制下最右边的一个1去掉 x=x&(x-1) 戒 x-=(

x&(-x)

 

※附加状态压缩DP例题:

农场主John新买了一块长方形的新牧场,这块牧场被划分

成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方

形的土地。John打算在牧场上的某几格里种上美味的草,

供他的奶牛们享用。

遗憾的是,有些土地相当贫瘠,只能用来种草。并且,奶

牛们喜欢独占一块草地的感觉,于是John只会选择两块相

邻的土地,也就是说,没有哪两块草地有公共边。

 John想知道,如果只考虑草地的总块数,那么,一共有多

少种种植方案可供他选择?(当然,把新牧场完全荒废也

是一种方案)

用f[i][j]代表第i层状态为j时的方案数

 f[i][j]+=f[i-1][k],枚丼j和k

先预处理,得到每一行的所有状态一共 s种 s=2的n次方-1

然后枚举每种状态 看是否符合左右没有相邻的

g[i]=(!(i<<1 & i) && !(i>>1 & i))

在输入的时候考虑哪些地只能种

用t[i]记录 判断的时候 (j&t[i])==j 看是否符合要求

一次枚举上一行的状态 for(int j=0;j<s;j++)

和下一行的状态 for(int k=0;k<s;k++)

判断上下没有相邻的 !(j&k)

#include<cstdio>

#define N 14

#define S (1<<13)+5

int m,n,s,x,ans,f[N][S],t[N],g[S];

int main(){

 

 

scanf("%d%d",&m,&n);

for(int i=1;i<=m;i++)

for(int j=1;j<=n;j++){

scanf("%d",&x);

t[i] = (t[i]<<1) + x;

}

s=1<<n;

for(int i=0;i<s;i++)

g[i]=(!(i<<1 & i) && !(i>>1 & i));

f[0][0]=1;

for(int i=1;i<=m;i++)

for(int j=0;j<s;j++)if(g[j]&&(j&t[i])==j)

for(int k=0;k<s;k++)if(!(j&k))f[i][j]+=f[i-1][k];

for(int i=0;i<s;i++)

ans=(ans+f[m][i])%100000000;

printf("%d\n",ans);

return 0;

}

 

28.拓扑排序

思路:

      1. 以邻接表作存储结构
      2. 设置一个包含n个元素的一维数组indegree,保存AOV网中每个顶点的入度值。
      3. 把邻接表中所有入度为0的顶点进栈
      4. 栈非空时,输出栈顶元素Vj并退栈;在邻接表中查找Vj的直接后继Vk,把Vk的入度减1,即indegree[k]-1;若Vk的入度为0则进栈
      5. 重复上述操作直至栈空为止。若栈空时输出的顶点个数不是n,则有向图有环;否则,拓扑排序完毕

 

#include <iostream>

#include <cstring>

#include <queue>

 

using namespace std;

 

int head[501];//起始点所连边的序号

queue<int> q;

int indeg[501];//每个点的入度

struct Node {

    int to;//终点

    int next;//下一条边的编号(-1表示边已经终结,冇得下一条边)

} edge[2501];

int index=0;

//链式前向星

inline void addedge(int u,int v) {

    edge[index].to=v;

    edge[index].next=head[u];//将本条边的next值指向该起点之前记录的最后一条边

    head[u]=index++;//将该起点的最后一条边变为本边,并对编号no自加以便下一次使用

    indeg[v]++;

}

 

inline void topsort(int n) {

    int id=0;

    int cnt=n;

    while(cnt--) {

       for(int i=1;i<=n;i++) {

           if(indeg[i]==0) {//记下第一个入度为0的点存入队列中

              id=i;

              break;

           }

       }

       q.push(id);

       indeg[id]=-1;

       //找到以后删除这个点所连的边

       for(int i=head[id];i!=-1;i=edge[i].next) {

           int k=edge[i].to;

           indeg[k]--;

       }

    }

    while(!q.empty()) {

       cout << q.front() << " ";

       q.pop();

    }

    cout << endl;

}

 

int main() {

    int n,m;

    memset(indeg,0,sizeof(indeg));

    memset(head,-1,sizeof(head));

    cin >> n >> m;

    for(int i=1; i<=m; i++) {

       int a,b;

       cin >> a >> b;

       addedge(a,b);

    }

    topsort(n);

    return 0;

}

 

29.图的最大团

//冇得边的建边,求补图

#include <iostream>

#include <cstring>

#include <cstdlib>

#include <cstdio>

#include <algorithm>

using namespace std;

 

//二分图:顶点数-最大匹配数

//树:转为2分图

int N, M, mp[105][105];

int ret, cnt, opt[105], st[105];

 

void dfs(int x) {

    if (x > N) { // 如果枚举了所有的节点

       ret = cnt;

       memcpy(opt, st, sizeof (st)); // 用一个更大的极大团替代原有的极大团

       return;

    }

    int flag = true;

    for (int i = 1; i < x; ++i) { // 检测新加入的点是否到团中的其他节点都存在一条边

       if (st[i] && !mp[i][x]) {

           flag = false;

           break;

       }

    }

    if (flag) { // 如果该节点满足在这个团中

       st[x] = 1, ++cnt; // 该节点被加入到完全子图中去

       dfs(x + 1);

       st[x] = 0, --cnt;

    }

    if (cnt+N-x > ret) { // 跳过x节点进行搜索同时进行一个可行性判定

       dfs(x + 1);

    }

}

 

int main() {

    int T, x, y;

    scanf("%d", &T);

    while (T--) {

       ret = cnt = 0;

       scanf("%d %d", &N, &M);

       memset(st, 0, sizeof (st));

       for (int i = 0; i < 105; ++i) {

           fill(mp[i], mp[i]+105, 1);

       }

       while (M--) {

           scanf("%d %d", &x, &y);

           mp[x][y] = mp[y][x] = 0;

       }

       dfs(1);

       printf("%d\n", ret);

       for (int i = 1, j = 0; i <= N; ++i) {

           if (opt[i]) {

              printf(j == 0 ? "%d" : " %d", i);

              ++j;

           }

       }

       puts("");

    }

    return 0;

}

 

30.欧拉路径

//欧拉路径,把所有的边都只走一遍

//思想:将所有边用dfs删掉直到无边可删为止

#include <iostream>

#include <algorithm>

#include <stack>

 

using namespace std;

 

int n;

int edge[501][1501]={0};

int degree[501]={0};//每个点的入度

int maxp=0,minp=1<<30;

stack<int> path;

 

inline void dfs(int cur) {

    for(int i=minp;i<=maxp;i++) {

       if(edge[cur][i]!=0) {

           edge[cur][i]--;

           edge[i][cur]--;

           dfs(i);

       }

    }

    path.push(cur);

}

 

int main() {

    int startp=1;//每个点入度都是偶数

    cin >> n;

    for(int i=1;i<=n;i++) {

       int a,b;

       cin >> a >> b;

       maxp=max(maxp,max(a,b));

       minp=min(minp,min(a,b));

       edge[a][b]++;

       edge[b][a]++;

       degree[a]++;

       degree[b]++;

    }

    //找入度为奇数的点(即起点),且编号最小

    for(int i=minp;i<=maxp;i++) {

       if(degree[i]%2==1) {

           startp=i;

           break;

       }

    }

    dfs(startp);

    while(!path.empty()) {

       cout << path.top() << endl;

       path.pop();

    }

    return 0;

}

31.离散化

/*先说两个函数

unique函数:

去重函数

使用方法:unique (首地址,尾地址);

功能:去除相邻的重复元素(只保留一个),并把重复的元素放在最后;

unique 是返回去重后的尾地址;

lower_bound() 函数,在前闭后开区间进行二分查找

lower_bound() 是返回>=val 的位置,当所有元素都小于val,返回last位置;

使用方法:lower_bound(首地址,尾地址,待查找元素val);*/

#include <iostream>

#include <vector>

#include <algorithm>

 

using namespace std;

const int N=5e5+10;

int n;

int arr[N];

vector<int> xs;

 

int main() {

    cin >> n;

    for(int i=1; i<=n; i++) {

       cin >> arr[i];

       xs.push_back(arr[i]);

    }

    sort(xs.begin(),xs.end());

    vector<int>::iterator e=unique(xs.begin(),xs.end());

    for(int i=1; i<=n; i++) arr[i]=lower_bound(xs.begin(),e,arr[i])-xs.begin()+1;

    for(int i=1; i<=n; i++) cout << arr[i] << " ";

    return 0;

}

 

32.树形动态规划

一.多叉树变二叉树

 

这个技巧其实也有两种具体的方法:树的孩子兄弟表示法与dfs序法。

 

1.树的孩子兄弟表示法。

   大家在学习树形结构时一定接触了一个多叉树变二叉树的方法,就是把每个点与它的第一个儿子连边,然后将它的儿子依次连接起来。可以结合下面的图片理解这句话。

总结成口诀就是:第一个儿子是左子树,其他儿子是左子树的右子树(似乎并不押韵,手动滑稽)

 

2.dfs序法

dfs序就是对一个树进行一个dfs,然后对于每个点记录一个时间戳dfn数组,这个时间戳就是这个节点的dfs序,然后我们再记录一个size数组,表示以每个节点为根的子树中节点的数量。

假设根节点是u,那么可以容易的推出

第一个儿子的dfs序dfn[first_son]就是dfn[u]+1

第二个儿子的dfs序dfn[second_son]就是dfn[u]+size[first_son]+1

其余同理。

那么u的下一个兄弟的dfs序dfn[next_brother]就是dfn[u]+size[u]+1

这两个方法大多用于树形依赖形背包(即使用一个节点必须要使用它所有的祖先),

主要解决点权问题

主要作用就是可以使决策转移的过程变成O(1)的了。

最常见的模型就是:有n个物品,有一个m大小的包,每个物品有wi物重与vi物品价值,物品之间存在只有装了物品a,才能装物品b的n-1条关系(就是一个树)。问能取得的最大价值。

简要分析:显然是一个多叉树,考虑转换。

1.孩子兄弟表示法:对于一个节点i,设dp[i][j]表示在以i为根的子树中,用大小为j的包能取得的最大价值,那么dp[i][j]=max(dp[left[i]][j-w[i]]+v[i],dp[right[i]][j])

注意,这里的left[i]是i在原树中的第一个儿子,right[i]是i在原树中的下一个兄弟。

这个方程是非常好理解的。效率就是O(nm)的。

2.dfs序法:对于一个dfs序为i的节点u,同样设dp[i][j]表示在以u为根的子树中,用大小为j的包能取得的最大价值,那么dp[i][j]+v[i]->dp[i+1][j-w[i]]

dp[i][j]->dp[i+size[i]+1][j]

注意,这里的转移并不是常见的dp[i][j]=max()....(用dp[i][j]的前驱状态去计算dp[i][j]),而是用dp[i][j]去更新它的后继状态。这种方法被称为刷表法。

两种方法都是非常巧妙的。但作用也是有限的,只能解决依赖性背包中的点权问题。

二.分组的树形背包。

这类问题也是有一个常见模型的,具体可参考洛谷P1272重建道路。

下面针对这道题来分析,能够解决多叉树的,分组的树形背包。

此时,我们的儿子与父亲之间并不存在依赖型关系,那么我们设dp[k][i][j]表示以i为根的子树,在前k个儿子中,分离出一个大小为j的子树(必须包含i),所需要最少的操作次数。

那么我们每计算到第k+1个新的儿子v时(full_son[v]表示v的儿子个数),

dp[k+1][i][j]=min(dp[k][i][j-t]+dp[full_son[v]][v][t]);

由于一个树形关系,我们需要在一个dfs上进行dp,即先dfs(v),然后更新dp[k+1][i][j]。

这个k的一维显然可以用滚动数组优化掉。

那么就是

j=m->1

t=1->j

dp[i][j]=min(dp[i][j-t]+dp[v][t]);

同时,dp一律要注意初始化,即刚开始时所有的dp[i][1]=du[i](du[i]表示与i连边的节点数,又称i的入度(树是无向边!))

  1. #include<cstdio> 
  2. #include<algorithm> 
  3. #include<cstring> 
  4. using namespace std; 
  5. const int INF=0x3f3f3f3f; 
  6. const int N=201; 
  7. struct Edge{ 
  8.     int to,next; 
  9. }e[N*2]; 
  10. int du[N],a[N],dp[N][N]; 
  11. int n,k,res=INF,EdgeCnt=0; 
  12. void addedge(int u,int v){ 
  13.     int p=++EdgeCnt; 
  14.     e[p].to=v;e[p].next=a[u]; 
  15.     a[u]=p; 
  16. void dfs(int u,int fa){ 
  17.     dp[u][1]=du[u]; 
  18.     for (int p=a[u];p;p=e[p].next){ 
  19.         int v=e[p].to; 
  20.         if (v!=fa){ 
  21.             dfs(v,u); 
  22.             for (int j=k;j>=1;j--) 
  23.                 for (int k=1;k<=j;k++) 
  24.                     dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-2); 
  25.         } 
  26.     } 
  27.     res=min(res,dp[u][k]); 
  28. int main(){ 
  29.     scanf("%d%d",&n,&k); 
  30.     memset(dp,0x3f,sizeof(dp)); 
  31.     for (int i=1;i<n;i++){ 
  32.         int u,v; 
  33.         scanf("%d%d",&u,&v); 
  34.         addedge(u,v); 
  35.         addedge(v,u); 
  36.         du[u]++;du[v]++; 
  37.     } 
  38.     dfs(1,0); 
  39.     printf("%d",res); 
  40.     return 0; 

 

33.线段树区间修改

#include <iostream>

#include <algorithm>

#include <cstdio>

 

//延迟标记区间乘法操作,处理先乘后加;区间加法操作只加;乘法延迟标记不乘区间长度,加法延迟标记要乘区间长度;加法、乘法要分开写

using namespace std;

 

typedef long long LL;

LL p;

const LL MAXN=300000;

LL arr[MAXN+1];

 

struct segtree {

    LL val;

    LL addmark;

    LL mulmark;

} st[2*MAXN];

 

 

 

inline void build(LL root,LL begin,LL end) {

    st[root].mulmark=1;

    st[root].addmark=0;

    if(begin==end) st[root].val=arr[begin];

    else {

       LL mid=(begin+end)>>1;

       build(root*2,begin,mid);

       build(root*2+1,mid+1,end);

       st[root].val=(st[root*2].val+st[root*2+1].val)%p;

    }

}

 

//下移加法延迟标记

inline void pushdown1(LL root,LL begin,LL end) {

    if(st[root].addmark!=0) {

       st[root*2].addmark=(st[root*2].addmark+st[root].addmark)%p;

       st[root*2+1].addmark=(st[root*2+1].addmark+st[root].addmark)%p;

       LL mid=(begin+end)>>1;

       //要变 !!!!!!!!!!!!!!!!!!!!!

       st[root*2].val=(st[root*2].val+st[root].addmark*(mid-begin+1)%p)%p;//要乘以左区间结点数(区间长度)

       st[root*2+1].val=(st[root*2+1].val+st[root].addmark*(end-mid)%p)%p;//要乘以右区间结点数(区间长度)

       st[root].addmark=0;

    }

}

 

//下移乘法延迟标记

inline void pushdown2(LL root,LL begin,LL end) {

    if(st[root].mulmark!=1) {

       st[root*2].mulmark=(st[root*2].mulmark*st[root].mulmark)%p;

       st[root*2+1].mulmark=(st[root*2+1].mulmark*st[root].mulmark)%p;

       st[root*2].addmark=(st[root*2].addmark*st[root].mulmark)%p;

       st[root*2+1].addmark=(st[root*2+1].addmark*st[root].mulmark)%p;

       LL mid=(begin+end)>>1;

       st[root*2].val=(st[root*2].val*st[root].mulmark)%p;

       st[root*2+1].val=(st[root*2+1].val*st[root].mulmark)%p;

       st[root].mulmark=1;//注意乘法延迟标记初始值一定是1,否则乘以0以后的值都是0鸟

    }

}

 

//[ub,ue]表示要修改的加区间和

void upd_area_add(LL root,LL nowb,LL nowe,LL ub,LL ue,LL addval) {

    if(ub>nowe || ue<nowb) return;

    if(ub<=nowb && ue>=nowe) {

       pushdown2(root,nowb,nowe);

       pushdown1(root,nowb,nowe);

       st[root].addmark=(st[root].addmark+addval)%p;

       st[root].val=(st[root].val+addval*(nowe-nowb+1)%p)%p;//注意更新多个结点!

       return;

    }

    pushdown2(root,nowb,nowe);

    pushdown1(root,nowb,nowe);

    LL mid=(nowb+nowe)>>1;

    upd_area_add(root*2,nowb,mid,ub,ue,addval);

    upd_area_add(root*2+1,mid+1,nowe,ub,ue,addval);

    st[root].val=(st[root*2].val+st[root*2+1].val)%p;//push_up

}

 

//[ub,ue]表示要修改的乘区间和

inline void upd_area_mul(LL root,LL nowb,LL nowe,LL ub,LL ue,LL mulval) {

    if(ub>nowe || ue<nowb) return;

    if(ub<=nowb && ue>=nowe) {

       pushdown2(root,nowb,nowe);

       pushdown1(root,nowb,nowe);

       st[root].mulmark=(st[root].mulmark*mulval)%p;

       st[root].val=(st[root].val*mulval)%p;

       return;

    }

    pushdown2(root,nowb,nowe);

    pushdown1(root,nowb,nowe);

    LL mid=(nowb+nowe)>>1;

    upd_area_mul(root*2,nowb,mid,ub,ue,mulval);

    upd_area_mul(root*2+1,mid+1,nowe,ub,ue,mulval);

    st[root].val=(st[root*2].val+st[root*2+1].val)%p;//push_up

}

 

inline LL query(LL root,LL nowb,LL nowe,LL qb,LL qe) {

    if(qb>nowe || qe<nowb) return 0;

    if(qb<=nowb && qe>=nowe) return st[root].val;

    pushdown2(root,nowb,nowe);

    pushdown1(root,nowb,nowe);

    LL mid=(nowb+nowe)>>1;

    LL p1=query(root*2,nowb,mid,qb,qe);

    LL p2=query(root*2+1,mid+1,nowe,qb,qe);

    return (p1+p2)%p;

}

 

int main() {

    LL n,m;

    freopen("p3373.in","r",stdin);

    freopen("p3373.out","w",stdout);

    cin >> n >> m >> p;

    for(LL i=1; i<=n; i++) cin >> arr[i];

    build(1,1,n);//一定记得要构建线段树

    for(LL i=1; i<=m; i++) {

       LL opt,x,y;

       cin >> opt >> x >> y;

       if(opt==1) {

           LL k;

           cin >> k;

           upd_area_mul(1,1,n,x,y,k);

       } else if(opt==2) {

           LL k;

           cin >> k;

           upd_area_add(1,1,n,x,y,k);

       } else cout << query(1,1,n,x,y) << endl;

    }

    fclose(stdin);

    fclose(stdout);

    return 0;

}

 

34.主席树(可持久化线段树)

//主席树

#include <iostream>

#include <algorithm>

 

using namespace std;

 

const int MAXN=200001;

int n,ques,a[MAXN],data[MAXN],siz;

int tmp[MAXN];

 

struct Node {

    int sum;//每个结点代表的区间内存在多少个数

    Node *ls,*rs;

    Node() : sum(0),ls(NULL),rs(NULL) {};

} pool[MAXN*20],*root[MAXN];

 

inline Node *newNode() {

    static int cnt=0;

    return &pool[cnt++];

}

 

//构造一个值域为[l,r]的权值线段树,表示在[l,r]内出现的树有几个

inline Node *build(int l,int r) {

    Node *rt = newNode();

    int mid=(l+r)>>1;

    if(l<r) {

       rt->ls=build(l,mid);

       rt->rs=build(mid+1,r);

    }

    return rt;

}

 

inline void update(Node *cur,Node *fa,int l,int r,int x) {

    cur->sum=fa->sum+1;

    if(l<r) {

       int mid=(l+r)>>1;

       if(x<=mid) {

           cur->ls=newNode();//需要修改的结点新建

           cur->rs=fa->rs;//可持久化的思想,复制

           update(cur->ls,fa->ls,l,mid,x);

       } else {

           cur->ls=fa->ls;//可持久化的思想,复制

           cur->rs=newNode();//需要修改的结点新建

           update(cur->rs,fa->rs,mid+1,r,x);

       }

    }

}

 

//查[l,r]内第k大的数的下标

inline int query(Node *cur,Node *fa,int l,int r,int k) {

    if(l<r) {

       int mid=(l+r)>>1;

       int s=cur->ls->sum-fa->ls->sum;//现在版本减去历史版本的值

       if(k<=s) return query(cur->ls,fa->ls,l,mid,k);

       else return query(cur->rs,fa->rs,mid+1,r,k-s);//减去排名再搜

    } return l;

}

 

int main() {

    cin >> n >> ques;

    for(int i=1; i<=n; i++) {

       cin >> a[i];

       tmp[i]=a[i];

    }

    sort(tmp+1,tmp+1+n);//下面是离散化a[i]数组至data[i]

    siz=unique(tmp+1,tmp+1+n)-(tmp+1);//去重后的尾地址-首地址

    root[0]=build(1,siz);

    for(int i=1; i<=n; i++) {

       int data=lower_bound(tmp+1,tmp+1+siz,a[i])-tmp;

       root[i]=newNode();

       update(root[i],root[i-1],1,siz,data);

    }

    for(int i=1; i<=ques; i++) {

       int l,r,k;

       cin >> l >> r >> k;

       //[1,r]-[1,l-1]前缀和思想

       cout << tmp[query(root[r],root[l-1],1,siz,k)] << endl;

    }

    return 0;

}

 

35.树套树

#include <cstdio>

#include <iostream>

 

using namespace std;

 

const int MAXN = 100005;

 

int a[MAXN], minV[MAXN], maxV[MAXN], dp[MAXN];

 

namespace DS {

 

struct inNode {//y

    int val;

    inNode *ls, *rs;

    inNode(): val(0), ls(NULL), rs(NULL) { }

} inPool[MAXN<<7];

 

struct outNode {//x

    inNode *root;

    outNode *ls, *rs;

    outNode(): root(NULL), ls(NULL), rs(NULL) { }

} outPool[MAXN<<1], *root;

 

inNode *newInNode() {

    static int cnt = 0;

    return &inPool[cnt++];

}

 

outNode *newOutNode() {

    static int cnt = 0;

    return &outPool[cnt++];

}

 

outNode *build(int l, int r) {

    outNode *cur = newOutNode();

    if(l < r) {

       int mid = (l + r) / 2;

       cur->ls = build(l, mid);

       cur->rs = build(mid + 1, r);

    }

    return cur;

}

 

void insertY(inNode *&cur, int l, int r, int y, int v) {

    if(!cur) cur = newInNode();

    cur->val = max(cur->val, v);

    if(l < r) {

       int mid = (l + r) / 2;

       if(y <= mid) insertY(cur->ls, l, mid, y, v);

       else insertY(cur->rs, mid + 1, r, y, v);

    }

}

 

void insertX(outNode *cur, int l, int r, int x, int y, int v) {

    insertY(cur->root, 1, 100000, y, v);

    if(l < r) {

       int mid = (l + r) / 2;

       if(x <= mid) insertX(cur->ls, l, mid, x, y, v);

       else insertX(cur->rs, mid + 1, r, x, y, v);

    }

}

 

int queryY(inNode *cur, int l, int r, int y1, int y2) {

    if(!cur) return 0;

    if(y1 <= l && y2 >= r) return cur->val;

    int mid = (l + r) / 2;

    int res = 0;

    if(y1 <= mid) res = max(res, queryY(cur->ls, l, mid, y1, y2));

    if(y2 > mid) res = max(res, queryY(cur->rs, mid + 1, r, y1, y2));

    return res;

}

 

int queryX(outNode *cur, int l, int r, int x1, int x2, int y1, int y2) {

    if(x1 <= l && x2 >= r) return queryY(cur->root, 1, 100000, y1, y2);

    int mid = (l + r) / 2;

    int res = 0;

    if(x1 <= mid) res = max(res, queryX(cur->ls, l, mid, x1, x2, y1, y2));

    if(x2 > mid) res = max(res, queryX(cur->rs, mid + 1, r, x1, x2, y1, y2));

    return res;

}

 

void init() {

    root = build(1, 100000);

}

 

void insert(int x, int y, int z) {

    insertX(root, 1, 100000, x, y, z);

}

 

int queryMax(int x1, int y1, int x2, int y2) {

    return queryX(root, 1, 100000, x1, x2, y1, y2);

}

}

 

int main() {

    int n, m;

    scanf("%d %d", &n, &m);

    for(int i = 1; i <= n; i++) {

       scanf("%d", &a[i]);

       minV[i] = maxV[i] = a[i];

    }

    while(m--) {

       int x, y;

       scanf("%d %d", &x, &y);

       minV[x] = min(minV[x], y);

       maxV[x] = max(maxV[x], y);

    }

    int ans = 0;

    DS::init();

    for(int i = 1; i <= n; i++) {

       dp[i] = DS::queryMax(1, 1, a[i], minV[i]) + 1;

       DS::insert(maxV[i], a[i], dp[i]);

       ans = max(ans, dp[i]);

    }

    printf("%d\n", ans);

    return 0;

}

 

36.整体二分

第一道整体二分,用了离线处理(貌似网上整体二分的资料不多?),先思考如果只有一个询问,如何二分?很简单嘛,在[L,R]的区间中取M=(L+R)>>1,计算在[L,M]区间中比询问的数大的有多少个。而整体二分,自然要整体,在二分区间的同时将所有询问一起二分处理。下面就是主要的思路

  • 如果二分的区间L==R那么答案在这个区间的全部为L(递归出口1)
  • 如果(l>r)返回(递归出口2)
  • 将询问分类,0类的答案在[L,M],1类的答案在[M+1,R]
  • 如果是增加类型,考虑它的贡献如果v>M,就在[q.l,q.r]中每位添加1,归为1类,否则归为0
  • 如果是查询类型,考虑答案位置,统计区间中比v大的个数,如果v<=s,答案在[M+1,R]中,归为1类,否则答案在[L,M]中,归为0

 

#include<cstdio>

#include<cstring>

#include<algorithm>

#include<iostream>

using namespace std;

#define N 50005

#define lc o*2

#define rc o*2+1

#define done seg.ql=q[i].l,seg.qr=q[i].r

struct SegmentTree{

    int ql,qr;bool clr[N<<2];

    int add[N<<2],sum[N<<2];

 

    inline void init(){sum[1]=add[1]=0,clr[1]=1;}

    inline void updata(int o){sum[o]=sum[lc]+sum[rc];}

    inline void pushdown(int o,int L,int R){

        if(clr[o]){sum[lc]=sum[rc]=add[lc]=add[rc]=0,

            clr[lc]=clr[rc]=1,clr[o]=0;}

        int M=(L+R)>>1;

        if(add[o]){add[lc]+=add[o],add[rc]+=add[o];

            sum[lc]+=(M-L+1)*add[o],sum[rc]+=(R-M)*add[o];add[o]=0;}

    }

    void Add(int o,int L,int R){

        if(ql<=L&&R<=qr){add[o]++,sum[o]+=(R-L+1);return;}

        int M=(L+R)>>1;pushdown(o,L,R);

        if(ql<=M) Add(lc,L,M);if(qr>M) Add(rc,M+1,R);updata(o);

    }

    int Query(int o,int L,int R){

        if(ql<=L&&R<=qr){return sum[o];}

        pushdown(o,L,R);int res=0,M=(L+R)>>1;

        if(ql<=M) res+=Query(lc,L,M);if(qr>M) res+=Query(rc,M+1,R);

        return res;

    }

}seg;

struct qs{int opt,l,r,v,id,k;}q[N];

int n,m;int ans[N];

int cmp(qs a,qs b){return a.k==b.k?a.id<b.id:a.k<b.k;}

void solve(int L,int R,int l,int r){

    if(l>r) return;

    if(L==R){

        for(int i=l;i<=r;i++)

            if(q[i].opt==2) ans[q[i].id]=L;

        return;

    }

    seg.init();

    int M=(L+R)>>1,t=l-1,s;

    for(int i=l;i<=r;i++){

        if(q[i].opt==1){

            if(q[i].v>M){done;seg.Add(1,1,n);q[i].k=1;}

            else{t++,q[i].k=0;}

        }

        else{

            done;s=seg.Query(1,1,n);

            if(q[i].v<=s) q[i].k=1;

            else t++,q[i].k=0,q[i].v-=s;

        }

    }

    sort(q+l,q+r+1,cmp);

    solve(L,M,l,t);solve(M+1,R,t+1,r);

}

inline int in(int x=0,char ch=getchar()){

    while(ch>'9'||ch<'0') ch=getchar();

    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x;

}

int main(){

    n=in(),m=in();

    for(int i=1;i<=m;i++){

        q[i].opt=in(),q[i].l=in(),q[i].r=in(),q[i].v=in(),q[i].id=i;

    }

    memset(ans,-1,sizeof(ans));

    solve(0,n,1,m);

    for(int i=1;i<=m;i++) if(ans[i]!=-1) printf("%d\n",ans[i]);

    return 0;

}

 

37.CDQ分治

CDQ分治是针对操作序列的一种离线算法。

我们要处理的序列区间是[L, R], M = (L + R >> 1)

则我们可以按照下面的分治思想来进行处理

1.递归处理[L, M]

2.处理[L, M]中的操作 对 [M+1, R]中带来的影响

3.递归处理[M+1, R]

cdq分治与一般的分治不同,一般的分治分出来

的子区间是独立的,个个击破即可,而cdq分治分出

来的两个子区间是相互联系的

 

#include <iostream>

#include <cstdio>

#include <cmath>

#include <algorithm>

#include <cstring>

#define maxn 500005

#define maxm 800005

using namespace std;

 

int n,m,cnt,sum[maxn],pos[maxm],ans[maxm];

struct date {

    int op,x,y,v,id;

} qs[maxm];

 

bool comp(date x,date y) {

    return x.x<y.x;

}

 

int lowbit(int x) {

    return x&(-x);

}

 

void add(int x,int y) {

    for (int i=x; i<=n; i+=lowbit(i)) {

       sum[i]+=y;

    }

}

 

int  query(int x) {

    int temp=0;

    for (int i=x; i>0; i-=lowbit(i)) {

       temp+=sum[i];

    }

    return temp;

}

 

void cdq_solve(int l,int r) {

    if (l==r) return;

    int mid=(l+r)/2,temp=0;

    cdq_solve(l,mid),cdq_solve(mid+1,r);

    sort(qs+l,qs+mid+1,comp),sort(qs+mid+1,qs+r+1,comp);

    int i=l,j=mid+1;

    while (j<=r) {

       while (qs[i].op==2&&i<=mid) i++;

       while (qs[j].op==1&&j<=r) j++;

       if (i<=mid&&qs[i].x<=qs[j].x) add(qs[i].y,qs[i].v),i++,temp=i-1;

       else if (j<=r) ans[qs[j].id]+=query(qs[j].y),j++;

    }

    for (int t=l; t<=temp; t++) if (qs[t].op==1) add(qs[t].y,-qs[t].v);

}

 

int main() {

    memset(ans,0,sizeof(ans));

    memset(sum,0,sizeof(sum));

    int op,x1,x2,y1,y2;

    scanf("%d",&n),m=cnt=0;

    for (;;) {

       scanf("%d",&op);

       if (op==1) {

           qs[++m].op=op,qs[m].id=m;

           scanf("%d%d%d",&qs[m].x,&qs[m].y,&qs[m].v);

       } else {

           if (op==2) {

              scanf("%d%d%d%d",&x1,&y1,&x2,&y2);

              pos[++cnt]=m;

              qs[++m].op=op,qs[m].x=x1-1,qs[m].y=y1-1,qs[m].id=m;

              qs[++m].op=op,qs[m].x=x2,qs[m].y=y2,qs[m].id=m;

              qs[++m].op=op,qs[m].x=x1-1,qs[m].y=y2,qs[m].id=m;

              qs[++m].op=op,qs[m].x=x2,qs[m].y=y1-1,qs[m].id=m;

           } else break;

       }

    }

    cdq_solve(1,m);

    for (int i=1; i<=cnt; i++) printf("%d\n",ans[pos[i]+1]+ans[pos[i]+2]-ans[pos[i]+3]-ans[pos[i]+4]);

}

 

38.动态树(LCT)

//lct动态树模版

#include <iostream>

#include <algorithm>

#include <stack>

#define MAXN 300001

 

using namespace std;

 

typedef long long LL;

 

LL val[MAXN],n,m;

 

struct LCT {

    LL ch[MAXN][2];//左右儿子

    LL fa[MAXN];//父节点

    LL res[MAXN];//每个结点xor的结果

    bool rev[MAXN];//是否翻转的延迟标记

    inline bool isRoot(LL x) {

       if(ch[fa[x]][0]==x

               ||ch[fa[x]][1]==x) return true;

       else return false;

    }

    inline void pushup(LL x) {

       res[x] = res[ch[x][0]]^res[ch[x][1]]^val[x];

    }

    inline void rever(LL x) {//翻转一个结点

       swap(ch[x][0],ch[x][1]);

       rev[x]^=1;//改变翻转状态true改false,false改true

    }

    inline void pushdown(LL x) {

       if(rev[x]) {//翻转这个点的作业子树

           LL L=ch[x][0],R=ch[x][1];

           if(L) rever(L);

           if(R) rever(R);

           rev[x]^=1;//消除标记

       }

    }

    //自己画个图就晓得了

    inline void Rotate(LL x) {

       LL y=fa[x];//x的父亲

       LL z=fa[y];//x的父亲的父亲

       LL k=(ch[y][1]==x);//x是y的哪个儿子,0左1右

       LL w=ch[x][k^1];//反儿子

       if(isRoot(y)) {//额外判断y是否是根,此处不判断会引起致命错误

           ch[z][ch[z][1]==y]=x;//Z的原来的Y的位置变为X

       }

       ch[x][k^1]=y;//X的 与X原来相对位置的儿子变成 Y

       ch[y][k]=w;//X的与X原来在Y的相对的那个儿子的反儿子变成Y的儿子

       if(w) {    //反儿子存在

           fa[w]=y;//更新父节点

       }

       fa[y]=x;

       fa[x]=z;

       pushup(y);

    }

    stack<LL> st;//栈,暂存当前点到根的整条路径

    //将x旋转到根

    /*第一种,X和Y分别是Y和Z的同一个儿子

    第二种,X和Y分别是Y和Z不同的儿子

    对于情况一,也就是类似上面给出的图的情况,就要考虑先旋转Y再旋转X

    对于情况二,自己画一下图,发现就是对X旋转两次,先旋转到Y再旋转到X

    */

    inline void splay(LL x) {

       LL y=x,z;

       //pushdown时一定要从上往下放标记

       st.push(y);

       while(isRoot(y)) {

           y=fa[y];

           st.push(y);

       }

       while(!st.empty()) {

           pushdown(st.top());

           st.pop();

       }

       while(isRoot(x)) {

           y=fa[x];

           z=fa[y];

           if(isRoot(y)) {

              bool sta=(ch[y][0]==x)^(ch[z][0]==y);//x,y,z三点是否在一条直线上

              Rotate(sta?x:y);//在一条直线上先旋转x,否则转y

           }

           Rotate(x);

       }

       pushup(x);

    }

    //打通根到x的一条实路径

    inline void access(LL x) {

       LL last=0;

       /*每次把一个节点旋到Splay的根,然后把上一次的Splay的根节

       点当作当前根的右孩子(也就是原树中的下方)第一

       次初始 last=0是为了清除x原来的孩子*/

       while(x!=0) {

           splay(x);

           ch[x][1]=last;

           pushup(x);//更新x因为其孩子改变了

           last=x;

           x=fa[x];

       }

    }

    //把x变成所在树的根

    /*access(x)access(x)后xx在Splay中一定是深度最大的点。

    splay(x)后,xx在Splay中将没有右子树(性质1)。

    于是翻转整个Splay,使得所有点的深度都倒过来了,xx没了左子树,

    反倒成了深度最小的点(根节点),达到了我们的目的*/

    inline void makeroot(LL x) {

       access(x);

       splay(x);

       rever(x);

    }

    inline LL findroot(LL x) {//找根(在真实的树中的)

       access(x);

       splay(x);

       while(ch[x][0]) {

           pushdown(x);

           x=ch[x][0];

       }

       return x;

    }

    inline void split(LL x,LL y) {//抽一个x->y的路径

       makeroot(x);

       access(y);

       splay(y);

    }

    inline void link(LL x,LL y) {//连一个x->y的路径

       makeroot(x);

       if(findroot(y)!=x) fa[x]=y;//注意判断根的合法性

    }

    /*因为分离路径时把x换成了根,所以x的层数比y小,一

    定为y的左孩子*/

    inline void del(LL x,LL y) {//删x->y的路径

       makeroot(x);

       if(findroot(y)==x&& fa[x]==y &&!ch[x][1]) {

           fa[x]=ch[y][0]=0;

           pushup(y);

       }

    }

} lct;

 

int main() {

    cin >> n >> m;

    for(LL i=1; i<=n; i++) cin >> val[i];

    for(LL i=1; i<=m; i++) {

       LL opt,a,b;

       cin >> opt >> a >> b;

       if(opt==0) {

           lct.split(a,b);

           cout << lct.res[b] << endl;

       }

       if(opt==1) lct.link(a,b);

       if(opt==2) lct.del(a,b);

       if(opt==3) {

           //先把x转上去再改,不然会影响Splay信息的正确性

           lct.splay(a);

           val[a]=b;

       }

    }

    return 0;

}

 

39.左偏树(可并堆)

//左偏树模版

#include <iostream>

#include <algorithm>

 

using namespace std;

 

int n,m;

const int maxn = 100010;

 

struct Element { //描述一个左偏树结点

    int value;

    int index;

    bool operator < (const Element &e) const {

       if(value==e.value) return index < e.index;

       else return value < e.value;

    }

 

} e[maxn];

 

//并查集

namespace UnionSet {

    int fa[maxn];

    inline int findfa(int x) {

       return (x==fa[x]) ? x : fa[x]=findfa(fa[x]);

    }

    inline void unit(int x,int y,int father) {

       fa[x]=fa[y]=father;

    }

    inline void phase1() {

       for(int i=0; i<maxn; i++) fa[i]=i;

    }

}

 

inline void zgswap(int &x,int &y) {

    int t;

    t=x;

    x=y;

    y=t;

}

 

//左偏树结构

namespace LeftTree {

    bool del[maxn];//指示哪些结点已经被删除

    struct Node {

       Element v;

       int lc,rc,d;//lc左伢,rc右伢,d代表这个结点的距离(即这个结点到最近外结点路径边数)

    } zg[maxn];

    inline void phase2() {

       for(int i=0; i<n; i++) {

           zg[i].v=e[i];

           zg[i].lc=zg[i].rc=-1;//初始状态是冇得子结点滴

           zg[i].d=0;

       }

    }

    //把两个小根堆合并到一坨,返回的是合并好的堆的序号

    inline int join(int x,int y) {

       if(x==-1) return y;//哪个子树是空的就返回它旁边的子树

       if(y==-1) return x;

       //小根,如果子节点小了要翻上去

       if(zg[y].v<zg[x].v) zgswap(x,y);

       zg[x].rc = join(zg[x].rc,y);//t1的右子树与t2合并

       //不满足左偏性质,调整

       if(zg[x].lc==-1 || zg[zg[x].lc].d<zg[zg[x].rc].d)

           zgswap(zg[x].lc,zg[x].rc);

       if(zg[x].rc==-1) zg[x].d=0;

       else zg[x].d=zg[zg[x].rc].d+1;

       return x;

    }

    inline int expurgate(int x) {//删除这个堆的最小数

       del[x]=true;

       return join(zg[x].lc,zg[x].rc);//把结点的两个子树合并就可以把这个结点删除

    }

}

 

using namespace UnionSet;

using namespace LeftTree;

 

int main() {

    cin >> n >> m;

    for(int i=0; i<n; i++) {

       cin >> e[i].value;

       e[i].index=i;

    }

    phase1();

    phase2();

    while(m--) {

       int opt;

       cin >> opt;

       switch(opt) {

           case 1: {

              int x,y;

              cin >> x >> y;

              x--,y--;

              if(del[x] || del[y]) continue;

              x = findfa(x);

              y = findfa(y);

              if(x==y) continue;

              //合并两个堆

              int idx = join(x,y);

              unit(x,y,idx);

              break;

           }

           case 2: {

              int x;

              cin >> x;

              x--;

              if(del[x]) {

                  cout << -1 << endl;

                  continue;

              }

              x=findfa(x);

              cout << e[x].value << endl;

              int idx=expurgate(x);

              unit(x,idx,idx);

              break;

           }

       }

    }

    return 0;

}

 

40.Splay

※在旋转的过程中,要分三种情况分别处理:

1)Zig 或 Zag

2)Zig-Zig 或 Zag-Zag

3)Zig-Zag 或 Zag-Zig

#include <iostream>

using namespace std;

struct node {

    int key;  //结点键值

    int size;  //以本结点为根的子树的结点数量

    bool lazy;  //懒标记,记录对以本结点为根的子树的操作,false表示不旋转,true表示待旋转

    node *ch[2];  //左右子树指针ch[0]左子树,ch[1]右子树

    void maintain() { //维护结点信息(size)

       size = 1;

       if (ch[0] != NULL) size += ch[0]->size;

       if (ch[1] != NULL) size += ch[1]->size;

    }

    int cmp (int x) { //求在以本结点为根的子树中,排名为x的节点相对于本节点的位置

       int s = 0;

       if (ch[0] != NULL) s = ch[0]->size;

       if (x <= s) return 0; //在左子树

       else if (x == s + 1) return -1; //本结点即为所求

       else return 1;  //在右子树

    }

}*root = NULL; //根节点指针

 

int n, m, i;

int l, r;

int r_x;  //待伸展的总排名为r+1的节点在根节点的右子树中的排名

 

void pushdown (node *p) {

    swap (p->ch[0], p->ch[1]); //交换左右子树

    if (p->ch[0] != NULL) p->ch[0]->lazy ^= 1;

    if (p->ch[1] != NULL) p->ch[1]->lazy ^= 1; //下放到左右子树

    p->lazy = 0; //清空本节点的懒标记

}

void rotate (node *&p, bool f) {//f表示旋转方向,false左转,true右转

    if (p->lazy) pushdown (p); //下放顺序:自上而下

    node *t = p->ch[f ^ 1];

    if (t->lazy) pushdown (t);

    p->ch[f ^ 1] = t->ch[f];

    t->ch[f] = p;

    p->maintain();  //维护顺序:自底向上

    t->maintain();

    p = t;

}

void splay (node *&p, int x) {

    if (p->lazy) pushdown (p); //由于要操作p的子树,故需下放,下面同理

    int d1 = p->cmp (x); //d1:待伸展节点相对于p的位置

    if (d1 == -1 || p->ch[d1] == NULL) return; //若当前节点即为待伸展节点,或d1指向的子树为空,则直接返回

    if (p->ch[d1]->lazy) pushdown (p->ch[d1]);

    int x2;

    if (d1 == 0) x2 = x;

    else {

       if (p->ch[0] == NULL) x2 = x - 1;

       else x2 = x - p->ch[0]->size - 1;

    }  //x2:待伸展节点在d1指向的子树中的排名

    int d2 = p->ch[d1]->cmp (x2); //d2:待伸展节点相对于d1指向的节点的位置

    if (d2 == -1 || p->ch[d1]->ch[d2] == NULL) {

       rotate (p, d1 ^ 1);

       return;

    }  //若d1指向的节点即为待伸展节点,或d2指向的子树为空,则直接将d1指向的节点上旋,然后返回即可

    else {

       int x3;  //在此处,由于splay函数在开始执行时会pushdown,故不需在此处pushdown

       if (d2 == 0) x3 = x2;

       else {

           if (p->ch[d1]->ch[0] == NULL) x3 = x2 - 1;

           else x3 = x2 - p->ch[d1]->ch[0]->size - 1;

       }  //x3:待伸展节点在d2指向的子树中的排名

       splay (p->ch[d1]->ch[d2], x3); //将待伸展节点递归伸展至d2指向的点

       if (d1 == d2) { //一字型旋转

           rotate (p, d1 ^ 1);

           rotate (p, d2 ^ 1);

       } else { //之字形旋转

           rotate (p->ch[d1], d1); //d2^1==d1

           rotate (p, d2); //d1^1==d2

       }

    }

}

void insert (node *&p, int x) {

    if (p == NULL) {

       p = new node;

       p->key = x;

       p->size = 1;

       p->lazy = 0;

       p->ch[0] = p->ch[1] = NULL;

       return;

    }  //新建节点

    else {

       if (p->lazy) pushdown (p); //由于要操作p的子树,故需下放

       insert (p->ch[1], x); //由于按左右顺序排名,故需插入至最右端

       p->size++;  //维护本节点信息

    }

}

void travel (node *p) {//遍历并输出整颗树的所有结点(按二叉查找树顺序输出)

    if (p->lazy) pushdown (p); //先进行下放,于是可以得到正确的顺序,然后遍历即可

    if (p->ch[0] != NULL) travel (p->ch[0]); //递归遍历左子树

    printf ("%d ", p->key); //遍历本节点

    if (p->ch[1] != NULL) travel (p->ch[1]); //递归遍历右子树

}

 

int main() {

    scanf ("%d%d", &n, &m);

    for (i = 1; i <= n; i++) {

       insert (root, i);

       splay (root, i);

    }  //插入并伸展

    for (i = 1; i <= m; i++) {

       scanf ("%d%d", &l, &r);

       if (l > 1 && r < n) { //一般情况

           splay (root, l - 1);

           r_x = r;

           if (root->ch[0] != NULL) r_x -= root->ch[0]->size; //计算r_x

           splay (root->ch[1], r_x); //已将待翻转区间提取至root->ch[1]->ch[0]

           root->ch[1]->ch[0]->lazy ^= 1; //打标记

       } else if (l == 1 && r == n) root->lazy ^= 1; //若待翻转区间为整个序列,则只需将根节点打上标记即可

       else {

           if (l == 1) {

              splay (root, r + 1);

              root->ch[0]->lazy ^= 1;

           }  //若待翻转区间为[1,r],且r<n,则将结点r+1伸展至根节点,则根节点的左子树即为待翻转区间

           else {

              splay (root, l - 1);

              root->ch[1]->lazy ^= 1;

           }  //同理

       }

    }

    travel (root); //遍历整棵树

    return 0;

}

 

41.AVL

#include <cstdio>

#include <algorithm>

#include <cmath>

 

using namespace std;

 

struct Node {

    int val;

    int h;//以当前节点为根节点的树的高度

    int bf;//平衡因子(左右子树的ΔH)

    Node *ls,*rs;

};

 

class AvlTree {

    private :

       Node *root;

    public :

       AvlTree() {

           root=NULL;

       }

       inline int height(Node *s) {

           if(s==NULL) return 0;

           else return s->h;

       }

       //计算平衡因子

       inline int BF(Node *s) {

           if(s->ls==s->rs) return 0;

           if(s->ls==NULL) return -(s->rs->h);

           if(s->rs==NULL) return s->ls->h;

           return (s->ls->h)-(s->rs->h);

       }

       inline Node *Lrotate(Node *s) {//左单旋

           Node *b = s->ls;

           s->ls=b->rs;

           b->rs=s;

           s->h=max(height(s->ls),height(s->rs))+1;

           b->h=max(height(b->ls),height(s->rs))+1;

           s->bf=BF(s);

           b->bf=BF(b);

           return b;

       }

       inline Node *Rrotate(Node *s) {//右单旋

           Node *b = s->rs;

           s->rs=b->ls;

           b->ls=s;

           s->h=max(height(s->ls),height(s->rs))+1;

           b->h=max(height(b->ls),height(s->rs))+1;

           s->bf=BF(s);

           b->bf=BF(b);

           return b;

       }

       inline Node *LRrotate(Node *s) {//左右旋转

           s->ls=Rrotate(s->ls);

           s=Lrotate(s);

           return s;

       }

       inline Node *RLrotate(Node *s) {//右左旋转

           s->rs=Lrotate(s->rs);

           s=Rrotate(s);

           return s;

       }

       inline void insert(Node *&s,int val) {

           if(s==NULL) {//空节点

              s = new Node;

              s->h=1;

              s->bf=0;

              s->val=val;

              s->ls=s->rs=NULL;

              return;

           }

           if(val<s->val) insert(s->ls,val);

           else insert(s->rs,val);

           s->h=max(height(s->ls),height(s->rs))+1;

           s->bf=BF(s);

           if(abs(s->bf)>1) {//调平衡树(平衡因子>1)

              if(s->bf>1 && s->ls->bf>0) s=Lrotate(s);//三点共线,向左偏

              if(s->bf<-1 && s->rs->bf<0) s=Rrotate(s);//三点共线,向右偏

              if(s->bf>1 && s->ls->bf<0) s=LRrotate(s);//一左一右

              if(s->bf<-1 && s->rs->bf>0) s=RLrotate(s);//一右一左

           }

       }

       inline void insert(int val) {

           insert(root,val);

       }

       //ismax表示是否找最大值

       inline int search(bool ismax) {

           if(root==NULL) return 0;

           Node *tmp = root;

           if(ismax) {//最右下的点

              while(tmp->rs) tmp=tmp->rs;

           } else {

              while(tmp->ls) tmp=tmp->ls;

           }

           return tmp->val;

       }

};

 

int main() {

    int n;

    AvlTree at;

    scanf("%d",&n);

    for(int i=1;i<=n;i++) {

       int v;

       scanf("%d",&v);

       at.insert(v);

       printf("CurrentMaxValue is %d, CurrentMinValue is %d\n",at.search(true),at.search(false));

    }

    return 0;

}

 

42.Hash

#include <iostream>

#include <string>

 

using namespace std;

 

string s;

typedef unsigned long long ULL;

const int sed = 64;

ULL zhash[10001]={0};

int cnt=0;

 

inline int calc(int index) {

    if(s[index]>='0' && s[index]<='9') return s[index]-'0';

    if(s[index]>='a' && s[index]<='z') return s[index]-'a'+10;

    if(s[index]>='A' && s[index]<='Z') return s[index]-'A'+36;

    return 1;

}

 

inline void stat(int index) {

    for(int i=0;i<s.size();i++) {

       zhash[index]=zhash[index]*sed+calc(i);

    }

    bool differ = true;

    for(int i=1;i<index;i++) {

       if(zhash[i]==zhash[index]) {

           differ=false;

           break;

       }

    }

    if(differ) cnt++;

}

 

int main() {

    int n;

    cin >> n;

    for(int i=1;i<=n;i++) {

       cin >> s;

       stat(i);

    }

    cout << cnt << endl;

    return 0;

}

 

43.树链剖分

//讲解http://blog.sina.com.cn/s/blog_7a1746820100wp67.html

#include <iostream>

#include <vector>

#include <algorithm>

 

using namespace std;

 

const int MAXN = 100005;

vector<int> G[MAXN];//存储图(树)

//size[x]->以x为根的子树节点个数(算自己在内)

//son[x]表示x的重儿子(与x在同一重链上的size值最大的儿子),fa[x]表示x的父节点

//dep[x]表示节点x的深度(根深度为1)

//top[x]表示x所在的链顶端节点

int size[MAXN],son[MAXN]={0},fa[MAXN],dep[MAXN],top[MAXN];

int pos[MAXN],cnt=0,id[MAXN];//cnt代表新编号

//pos[x]表示x与父亲节点的连边在线段树中的位置

int n,m,root,mod;

int w[MAXN];

typedef long long LL;

 

struct node {

    LL l,r,sum,tag;

}tn[MAXN<<2];

 

inline void addedge(int u,int v) {

    G[u].push_back(v);

    G[v].push_back(u);

}

 

inline void pushup(int rt) {

    tn[rt].sum=tn[rt*2].sum+tn[rt*2+1].sum;

}

 

inline void build(int l,int r,int rt) {

    tn[rt].l=l;

    tn[rt].r=r;

    if(l==r) tn[rt].sum=w[id[l]];

    else {

       int mid=(l+r)/2;

       build(l,mid,rt*2);

       build(mid+1,r,rt*2+1);

       pushup(rt);

    }

}

 

inline void pushdown(int rt) {

    if(tn[rt].tag!=0) {

       tn[rt*2].tag=(tn[rt*2].tag+tn[rt].tag)%mod;

       tn[rt*2+1].tag=(tn[rt*2+1].tag+tn[rt].tag)%mod;

       tn[rt*2].sum=(tn[rt*2].sum+(tn[rt*2].r-tn[rt*2].l+1)*tn[rt].tag)%mod;

       tn[rt*2+1].sum=(tn[rt*2+1].sum+(tn[rt*2+1].r-tn[rt*2+1].l+1)*tn[rt].tag)%mod;

       tn[rt].tag=0;

    }

}

 

inline void update(int l,int r,int c,int rt) {

    if(l<=tn[rt].l && r>=tn[rt].r) {

       tn[rt].sum=(tn[rt].sum+(c*(tn[rt].r-tn[rt].l+1)%mod))%mod;//区间更新

       tn[rt].tag+=c%mod;

    } else {

       pushdown(rt);

       int mid=(tn[rt].l+tn[rt].r)/2;

       if(l<=mid) update(l,r,c,rt*2);

       if(r>mid) update(l,r,c,rt*2+1);

       pushup(rt);

    }

}

 

inline LL query(int l,int r,int rt) {

    if(l<=tn[rt].l&&tn[rt].r<=r) return tn[rt].sum;

    else {

        pushdown(rt);

       LL ans=0;

       int mid=(tn[rt].l+tn[rt].r)/2;

       if(l<=mid) ans+=query(l,r,rt*2)%mod;

       if(r>mid) ans+=query(l,r,rt*2+1)%mod;

       return ans%mod;

    }

}

 

 

//找重边

inline void dfs1(int cur,int father,int depth) {

    size[cur]=1;

    son[cur]=0;

    fa[cur]=father;

    dep[cur]=depth;

    for(int i=0;i<G[cur].size();i++) {

       int v = G[cur][i];

       if(v != father) {//不是根节点才可以递归

           dfs1(v,cur,depth+1);

           size[cur]+=size[v];

           if(size[v]>size[son[cur]]) {

              son[cur]=v;

           }

       }

    }

}

 

//连重边为重链

inline void dfs2(int cur,int tp) {

    top[cur]=tp;

    pos[cur]=++cnt;

    id[pos[cur]]=cur;

    if(son[cur]!=0) dfs2(son[cur],top[cur]);//若cur不是叶子节点,top[son[cur]]=top[cur]

    for(int i=0;i<G[cur].size();i++) {

       int v=G[cur][i];

       if(v!=fa[cur] && v!=son[cur]) dfs2(v,v);//top[v]=v,v为cur的轻儿子

    }

}

 

inline LL add(int u,int v,int w,bool out) {

    LL ans=0;

    while(top[u]!=top[v]) {//把u,v移到同一条重链上(过程见ppt)

       if(dep[top[u]]>dep[top[v]]) swap(u,v);

       if(!out) update(pos[top[v]],pos[v],w,1);

       else ans+=query(pos[top[v]],pos[v],1)%mod;

       v=fa[top[v]];

    }

    //移动完成后将两点(含)之间的所有点更新

    if(dep[u]>dep[v]) swap(u,v);

    if(!out) update(pos[u],pos[v],w,1);

    else ans+=query(pos[u],pos[v],1)%mod;

    return ans%mod;

}

 

int main() {

    cin >> n >> m >> root >> mod;

    for(int i=1;i<=n;i++) cin >> w[i];

    for(int i=1;i<n;i++) {

       int u,v;

       cin >> u >> v;

       addedge(u,v);

    }

    //先剖分

    dfs1(root,-1,1);

    dfs2(root,root);

    build(1,n,1);

    for(int i=1;i<=m;i++) {

       int opt,u,v,w;

       cin >> opt;

       if(opt==1) {

           cin >> u >> v >> w;

           add(u,v,w,false);

       }

       if(opt==2) {

           cin >> u >> v;

           cout << add(u,v,0,true)%mod << endl;

       }

       if(opt==3) {

           cin >> u >> w;

           update(pos[u],pos[u]+size[u]-1,w,1);

       }

       if(opt==4) {

           cin >> u;

           cout << query(pos[u],pos[u]+size[u]-1,1)%mod << endl;

       }

    }

    return 0;

}

 

44.树状数组(主要是用来求区间和的,可修改区间内某个点的值)

//树状数组

#include <iostream>

#include <cstdio>

 

#pragma \

GCC optimize("O3")

 

using namespace std;

 

int arr[500001]={0};

int c[500001]={0};//相当于arr[x-lowbit(x)]+....+arr[x]

int n,m;

 

#define lowbit(x) x&(-x)

 

//求x的前缀和(前x个元素之和)

inline int sum(int x) {//O(log(2,n))

    int ret=0;

    while(x>0) {

       ret+=c[x];

       x-=lowbit(x);

    }

    return ret;

}

 

//将第x个数加上val

inline void add(int x,int val) {

    while(x<=n) {

       c[x]+=val;

       x+=lowbit(x);

    }

}

 

int main() {

    freopen("p3374.in","r",stdin);

    freopen("p3374.ans","w",stdout);

    cin >> n >> m;

    for(int i=1;i<=n;i++) {

       cin >> arr[i];

       add(i,arr[i]);

    }

    for(int i=1;i<=m;i++) {

       int opt,x,y,k;

       cin >> opt >> x;

       if(opt==1) {

           cin >> k;

           add(x,k);

       }

       if(opt==2) {

           cin >> y;

           cout << sum(y)-sum(x-1) << endl;

       }

    }

    return 0;

}

 

45.Trie

//trie树模版

#include <iostream>

#include <cstring>

#include <string>

 

using namespace std;

 

typedef struct TrieTree {

    struct trie {

       trie *nxt[26];//最多映射26个字母a->0,b->1,c->2,d->3

       bool visited=false;

       trie() {

           for(int i=0;i<26;i++) nxt[i]=NULL;

       }

    }root;

    inline void ins_str(string ch) { //插入字符串s

       trie *s=&root;

       for(int i=0;i<ch.length();i++) {

           if(s->nxt[ch[i]-'a']==NULL) s->nxt[ch[i]-'a']=new trie;//冇得就要新建一个结点

           s=s->nxt[ch[i]-'a'];

       }

    }

    inline int query(string ch) { //查询字符串s是否存在,存在返回1,重复返回2,冇得返回0

       trie *s=&root;

       int i;

       for(i=0;i<ch.length();i++) {

           if(s->nxt[ch[i]-'a']==NULL) return 0;//找不到

           s=s->nxt[ch[i]-'a'];

       }

       if(s->visited) return 2;//重复查询

       s->visited=true;

       return 1;//首次访问到

    }

    inline bool del_str(string ch) {//从trie删除这个字符串

       trie *s=&root;

       if(query(ch)==0) return false;//找不到就不能删

       for(int i=0;i<ch.size();i++) {

           delete s->nxt[ch[0]-'a'];

           s->nxt[ch[0]-'a']=NULL;

       }

       return true;

    }

};

 

int main() {

    //a->加入,q->查询,d->删除

    string s;

    char c;

    int opt;

    TrieTree t;

    cin >> opt;

    for(int i=1;i<=opt;i++) {

       cin >> c >> s;

       if(c=='a') t.ins_str(s);

       if(c=='q') {

           int ret = t.query(s);

           if(ret==0) cout << "NOT EXIST" << endl;

           else if(ret==1) cout << "OK" << endl;

           else cout << "REPEAT" << endl;

       }

       if(c=='d') t.del_str(s);

    }

    return 0;

}

 

46.Kmp匹配算法

#include <iostream>

#include <string>

 

using namespace std;

 

//算nxt数组其实就是算s子串最长前、后缀(不可以是整个字符串)相等长度

/*

第一位的next值必定为-1

计算第n个字符的next值

1.查看第n-1个字符对应的next值,设为a

2.判断a是否为-1,若为-1,则第n个字符next值为0

3.若不为-1,将第n-1个字符与第a个字符比较

4.如果相同,第n个字符对应的next值为a+1

5.如果不同,令a等于第a个字符的next值,执行第2步

*/

inline void calc_next(string s,int *nxt) {

    nxt[0]=-1;//-1表示不存在这种前后缀

    int k=-1;

    for(int q=1;q<s.length();q++) {

       while(k!=-1 && s[k+1]!=s[q]) k=nxt[k];//下一个不同就变为nxt[k],回溯

       if(s[k+1]==s[q]) k++;

       nxt[q]=k;

    }

}

 

int *kmp(string s,string p) {

    int slen=s.length();

    int plen=p.length();

    int k=-1;//移动位数

    int *nxt=new int[plen];

    calc_next(p,nxt);//一定要计算!!!!!!!!!!!!!!!!!!!!!

    for(int i=0;i<slen;i++) {

       while(k!=-1 && p[k+1]!=s[i]) k=nxt[k];//不匹配向前移

       if(p[k+1]==s[i]) k++;//匹配向后移

       if(k==plen-1) {//k移动到最后,匹配完成

           i=i-(plen-1);

           //cout << i << endl;

           cout << i+1 << endl;

           k=-1;//继续匹配下一个

           //return i-(plen-1);//计算第一个匹配的位置

       }

    }

    //return -1;//不可以匹配

    return nxt;//题目要求输出next数组

}

 

int main() {

    string s,p;

    cin >> s >> p;

    int *r = kmp(s,p);

    //cout << r[i] << " ";

    for(int i=0;i<p.length();i++) cout << r[i]+1 << " ";

    return 0;

}

 

47.AC自动机

※构建失败指针是AC自动机的关键所在,可以说,若没有失败指针,

所谓的AC自动机只不过是Trie树而已。

失败指针原理:

构建失败指针,使当前字符失配时跳转到另一段从root开始每一

个字符都与当前已匹配字符段某一个后缀完全相同且长度最大的

位置继续匹配,如同KMP算法一样,AC自动机在匹配时如果当前字

符串匹配失败,那么利用失配指针进行跳转。由此可知如果跳转,

跳转后的串的前缀必为跳转前的模式串的后缀,并且跳转的新位

置的深度(匹配字符个数)一定小于跳之前的节点(跳转后匹配

字符数不可能大于跳转前,否则无法保证跳转后的序列的前缀与

跳转前的序列的后缀匹配)。所以可以利用BFS在Trie上进行失败

指针求解。

※失败指针利用:

如 果 当 前 指 针 在 某 一 字 符 s [ m + 1 ] 处 失 配 , 即 ( p -

>next[s[m+1]]==NULL),则说明没有单词s[1...m+1]存在,此时,

如果当前指针的失配指针指向root,则说明当前序列的任何后缀

不是是某个单词的前缀,如果指针的失配指针不指向root,则说

明当前序列s[i...m]是某一单词的前缀,于是跳转到当前指针的

失配指针,以s[i...m]为前缀继续匹配s[m+1]。

对于已经得到的序列s[1...m],由于s[i...m]可能是某单词的后

缀,s[1...j]可能是某单词的前缀,所以s[1...m]中可能会出现

单词,但是当前指针的位置是确定的,不能移动,我们就需要

t e m p 临 时 指 针 , 令 t e m p = 当 前 指 针 , 然 后 依 次 测 试

s[1...m],s[i...m]是否是单词。

>>>简单来说,失败指针的作用就是将主串某一位之前的所有可以

与模式串匹配的单词快速在Trie树中找出

在构造完Tire树之后,接下去的工作就是构造失败指针。

构造失败指针的过程概括起来就一句话:设这个节点上的

字母为C,沿着它父亲节点的失败指针走,直到走到一个节

点,它的子结点中也有字母为C的节点。然后把当前节点的

失败指针指向那个字母也为C的儿子。如果一直走到了root

都没找到,那就把失败指针指向root。具体操作起来只需

要:先把root加入队列(root的失败指针指向自己或者

NULL),这以后我们每处理一个点,就把它的所有儿子加入

队列。

观察构造失败指针的流程:对照图来看,首先root的fail指针指向NULL,然后root入队,进入循

环。从队列中弹出root,root节点与s,h节点相连,因为它们是第一层的字符,肯定没有比它层数

更小的共同前后缀,所以把这2个节点的失败指针指向root,并且先后进入队列,失败指针的指向

对应图中的(1),(2)两条虚线;从队列中先弹出h(右边那个),h所连的只有e结点,所以接下来扫

描指针指向e节点的父节点h节点的fail指针指向的节点,也就是root,root->next['e']==NULL,

并且root->fail==NULL,说明匹配序列为空,则把节点e的fail指针指向root,对应图中的(3),然后

节点e进入队列;从队列中弹出s,s节点与a,h(左边那个)相连,先遍历到a节点,扫描指针指向a

节点的父节点s节点的fail指针指向的节点,也就是root,root->next['a']==NULL,并且root-

>fail==NULL,说明匹配序列为空,则把节点a的fail指针指向root,对应图中的(4),然后节点a进入

队列。接着遍历到h节点,扫描指针指向h节点的父节点s节点的fail指针指向的节点,也就是root,

root->next['h']!=NULL,所以把节点h的fail指针指向右边那个h,对应图中的(5),然后节点h进入队列,由此类推

※最后,我们便可以在AC自动机上查找模式串中出现过哪些单词了。匹配过

程分两种情况:(1)当前字符匹配,表示从当前节点沿着树边有一条路径

可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目

标字符串指针移向下个字符继续匹配;(2)当前字符不匹配,则去当前节

点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重

复这2个过程中的任意一个,直到模式串走到结尾为止。

对例子来说:其中模式串为yasherhs。对于i=0,1。Trie中没有对应的路

径,故不做任何操作;i=2,3,4时,指针p走到左下节点e。因为节点e的

count信息为1,所以cnt+1,并且将节点e的count值设置为-1,表示改单

词已经出现过了,防止重复计数,最后temp指向e节点的失败指针所指向

的节点继续查找,以此类推,最后temp指向root,退出while循环,这个

过程中count增加了2。表示找到了2个单词she和he。当i=5时,程序进入

第5行,p指向其失败指针的节点,也就是右边那个e节点,随后在第6行指

向r节点,r节点的count值为1,从而count+1,循环直到temp指向root为

止。最后i=6,7时,找不到任何匹配,匹配过程结束

#include <iostream>

#include <cstring>

#include <string>

#include <queue>

 

using namespace std;

 

struct ACauto {

    struct Node {

       Node *fail;//失败指针

       Node *nxt[26];//trie树每个节点的子节点

       int last=0;//是否是单词最后一个节点,并统计个数

       Node () {

           fail=NULL;

           last=0;

           memset(nxt,NULL,sizeof(nxt));

       }

    }root;

    queue<Node*> q;

   

    inline void add(string str) {

       Node *p = &root;

        int index=0;

       for(int i=0;i<str.length();i++) {

           index=str[i]-'a';

           if(p->nxt[index]==NULL) p->nxt[index]=new Node;

           p=p->nxt[index];

       }

       p->last++;

    }

   

    inline void buildac() {

       Node *r=&root;

       r->fail=NULL;

       q.push(r);

       while(!q.empty()) {

           Node *tmp=q.front();

           Node *p=NULL;//失败指针

           for(int i=0;i<26;i++) {

              if(tmp->nxt[i]!=NULL) {

                  if(tmp==r) tmp->nxt[i]->fail=r;//根节点特殊考虑,失败指针就是它自己

                  else {

                     p=tmp->fail;

                     while(p!=NULL) {

                         if(p->nxt[i]!=NULL) {//失败指针接上有相同前缀的字符串末尾

                            tmp->nxt[i]->fail=p->nxt[i];

                            break;

                         }

                         p=p->fail;

                     }

                     if(p==NULL) tmp->nxt[i]->fail=r;

                  }

                  q.push(tmp->nxt[i]);

              }

           }

       }

    }

   

    int query(string key) {

       int cnt=0,index,len=key.length();

       Node *r=&root;

       Node *p=&root;

       for(int i=0;i<len;i++) {

           index=key[i]-'a';

           while(p->nxt[index]==NULL && p!=r) p=p->fail;//下一个结点找不到就跳转到失败指针查找

           p=p->nxt[index];

           if(p==NULL) p=r;//找不到了

           Node *tmp=p;

           while(tmp!=r) {

              if(tmp->last>=0) {//匹配成功

                  cnt+=tmp->last;

                  tmp->last=-1;//找到了就要做标记防止重复查找

              } else break;

              tmp=tmp->fail;

           }

       }

       return cnt;//匹配长度

    }

};

 

int main() {

    string mod,text1,text2;

    cin >> mod >> text1 >> text2;

    ACauto aa;

    aa.add(mod);

    aa.buildac();

    cout << aa.query(text1) << " " << aa.query(text2) << endl;

    return 0;

}

 

48.树分治

#include <iostream>

#include <cstdio>

#include <cstring>

#include <algorithm>

 

using namespace std;

const int INF=0x3f3f3f3f;

const int maxn=11000;

const int maxm=21111;

struct EdgeNode {

    int to;

    int w;

    int next;

} edges[maxm];

int head[maxn],edge;

bool vis[maxn];

 

void init() {

    edge=0;

    memset(head,-1,sizeof(head));

    memset(vis,0,sizeof(vis));

}

void addedge(int u,int v,int w) {

    edges[edge].w=w,edges[edge].to=v,edges[edge].next=head[u],head[u]=edge++;

}

int n,K;

struct CenterTree {

    int n;

    int ans;

    int siz;

    int son[maxn];

    void dfs(int u,int pa) {

       son[u]=1;

       int res=0;

       for (int i=head[u]; i!=-1; i=edges[i].next) {

           int v=edges[i].to;

           if (v==pa) continue;

           if (vis[v]) continue;

           dfs(v,u);

           son[u]+=son[v];

           res=max(res,son[v]-1);

       }

       res=max(res,n-son[u]);

       if (res<siz) {

           ans=u;

           siz=res;

       }

    }

    int getCenter(int x) {

       ans=0;

       siz=INF;

       dfs(x,-1);

       return ans;

    }

} Cent;

int data[maxn];

int dis[maxn];

int Len;

int ans;

void getArray(int u,int pa) {

    data[++Len]=dis[u];

    for (int i=head[u]; i!=-1; i=edges[i].next) {

       int v=edges[i].to;

       int w=edges[i].w;

       if (v==pa) continue;

       if (vis[v]) continue;

       dis[v]=dis[u]+w;

       getArray(v,u);

    }

}

int calc(int u,int now) {

    dis[u]=now;

    Len=0;

    getArray(u,-1);

    sort(data+1,data+Len+1);

    int res=0;

    int l=1,r=Len;

    while (l<r) {

       if (data[r]+data[l]<=K) {

           res+=(r-l);

           l++;

       } else r--;

    }

    return res;

}

void solve(int u) {

    ans+=calc(u,0);

    vis[u]=true;

    for (int i=head[u]; i!=-1; i=edges[i].next) {

       int v=edges[i].to;

       int w=edges[i].w;

       if (vis[v]) continue;

       ans-=calc(v,w);

       Cent.n=Cent.son[v];

       int rt=Cent.getCenter(v);

       solve(rt);

    }

}

int main() {

    while (~scanf("%d%d",&n,&K)) {

       if (n==0&&K==0) break;

       init();

       for (int i=1; i<n; i++) {

           int x,y,z;

           scanf("%d%d%d",&x,&y,&z);

           addedge(x,y,z);

           addedge(y,x,z);

       }

       ans=0;

       Cent.n=n;

       int root=Cent.getCenter(1);

       solve(root);

        printf("%d\n",ans);

    }

    return 0;

}

 

49.单调队列

#include <cstdio>

#include <queue>

 

using namespace std;

int n,m;

 

struct Node {

    int pos,val;

    inline bool operator < (const Node &m) const {//大的元素放队列头部

        return val>m.val;

    }

};

priority_queue<Node> que;

int v[2000001];

 

int main() {

    scanf("%d%d",&n,&m);

    for(int j=1;j<=n;j++) scanf("%d",&v[j]);

    int i=1;

    while(i<=n) {

        if(i==1) printf("0\n");

        else {

            while(que.top().pos+m<i) que.pop();//头是否在合法区间,队首元素pos超出界限就弹出

            printf("%d\n",que.top().val);

        }

        que.push((Node){i,v[i]});

        i++;

    }

    return 0;

}

猜你喜欢

转载自blog.csdn.net/u012783994/article/details/83240064