lca
1.1倍增法求lca
【manim | 算法】7分钟学会倍增法求解LCA_哔哩哔哩_bilibili
递归初始化
void dfs(long u,long father){
dep[u]=dep[father]+1;//只在这里初始化dep
for(long i=1;(1<<i)<=dep[u];i++)
fa[u][i]=fa[fa[u][i-1]][i-1];//只这里用的倍增
for(long i=head[u];~i;i=edge[i].next){
long v=edge[i].to;
if(v==father)continue;
fa[v][0]=u;
dfs(v,u);
}
}
lca主体
long lca(long x,long y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--){//跳到同一个深度
if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
if(x==y)return x;
}
for(int i=20;i>=0;i--){
if(fa[x][i]!=fa[y][i]){//一起跳
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
开始交了几发都不对,以为必须用tarjan等快方法
今天看到输出少了一个,n-1条边,s的父是0,ac了
#include<iostream>
using namespace std;
long n,q,s;
struct EDGE{
long next;
long to;
}edge[1000001];
long head[500001];
long tot=0;
void addedge(long u,long v){
edge[++tot].next=head[u];
edge[tot].to=v;
head[u]=tot;
}
long dep[500001];
long fa[500001][21];//最多跳20次
void dfs(long u,long father){
dep[u]=dep[father]+1;
for(long i=1;(1<<i)<=dep[u];i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(long i=head[u];~i;i=edge[i].next){
long v=edge[i].to;
if(v==father)continue;
fa[v][0]=u;
dfs(v,u);
}
}
long lca(long x,long y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--){
if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
if(x==y)return x;
}
for(int i=20;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
int main(){
cin>>n>>q>>s;
for(long i=1;i<=n;i++)head[i]=-1;
for(long i=1;i<=n-1;i++){
long u,v;cin>>u>>v;
addedge(u,v);addedge(v,u);
}
dfs(s,0);
while(q--){
long a,b;cin>>a>>b;
if(a==b)cout<<a<<endl;
else
cout<<lca(a,b)<<endl;
}
}
1.2Design the city
Design the city - ZOJ 3195 - Virtual Judge (vjudge.net.cn)
先mark着
#include<iostream>
using namespace std;
//连接三个点最短路径
//无向无环
//必然有一个深度最小
//就当图来做,这三个点的最小生成树
//两两找最近公共祖先
-------------------------------
2.1强连通分量
B3609 [图论与代数结构 701] 强连通分量 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
stack<int> s;
int low[10001];int dfn[10001];
bool vis[10001];
int scc[10001];
int block_cnt;
int scc_cnt;
vector<int> scc_zong[10001];
vector<int> G[10001];
int n,m;
void tarjan(int x){
low[x]=dfn[x]=++block_cnt;
s.push(x);
vis[x]=true;
for(int i=0;i<G[x].size();i++){
int v=G[x][i];
if(!vis[v]){
//为什么是low[v]
//如果v是没走过,当场走的话,可以追溯v更新后的
//如果v走过,但不属于联通分量,v就没有追溯的,现在收入
//觉得一样,一回试试low[v]
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(!scc[v]){
low[x]=min(low[x],dfn[v]);
}
}
//这样走过一通后,如果是最栈底的点
if(dfn[x]==low[x]){
scc_cnt++;
while(1){
int temp=s.top();
s.pop();
scc[temp]=scc_cnt;
scc_zong[scc_cnt].push_back(temp);
if(temp==x)break;
}
}
}
void solu(){
for(int i=0;i<=n;i++)vis[i]=0;
cout<<scc_cnt-1;
for(int i=1;i<=n;i++){
if(!vis[i]){
cout<<endl;
vis[i]=1;
cout<<i;
for(int j=i+1;j<=n;j++){
if(scc[i]==scc[j]){
vis[j]=1;
cout<<" "<<j;
}
}
}
}
//想欧拉筛
/*for(int i=1;i<=n;i++){
if(!vis[i]){
int gai_scc=scc[i];
for(int j=0;i<gai_scc[])
}
}*/
/*for(int i=scc_cnt;i>=1;i--){
cout<<endl;
for(int j=scc_zong[i].size()-1;j>=0;j--){
cout<<scc_zong[i][j];
if(j!=0)cout<<" ";
}
}
*/
/*for(int i=1;i<=scc_cnt;i++){if(i!=1)cout<<endl;
for(int j=0;j<scc_zong[i].size();j++){
if(j!=0)cout<<" ";
cout<<scc_zong[i][j];
}*/
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)G[0].push_back(i);
//这样应该就能按顺序遍历1,2,3,点所在的强连通分量了
//啊啊错了
while(m--){
int u,v;
cin>>u>>v;
G[u].push_back(v);
}
tarjan(0);//0不会只追溯到自己的
solu();
}
这里是加了一个万能源点0,从后面模板割点学到了tarjan整图不连通的另一个解决方案
for(int i=1;i<=n;i++)
if(!dfn[x])tarjan(x);
2.2牛的舞会
P2863 [USACO06JAN] The Cow Prom S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
2.3popular cows
2186 -- Popular Cows (poj.org)
P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
2.4可达性
每个入度为0的联通块取一个最小点
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
bool ans[100001];
stack<long> s;
long low[100001];long dfn[100001];
bool vis[100001];
long color[100001];
long block_cnt;
long scc_cnt;
vector<long> scc[100001];
vector<long> G[100001];
long n,m;
void tarjan(long x){
low[x]=dfn[x]=++block_cnt;
s.push(x);
vis[x]=true;
for(long i=0;i<G[x].size();i++){
long v=G[x][i];
if(!vis[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(!color[v]){
low[x]=min(low[x],dfn[v]);
}
}
//这样走过一通后,如果是最栈底的点
if(dfn[x]==low[x]){
scc_cnt++;
while(1){
long temp=s.top();
s.pop();
color[temp]=scc_cnt;
scc[scc_cnt].push_back(temp);
if(temp==x)break;
}
}
}
//有那种能到任意点的,说明暗暗给出图是弱联通
void solve(){
//如果u和v不是一个联通酷爱
//v记录入度
//入度为0的快加上
for(long i=1;i<=scc_cnt;i++){
ans[i]=true;
}
for(long i=1;i<=n;i++){
for(long j=0;j<G[i].size();j++){
long v=G[i][j];
if(color[i]!=color[v]){
ans[color[v]]=0;
}
}
}
long out=0;
for(long i=1;i<=scc_cnt;i++){//i是联通块编号
if(ans[i]){out++;}}
cout<<out<<endl;
//入度为0的块,每个取一个最小的点
bool flag=1;
for(long i=1;i<=scc_cnt;i++){//i是联通块编号
if(ans[i]){
long temp=n;
for(long j=0;j<scc[i].size();j++){
temp=min(temp,scc[i][j]);
}
if(flag==1)flag=0;
else cout<<" ";
cout<<temp;
}
}
}
int main(){
cin>>n>>m;
while(m--){
long u,v;
cin>>u>>v;
G[u].push_back(v);
}
for(long i=1;i<=n;i++){
if(!dfn[i]){tarjan(i);}
}
solve();
}
-------------------------------
模板缩点
P3387 【模板】缩点 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
stl不知道怎么用,先mark
更新:stl会用了,dp不会了
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
bool vis[10001];
int dfn[10001];
int low[10001];
int block_cnt;
stack<int> s;
vector<int> G[10001];
void tarjan(int x){
dfn[x]=low[x]=++block_cnt;
s.push(x);
vis[x]=1;
for(int i=head[x];~i;i=edge[i].next){
int v=edge[i].to;
if(!vis[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(!scc[v]){
low[x]=min(low[x],dfn[v]);
}
}
if(low[x]==dfn[x]){
scc_cnt++;
while(1){
int temp=s.top();
s.pop();
scc[temp]=scc_cnt;
quanscc[scc_cnt]+=quanpoint[temp];
if(temp==x)break;
}
}
}
void suo(){
for(int i=1;i<=n;i++){
for(int j=0;j<G[u].size();j++){
if(scc[i]!=scc[G[i][j]]){
addedge(scc[i],scc[G[i][j]]);
}
}
}
//dp
//状压
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>quanpoint[i];
while(m--){
cin>>u>>v;
G[u].push_back(v);
}
}
-------------------------------
模板割点
P3388 【模板】割点(割顶) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
int n;long m;
stack<int> s;
int block_cnt;
int dfn[20001];
int low[20001];
int root;
bool ge[20001];
vector<int> G[20001];
//割点ge[x]=0
//非root,有儿子,low[v]>=dfn[x]
//root,没有连线
//传参root,就对了
void tarjan(int x,int root){
dfn[x]=low[x]=++block_cnt;
s.push(x);
int ch=0;
for(int i=0;i<G[x].size();i++){
int v=G[x][i];
if(!dfn[v]){
ch++;//如果遍历过,说明是两个子树中间有边的情况
//不是一个v一条边
tarjan(v,root);//ch在tarjan(x)内定义,不会到v内
low[x]=min(low[x],low[v]);
if(x!=root&&low[v]>=dfn[x]){ge[x]=1;}
//无论如何,认为所有v的low都必须》=dfn[x],
//只一个就决定了怎么能行
}
else {
low[x]=min(low[x],dfn[v]);
}
}
if(ch>=2&&root==x){
ge[x]=1;
}
}
int main(){
cin>>n>>m;
while(m--){
int u,v;
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
//root=i;
//root是一个滚动的根
tarjan(i,i);
}
}
int ans=0;
for(int i=1;i<=n;i++){
if(ge[i])ans++;
}
cout<<ans<<endl;
bool flag=1;
for(int i=1;i<=n;i++)
if(ge[i]){
if(flag==1){flag=0;}
else {cout<<" ";}
cout<<i;}
}