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. }
  17. void dfs(int u,int fa){
  18. dp[u][1]=du[u];
  19. for (int p=a[u];p;p=e[p].next){
  20. int v=e[p].to;
  21. if (v!=fa){
  22. dfs(v,u);
  23. for (int j=k;j>=1;j--)
  24. for (int k=1;k<=j;k++)
  25. dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-2);
  26. }
  27. }
  28. res=min(res,dp[u][k]);
  29. }
  30. int main(){
  31. scanf("%d%d",&n,&k);
  32. memset(dp,0x3f,sizeof(dp));
  33. for (int i=1;i<n;i++){
  34. int u,v;
  35. scanf("%d%d",&u,&v);
  36. addedge(u,v);
  37. addedge(v,u);
  38. du[u]++;du[v]++;
  39. }
  40. dfs(1,0);
  41. printf("%d",res);
  42. return 0;
  43. }

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;

}

NOIP常用算法模板相关推荐

  1. 常用算法模板(递归、分治、贪心、动态规划、回溯)

    一.递归算法 递归算法:是指一种通过重复将问题分节为同类的子问题而解决问题的方法.它能解决的问题有①数据的定义是按递归定义的,如斐波拉契数:②问题解法按递归算法实现,如汉诺塔问题:③数据的结构形式是按 ...

  2. 刷题常用算法模板(持续更新)

    目录 1.二分查找 2.线段树 3.树状数组 4.差分数组 5.前缀树 6.并查集 7.AC自动机 8.Morris遍历 9.二叉树非递归遍历 10.KMP 11.Manacher 12.快速选择 b ...

  3. ACM 常用算法模板(膜拜大佬kuangbin)

    Contents 1 字符串处理 5 1.1 KMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ...

  4. [C++ STL] 常用算法总结

    1 概述 STL算法部分主要由头文件<algorithm>,<numeric>,<functional>组成.要使用 STL中的算法函数必须包含头文件<alg ...

  5. C++超详细STL常用算法总结

    STL 常用算法: 写在开篇:整理了一些stl中很常用的算法,涉及到比较,交换,查找,遍历,复制,修改等.值得大嘎收藏呀!! 目录: STL 常用算法: 1. 常用遍历算法: 1.1 for_each ...

  6. kuangbin 最小生成树专题 - ZOJ - 1586 QS Network (朴素 Prim算法 模板题)

    kuangbin 最小生成树专题 - ZOJ - 1586 QS Network (朴素 Prim算法 模板题) 总题单 week 3 [kuangbin带你飞] 题单 最小生成树 + 线段树 Cli ...

  7. 五大常用算法:分治、动态规划、贪心、回溯和分支界定

    算法系列之十六:使用穷举法解猜结果游戏--http://blog.csdn.net/orbit/article/details/7607685 ---------------例子: 麻将PC上发送操作 ...

  8. 黑马C++笔记——STL常用算法

    STL常用算法 1.概述 STL算法主要是由头文件algorithm functional numeric组成 algorithm 是所有STL文件中最大的一个,涉及到比较.交换.查找.遍历.复制.修 ...

  9. 机器视觉工业缺陷检测的那些事(四、常用算法与库)

    机器视觉工业缺陷检测的那些事(四.常用算法与库) 目录 机器视觉工业缺陷检测的那些事(四) 二.算法(预处理算法.检测算法) 常用的图像处理算法: 1.图像变换:(空域和频域.几何变换.色度变换.尺度 ...

最新文章

  1. 2019山东夏令营摸鱼记
  2. CSS你可能还不知道的一些知识点
  3. 最新版谷歌浏览器Chrome45版本性能提升
  4. qt5连接sqlite数据库实例
  5. STM32 IIC详解
  6. 【报告分享】2020直播电商分析报告-抖音VS快手.pdf(附下载链接)
  7. STL的Vector介绍
  8. 深入解读Linux进程调度系列(总览)
  9. HTML5 Web SQL 数据库
  10. MarkDown编辑器实用语法、数学公式汇总
  11. 两款WiFi无线网络扫描工具软件:WirelessMon、Xirrus WiFi Inspector
  12. 关于html5毕业论文设计任务书,毕业论文设计任务书(精选多篇)
  13. 如何注册阿里大于申请签名和短信模板
  14. Android 客户端性能优化(魅族资深工程师毫无保留奉献)
  15. 亚马逊网络关联是什么
  16. 实践任务1:利用 HBuilderX制作产品展示模块+实践任务2:利用 HBuilderX制作公司网站首页+实践任务3: 利用 HBuilderX制作公司网站首页实现固定侧边菜单
  17. C++Primer笔记——拷贝控制
  18. E: 无法定位软件包 mjepgtools
  19. ISCSI linux/windows配置及使用
  20. 关于ETD.sys的系统蓝屏问题的解决

热门文章

  1. SystemUi状态栏客制化功能和常见问题分析
  2. 字符串转数组,并去除掉字符串的中英文引号
  3. 游戏美术行业职位中原画和建模哪个好?
  4. PHP-Laravel简介
  5. 桌面没有计算机的图标,电脑桌面没有图标怎么办
  6. 湖北师范大学计信2018届操作系统实训(参考答案)
  7. 中国博士为什么会没有尊严
  8. MovieClip.setMask()
  9. 修改服务器超时时间,服务器超时时间设置
  10. c#客户端的自动从新连接服务器功能