NOIP复赛快到了,于是我整理了一份算法模板,以防忘记。本人弱省OI蒟蒻,若有不正确的地方请指出
1.并查集算法
//并查集基本思想:将两个独立的集合合并到一坨(莫忘鸟要判断根节点是否相同)
#include
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
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
#include
#include
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 for(int i=0;i 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 #include #include 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() else if(a.length()==b.length()) { if(a
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 for(int i=0; i 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 #include #include 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 for(int i=0;i 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 #include #include 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 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 #include #include #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() else if(a.length()==b.length()) { if(a
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 for(int i=0; i 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 for(int i=0;i 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 #include #include 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 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 #include 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 #include #include 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 #include #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 #include 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 #include 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 #include using namespace std; const int MAX = 10000; struct EdgeNode { int to;//指向的结点编号 int w;//这两点间的权值 }; vector 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 cin >> a >> b >> w; v[a].push_back({b,w}); } //遍历 for(int i=1;i<=n;i++) { vector 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 #include #include 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< } 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<
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 #include #include #include #include #define FSTREAM 1 using namespace std; struct Edge{ int u,v,w; }; const int inf = 1<<30; int n,m; queue vector 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 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 #include #include 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 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 #include #include using namespace std; const int MAX = 100001; bool prime[MAX]; vector 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 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 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 #include #include using namespace std; struct Edge { int u,v,w; }; vector int n,m; bool cmp(Edge a,Edge b) { return a.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 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 #include #include 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 #include #include 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 #include 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 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=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 #include #include #include 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 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 #include 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 #include #include #include 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] 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 #include #include #include using namespace std; int n,m; const int maxp = 10001; vector int num[maxp]={0};//深搜的访问次序 int low[maxp]={0};//能回溯到最早次序 int idx=0;//索引编号 stack bool instack[maxp];//记录每个点是否在栈中 int cnum=0;//强连通分量个数 vector 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 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 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 #include #include using namespace std; const int maxp=10001; vector 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 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 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 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 (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< (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 和下一行的状态 for(int k=0;k 判断上下没有相邻的 !(j&k) #include #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< for(int i=0;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 for(int k=0;k for(int i=0;i ans=(ans+f[m][i])%100000000; printf("%d\n",ans); return 0; } 28.拓扑排序 思路: #include #include #include using namespace std; int head[501];//起始点所连边的序号 queue 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 #include #include #include #include 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 #include #include using namespace std; int n; int edge[501][1501]={0}; int degree[501]={0};//每个点的入度 int maxp=0,minp=1<<30; stack 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 #include #include using namespace std; const int N=5e5+10; int n; int arr[N]; vector 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 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的入度(树是无向边!)) 33.线段树区间修改 #include #include #include //延迟标记区间乘法操作,处理先乘后加;区间加法操作只加;乘法延迟标记不乘区间长度,加法延迟标记要乘区间长度;加法、乘法要分开写 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 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 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 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 #include 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 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 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 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 #include 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]区间中比询问的数大的有多少个。而整体二分,自然要整体,在二分区间的同时将所有询问一起二分处理。下面就是主要的思路 #include #include #include #include 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 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 #include #include #include #include #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 } 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 #include #include #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 //将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 #include 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 } } 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 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].rc = join(zg[x].rc,y);//t1的右子树与t2合并 //不满足左偏性质,调整 if(zg[x].lc==-1 || zg[zg[x].lc].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 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 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 else { splay (root, l - 1); root->ch[1]->lazy ^= 1; } //同理 } } travel (root); //遍历整棵树 return 0; } 41.AVL树 #include #include #include 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 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 #include 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 zhash[index]=zhash[index]*sed+calc(i); } bool differ = true; for(int i=1;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 #include #include using namespace std; const int MAXN = 100005; vector //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 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 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 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 #include #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 #include #include 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 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 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 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 #include 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 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 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 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 #include #include #include 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 inline void add(string str) { Node *p = &root; int index=0; for(int i=0;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 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 #include #include #include 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 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 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 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 #include using namespace std; int n,m; struct Node { int pos,val; inline bool operator < (const Node &m) const {//大的元素放队列头部 return val>m.val; } }; priority_queue 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
printf("%d\n",que.top().val); } que.push((Node){i,v[i]}); i++; } return 0; }