1.LCA(两个节点的最近公共祖先)
LCA_Tarjan(并查集)(时间复杂度O(n+q))
#include<bits/stdc++.h>
using namespace std;
const int N=40000+5;
struct Edge{
int cnt,x[N],y[N],z[N],nxt[N],fst[N];
void set(){
cnt=0;memset(x,0,sizeof x);memset(y,0,sizeof y);memset(z,0,sizeof z);
memset(nxt,0,sizeof nxt);memset(fst,0,sizeof fst);
}
void add(int a,int b,int c){
x[++cnt]=a;y[cnt]=b;z[cnt]=c;nxt[cnt]=fst[a];fst[a]=cnt;
}
}e,q;
int T,n,m,from,to,dist,in[N],rt,dis[N],fa[N],ans[N];
bool vis[N];
void dfs(int rt){
for (int i=e.fst[rt];i;i=e.nxt[i]){
dis[e.y[i]]=dis[rt]+e.z[i];
dfs(e.y[i]);
}
}
int getf(int k){
return fa[k]==k?k:fa[k]=getf(fa[k]);
}
void LCA(int rt){
for (int i=e.fst[rt];i;i=e.nxt[i]){
LCA(e.y[i]);
fa[getf(e.y[i])]=rt;//直接父代
}
vis[rt]=1;
for (int i=q.fst[rt];i;i=q.nxt[i])
if (vis[q.y[i]]&&!ans[q.z[i]])
ans[q.z[i]]=dis[q.y[i]]+dis[rt]-2*dis[getf(q.y[i])];
}
int main(){
scanf("%d",&T);
while (T--){
q.set(),e.set();memset(in,0,sizeof in);
memset(vis,0,sizeof vis);memset(ans,0,sizeof ans);
scanf("%d%d",&n,&m);
for (int i=1;i<n;i++)
scanf("%d%d%d",&from,&to,&dist),e.add(from,to,dist),in[to]++;
for (int i=1;i<=m;i++)
scanf("%d%d",&from,&to),q.add(from,to,i),q.add(to,from,i);
rt=0;
for(int i=1;i<=n&&rt==0;i++)
if(in[i]==0) rt=i;
dis[rt]=0;
dfs(rt);//更新权值
for(int i=1;i<=n;i++) fa[i]=i;
LCA(rt);//找公共祖父
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
return 0;
}
2.LCA_倍增 时间和空间复杂度分别是O((n+q)log n)和O(n log n)
using namespace std;
const int N=10000+5;
vector <int> son[N];
int T,n,depth[N],fa[N][17],in[N],a,b;
void dfs(int prev,int rt){
depth[rt]=depth[prev]+1;
fa[rt][0]=prev;
for (int i=1;(1<<i)<=depth[rt];i++)
fa[rt][i]=fa[fa[rt][i-1]][i-1];
for (int i=0;i<son[rt].size();i++)
dfs(rt,son[rt][i]);
}
int LCA(int a,int b){
if (depth[a]>depth[b])
swap(a,b);
for (int i=depth[b]-depth[a],j=0;i>0;i>>=1,j++)
if (i&1)
b=fa[b][j];
if (a==b)
return a;
int k;
for (k=0;(1<<k)<=depth[a];k++);
for (;k>=0;k--)
if ((1<<k)<=depth[a]&&fa[a][k]!=fa[b][k])
a=fa[a][k],b=fa[b][k];
return fa[a][0];
}
int main(){
scanf("%d",&T);
while (T--){
scanf("%d",&n);
for (int i=1;i<=n;i++)
son[i].clear();
memset(in,0,sizeof in);
for (int i=1;i<n;i++){
scanf("%d%d",&a,&b);
son[a].push_back(b);
in[b]++;
}
depth[0]=-1;
int rt=0;
for (int i=1;i<=n&&rt==0;i++)
if (in[i]==0)
rt=i;
dfs(0,rt);
scanf("%d%d",&a,&b);
printf("%d\n",LCA(a,b));
}
return 0;
}
3.kmp算法
void getFail(char *P,int *f)//P模板串
{
f[1]=f[0]=0;
int m=strlen(P);
for(int i=1,j;i<m;i++)
{
j=f[i];
while(j&&P[i]!=P[j])
j=f[j];
f[i+1]=P[i]==P[j]?j+1:0;
}
for(int i=1;i<m;i++)
if(P[i+1]==P[f[i+1]])
f[i+1]=f[f[i+1]];
}
void find(char *T,char *P,int *f)//P子串
{
int n=strlen(T);
int m=strlen(P);
int j=0;
for(int i=0;i<n;i++)
{
while(j && T[i]!=P[j]) j=f[j];
if(T[i]==P[j]) j++;
if(j==m) cnt++;//i-m+1: 下标从0开始,能够匹配的初始位置。
}
}
4.回文串最长回文串O(n) manacher算法
#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
const int inf = 1000000000;
int p[N*2],s[N*2],n;
void Manacher() {
int id = 0, maxlen = 0;
for (int i = n;i >= 0;--i) {
s[i + i + 2] = s[i];
s[i + i + 1] = -1;
}
s[0] = -2;
for (int i = 2;i < 2 * n + 1;++i) {
if (p[id] + id > i)p[i] = min(p[2 * id - i], p[id] + id - i);
else p[i] = 1;
while (s[i - p[i]] == s[i + p[i]]) ++p[i];
if (id + p[id] < i + p[i])id = i;
if (maxlen < p[i])maxlen = p[i];
}
//cout << maxlen - 1 << endl;
}
int main()
{
int t,i,j,x,Max,k=1,c;
scanf("%d",&t);
while(t--)
{
Max=0;
memset(p,0,sizeof(p));
scanf("%d",&n);
for(i=0;i<n;i++) scanf("%d",&s[i]);
Manacher();
for(i=3;i<2 * n + 1;i+=2)
if(p[i]-1>Max)
{
c=p[i]-1;
while(c>Max&&p[i+c]<c)
c--;
Max=Max>c?Max:c;
}
printf("Case #%d: %d\n",k++,Max/2*3);
}
return 0;
}
5.RMQ(区间问题处理,O(1)查询,适用于查询次数多的情况)
#include<cstdio>
using namespace std;
const int MAXN=500000+100;
int a[MAXN],b[MAXN];
long long merge_sort(int *a,int *b,int i,int j)//归并排序并返回逆序值,a为待排序的数组,b为辅助空间
{
if(i==j)return 0;
long long ans=0;//逆序值
int mid= (i+j)/2;
ans+=merge_sort(a,b,i,mid);//获取左边的逆序值
ans+=merge_sort(a,b,mid+1,j);//获取右边的逆序值
int p=i,q=mid+1,k=i;
while(p<=mid||q<=j)//获取左边与右边关联的逆序值
{
if(p>mid || (q<=j&&a[p]>a[q]))
{
b[k++]=a[q++];
ans+=mid-p+1;
}
else
{
b[k++]=a[p++];
}
}
for(int k=i;k<=j;k++)a[k]=b[k];
return ans;
}
int main()
{
long long ans;
int n;
while(scanf("%d",&n)==1&&n)
{
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
ans = merge_sort(a,b,0,n-1);
printf("%I64d\n",ans);
}
return 0;
}
6.有向环 拓扑排序
/* 3 2 A<B B<A */
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f;
int a[30][30],f[30],ans[30],n,m;
int query(int x) {
int in[30],k=0,num=0,q,zz=1;
for(int i=1;i<=n;i++) in[i]=f[i];
for(int i=1;i<=n;i++) { k=0;
for(int j=1;j<=n;j++) { if(in[j]==0) {q=j;k++;} }
if(k==0) return 0;
if(k>1) zz=-1;
ans[num++]=q;in[q]--;
for(int s=1;s<=n;s++) { if(a[q][s]) in[s]--; }
}
return zz;
}
int main()
{ int x,y; char ch[5];
while(~scanf("%d%d",&n,&m)){ int ok=0;
if(n==0&&m==0) break;
memset(f,0,sizeof(f)); memset(a,0,sizeof(a));
for(int u=1;u<=m;u++) {
scanf("%s",ch);
if(ok) continue;
x=ch[0]-'A'+1; y=ch[2]-'A'+1;
if(a[x][y]==0) {a[x][y]=1; f[y]++;}
int s=query(n);
if(s==0) {//某一个后产生矛盾
ok=1; printf("Inconsistency found after %d relations.\n",u);
}
if(s==1) {//某一个关系后排好了大小关系
ok=1; printf("Sorted sequence determined after %d relations: ",u);
for(int i=0;i<n;i++) cout<<(char)('A'+ans[i]-1);
cout<<"."<<endl;
}
}//不能确定大小关系
if(ok==0){printf("Sorted sequence cannot be determined.\n");}
}
}
PS:)棋盘问题
n*m个点,一笔画,(1,1)到(n,m),黑白相邻染色,只能不同色走,n,m都是偶数时会是始终点同色,倒置多另一种颜色,所以要建另一种颜色的线,消去多的颜色点数,即可以一笔走到
n*m个点,走回路(1,1)走遍所有点回到(1,1),n*m是偶数时走n*m步,n*m是奇数,只能黑到白,相当于起终点颜色不同,又因为奇数个点,所以颜色数量差为1,要有多的颜色的点之间的连线,以减少多出来的点,构成回路!
2.一笔画
#include<bits/stdc++.h>
#define MAXN 1005
using namespace std;
int N,M,a,b,pre[MAXN],in[MAXN];
vector<int>v;
int fun(int x) {
if(x==pre[x]) return x;
return pre[x]=fun(pre[x]);
}
void change(int x,int y) {
int c=fun(x); int v=fun(y);
if(c==v) return;
pre[x]=y;
}
int main()
{
while(~scanf("%d",&N)) {
if(N==0) break;
for(int i=1;i<=N;i++)
pre[i]=i;
memset(in,0,sizeof(in));
scanf("%d",&M);
for(int i=1;i<=M;i++) {
scanf("%d%d",&a,&b);
change(a,b);
in[a]++;
in[b]++;
}
int ok=1,z=fun(1);
for(int i=1;i<=N;i++) {
if(in[i]&1) ok=0;
if(fun(i)!=z) ok=0;
}
cout<<ok<<endl;//欧拉回路存在则输出1,否则输出0.
}
}
3.欧拉回路:输出点轨迹
#include<bits/stdc++.h>
using namespace std;
#define MAX 20005
int look[MAX*5],n,num,l;
int S[MAX*10];
struct AA{int x,id;}aa;
vector<AA>v[MAX];
int dfs(int x){
for(int i=0;i<v[x].size();i++){
if(look[v[x][i].id]==0) {
look[v[x][i].id]=1;dfs(v[x][i].x);S[num++]=v[x][i].x;
}
}
}
int main()
{
int x,y,n,m;
while(~scanf("%d%d",&n,&m))
{num=0;
memset(S,0,sizeof(S));memset(look,0,sizeof(look));
for(int i=1;i<=MAX;i++)v[i].clear();
while(m--){
scanf("%d%d",&x,&y);
aa.x=y;aa.id=(m+1)*2;v[x].push_back(aa);
aa.x=x;aa.id=(m+1)*2-1;v[y].push_back(aa);
}
int ok=0;dfs(1);cout<<"1";
while(num--){cout<<" "<<S[num];}
cout<<endl;
}
}
4.欧拉回路例题思路
题意:有n个点,m条边,人们希望走完所有的路,且每条道路只能走一遍。至少要将人们分成几组。
解题思路:先用并查集求出所有的连通块,然后判断每个连通块内点的度数,如果有奇数点则需要的组数ans+=奇数点/2;反之,所需组数ans+=1。注意:如果遇到孤立点即度数为0的点则不用算进去,因为没有跟他相连的边。
7.强连通
强连通分支问题A:入度为0的点的个数 问题B:需要添加边的数量,使图强连通 #include<bits/stdc++.h> using namespace std; vector<int>v[105]; stack<int>S; int look[105],low[105],pre[105],ok[105],num,ans[105],k; int dfs(int x) { S.push(x); low[x]=pre[x]=++num; for(int i=0;i<v[x].size();i++){ if(!pre[v[x][i]]){dfs(v[x][i]); low[x]=min(low[x],low[v[x][i]]); } else if(!ans[v[x][i]])low[x]=min(low[x],pre[v[x][i]]); } if(low[x]==pre[x]){ k++; while(1){ int z=S.top();S.pop(); ans[z]=k; if(z==x) break; } } } int main() { int N,i,j,x;scanf("%d",&N); for(i=1;i<=N;i++) { while(scanf("%d",&x)) { if(x==0) break; v[i].push_back(x); } } for(i=1;i<=N;i++){if(!pre[i]) dfs(i); } int in[105],out[105],a=0,b=0; memset(in,0,sizeof(in));memset(out,0,sizeof(out)); for(i=1;i<=N;i++) for(j=0;j<v[i].size();j++){ if(ans[i]!=ans[v[i][j]]) in[ans[v[i][j]]]=out[ans[i]]=1;} if(k==1) printf("1\n0\n"); else{ for(int i=1;i<=k;i++){if(in[i]==0) a++; if(out[i]==0) b++;} printf("%d\n%d\n",a,max(a,b)); } }
-
强连通分量缩点
#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
struct AA{int v,next;}pos[maxn];
int f[maxn],low[maxn],dfn[maxn],stackk[maxn];//最小父代,时间戳,栈
int mm[maxn],in[maxn],look[maxn],ans[maxn],cc[maxn];//新图入度,新图的点位,,新图的点对应的原强连通图内最小的点
int aa,kk,op,num,zz,n,m;
int dfs(int rt){int v;
dfn[rt]=++kk; low[rt]=kk;look[rt]=1;stackk[++zz]=rt;
for(int i=f[rt];i!=-1;i=pos[i].next){v=pos[i].v;
if(!dfn[v]){ dfs(v);low[rt]=min(low[rt],low[v]);}
else{if(look[v]){low[rt]=min(low[rt],low[v]);}}
}
if(dfn[rt]==low[rt]){
in[rt]=++aa;cc[aa]=rt;look[rt]=0;
while(stackk[zz]!=rt){ in[stackk[zz]]=aa;
cc[aa]=min(cc[aa],stackk[zz]);look[stackk[zz--]]=0;
}zz--;
}return 0;
}
int main()
{int u,v;
cin>>n>>m;
memset(f,-1,sizeof(f));
while(m--)
{scanf("%d%d",&u,&v);pos[++op].v=v; pos[op].next=f[u];f[u]=op;}
for(int i=1;i<=n;i++)//缩图{ if(dfn[i]==0){dfs(i);} }
for(int i=1;i<=n;i++)//构建新图{int l;
for(int j=f[i];j!=-1;j=pos[j].next){l=pos[j].v;
if(in[i]!=in[l]){mm[in[l]]++;}
}
}int ss=0;
for(int i=1;i<=aa;i++) {if(mm[i]==0) ans[++ss]=cc[i];}
sort(ans+1,ans+ss+1); cout<<ss<<endl;
for(int i=1;i<ss;i++)
cout<<ans[i]<<" ";
if(ss>0)
cout<<ans[ss]<<endl;
}
8.双连通
求割点:割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点.
原理:若low[v]>=dfn[u],则u为割点。
int tarjan(int x) {
v[x]=1; //点的状态标记,1为已访问,2为割点
Dfn[x]=Low[x]=time++;
for(int i=head[x];i;i=next[i]){
if(!v[ver[i]]){
tarjan(ver[i]);
Low[x]=min(Low[x],Low[ver[i]]);
if(Dfn[x]<=low[ver[i]]) v[x]++;
}
else Low[x]=min(low[x],Dfn[ver[i]]);
if((x==1&&v[x]>2)||(x>1&&v[x]>1)) v[x]=2; //对第一个特判
else v[x]=1;
}
}
2.求桥 桥(割边):删掉它之后,图必然会分裂为两个或两个以上的子图。
原理:若low[v]>dfn[u],则(u,v)为桥。
void tarjan(int x){ v[x]=1;
Dfn[x]=Low[x]=time++;
for(int i=head[x];i;i=next[i])
{
if(!v[ver[i]])
{
p[ver[i]]=edge[i]; //记录父亲边
tarjan(ver[i]);
Low[x]=min(Low[x],Low[ver[i]]);
}
else if(p[x]!=edge[i]) //不是父亲边则更新
Low[x]=min(Low[x],Dfn[ver[i]]);
if(p[x]&&low[x]==dfn[x])
f[p[x]]=1; //为割边
}
}
3.点双连通分量
点双连通分支,在求割点的过程中就能把每个点双连通分支求出。建立一个栈,存储双连通分支。在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。
if(dfn[u]==low[u])
{
scc++;
while(1) //记录每一个点属于的连通块
{
v=sta[top--];
instack[v]=0;
belong[v]=scc; //所取出的点即为双连通分支
if(v==u)
break;
}
}
}
在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。
首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。
统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。
void Tarjan(int u,int fa)
{
int i,v;
low[u]=dfn[u]=++cnt;
sta[++top]=u;
instack[u]=1;
for(i=first[u];i!=-1;i=edge[i].next)
{
v=edge[i].v;
if(i==(fa^1))
continue;
if(!dfn[v])
{
Tarjan(v,i);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
scc++;
while(1) //记录每一个点属于的连通块
{
v=sta[top--];
instack[v]=0;
belong[v]=scc;
if(v==u)
break;
}
}
}for(i=1;i<=n;i++)
{
for(j=first[i];j!=-1;j=edge[j].next)
{
v=edge[j].v;
if(belong[i]!=belong[v])
degree[belong[i]]++; //degree为1则为leaf
}
}
int sum=0;
for(i=1;i<=n;i++)
if(degree[i]==1)
sum++; 统计leaf数
}
9.最短路(两种算法分点多还是路多的情况)
void spfa(int st){ int u,v; queue<int>q;
q.push(st); dis[st]=0;vis[st]=1;
while(!q.empty()) {
u=q.front(); q.pop(); vis[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next) {v=edge[i].v;
if(dis[u]+edge[i].dis<dis[v])
{ dis[v]=dis[u]+edge[i].dis;
if(vis[v]==0){ vis[v]=1;q.push(v); }
}
}
}
}
void work(){
int pos = 0,Min;
memset(vis,0,sizeof(vis));
for(int i = 0; i <= n; i++)
low[i] = Map[pos][i];
vis[pos] = 1;
for(int i = 0; i < n; i++)
{Min = MaxInt;
for(int j = 0; j <= n; j++)
if(!vis[j] && low[j] < Min){pos = j;Min = low[j];}
if(Min == MaxInt)break;
vis[pos] = 1;
for(int j = 0; j <= n; j++)
if(!vis[j] && low[j] > low[pos]+Map[pos][j])
low[j] = low[pos]+Map[pos][j];
}
if(low[s] < MaxInt)
printf("%d\n",low[s]);
else
printf("-1\n");
}
10. 2-SAT
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
const int maxm=maxn*maxn;
struct node{int u,next;}edge[maxm];
int head[maxn],cnt, vis[maxn],dfn[maxn],low[maxn],belong[maxn],id,cnt2;
stack<int>s;
void init(){memset(head,-1,sizeof(head));cnt=0;id=0;
while(!s.empty())s.pop();
cnt2=0;memset(vis,0,sizeof(vis));memset(dfn,-1,sizeof(dfn));
memset(low,-1,sizeof(low)); memset(belong,-1,sizeof(belong));}
void addedge(int a,int b){edge[cnt].u=b;edge[cnt].next=head[a];head[a]=cnt++;}
void tarjan(int now) {dfn[now]=low[now]=id++;vis[now]=1;s.push(now);
for(int i=head[now];i!=-1;i=edge[i].next) { int next=edge[i].u;
if(dfn[next]==-1){ tarjan(next);low[now]=min(low[now],low[next]);}
else if(vis[next]==1){low[now]=min(low[now],dfn[next]);}
}
if(low[now]==dfn[now]){cnt2++;
while(1){ int tmp;tmp=s.top(),s.pop();vis[tmp]=0;belong[tmp]=cnt2;
if( tmp==now ) break;}
}
}
int main(){int n,m;
while(scanf("%d%d",&n,&m)==2 ) {init();int a,b,c,d;
while(m--){
scanf("%d%d%d%d",&a,&b,&c,&d);
addedge(2*a+c,2*b+1-d);addedge(2*b+d,2*a+1-c);
}
for( int i=0;i<2*n;i++) if(dfn[i]==-1) tarjan(i);
bool flag=true;
for(int i=0;i<n;i++){
if(belong[2*i]==belong[2*i+1]){flag=false;break;}
}
if(flag) printf("YES\n"); else printf("NO\n");
}
return 0;
}