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.拓扑排序
思路:
-
-
- 以邻接表作存储结构
- 设置一个包含n个元素的一维数组indegree,保存AOV网中每个顶点的入度值。
- 把邻接表中所有入度为0的顶点进栈
- 栈非空时,输出栈顶元素Vj并退栈;在邻接表中查找Vj的直接后继Vk,把Vk的入度减1,即indegree[k]-1;若Vk的入度为0则进栈
- 重复上述操作直至栈空为止。若栈空时输出的顶点个数不是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的入度(树是无向边!))
- #include<cstdio>
- #include<algorithm>
- #include<cstring>
- using namespace std;
- const int INF=0x3f3f3f3f;
- const int N=201;
- struct Edge{
- int to,next;
- }e[N*2];
- int du[N],a[N],dp[N][N];
- int n,k,res=INF,EdgeCnt=0;
- void addedge(int u,int v){
- int p=++EdgeCnt;
- e[p].to=v;e[p].next=a[u];
- a[u]=p;
- }
- void dfs(int u,int fa){
- dp[u][1]=du[u];
- for (int p=a[u];p;p=e[p].next){
- int v=e[p].to;
- if (v!=fa){
- dfs(v,u);
- for (int j=k;j>=1;j--)
- for (int k=1;k<=j;k++)
- dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-2);
- }
- }
- res=min(res,dp[u][k]);
- }
- int main(){
- scanf("%d%d",&n,&k);
- memset(dp,0x3f,sizeof(dp));
- for (int i=1;i<n;i++){
- int u,v;
- scanf("%d%d",&u,&v);
- addedge(u,v);
- addedge(v,u);
- du[u]++;du[v]++;
- }
- dfs(1,0);
- printf("%d",res);
- 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;
}