支配树学习

利用bfs和lca巧妙的求出DAG上的直接支配点
存储的时候注意
第一张图:正向建图
第二张图:反向建图
第三张图:重构后图(树)
再设虚拟根节点0,连接所有DAG度为0的点
核心环节:利用lca重构树,类似并查集,第三张图有时可省略)
#include<bits/stdc++.h>
#define RD1(x) scanf("%d",&x)
#define RD2(x,y) scanf("%d %d",&x,&y)
#define RD3(x,y,z) scanf("%d %d %d",&x,&y,&z)
#define RDL1(x) scanf("%lld",&x)
#define RDL2(x,y) scanf("%lld %lld",&x,&y)
#define RDL3(x,y,z) scanf("%lld %lld %lld",&x,&y,&z)
#define CLR(x) memset(x,0,sizeof(x))
#define NEG(x) memset(x,0xff,sizeof(x))
#define maxn (100000+10)
#define maxm (3000000+10)
#define lson (rt<<1)
#define rson (rt<<1|1)
#define mod (1000000007)
#define pi acos(-1)
#define eps 1e-14
#define acc ios::sync_with_stdio(false)
#define inf 0x3f3f3f3f
#define INF(x) memset(x,inf,sizeof(x))
#define CLRQ(Q) while(!(Q).empty())(Q).pop()
#define see(x) (cerr<<(#x)<<'='<<(x)<<' ')
#define NL printf("\n")
#define fi freopen("C:\\Users\\Administrator\\Desktop\\in","r",stdin)
#define fo freopen("C:\\Users\\Administrator\\Desktop\\out","w",stdout)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int head1[maxn],pre1[maxm],tot1,to1[maxm];
int head2[maxn],pre2[maxm],tot2,to2[maxm];
int head[maxn],pre[maxm],tot,to[maxm];
int n,in[maxn];
int dep[maxn],p[maxn],f[maxn][30];
int sz[maxn];
queue<int>Q;
void a(int u,int v)
{
    to[++tot]=v,pre[tot]=head[u],head[u]=tot;
}
void a1(int u,int v)
{
    to1[++tot1]=v,pre1[tot1]=head1[u],head1[u]=tot1;
}
void a2(int u,int v)
{
    to2[++tot2]=v,pre2[tot2]=head2[u],head2[u]=tot2;
}
int lca(int x,int y)
{
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;i--)if(dep[x]-(1<<i)>=dep[y])
        x=f[x][i];
    if(x==y)return x;
    //for(int i=0;i<2;i++)see(x),see(y),see(i),see(f[x][i]),see(f[y][i]),NL;
    for(int i=20;i>=0;i--)if(f[x][i]!=f[y][i])
        //see(i),see(x),see(y),see(dep[x]),see(dep[y]),see(f[x][i]),see(f[y][i]),NL,
        x=f[x][i],y=f[y][i];
    return f[x][0];
}
void bfs()
{
    NEG(p);p[0]=0;for(int i=1;i<=n;i++)if(!in[i])
    {
        Q.push(i);p[i]=0;dep[i]=1;
    }
    while(!Q.empty())
    {
        int u=Q.front();Q.pop();
        for(int i=head2[u];i;i=pre2[i])
        {
            int v=to2[i];in[v]--;
            if(!in[v])Q.push(v);
        }
        for(int i=head1[u];i;i=pre1[i])
        {
            int v=to1[i];
            if(p[u]==-1)p[u]=v;
            else p[u]=lca(p[u],v);
        }
        dep[u]=dep[p[u]]+1;
        f[u][0]=p[u];
        for(int i=1;i<=20;i++)
            f[u][i]=f[f[u][i-1]][i-1];
        a(u,p[u]),a(p[u],u);
    }
}
void dfs(int u,int from)
{
    sz[u]=1;
    for(int i=head[u];i;i=pre[i])
    {
        int v=to[i];if(v==from)continue;
        dfs(v,u);sz[u]+=sz[v];
    }
}
int main()
{
    //fi;
    RD1(n);for(int i=1;i<=n;i++)
    {
        int t;while(RD1(t)&&t)
        {//see(t);
            a1(i,t),a2(t,i);in[i]++;
        }
    }
    bfs();
    dfs(0,-1);
    for(int i=1;i<=n;i++)printf("%d\n",sz[i]-1);
    return 0;
}


圆方树学习

圆方树的本质就是去掉环,重构出’等效‘的图






去掉环时保证距离是等效的就行

首先建出仙人掌圆方树(与点双圆方树的区别在于直接连割边,也就是存在圆圆边),然后考虑点u-v的最短路径,显然就是:在圆方树上u-v的路径上的所有边权之和,加上每个环(方点)中连出去的两个点的最短距离。

现在问题就是:如何求出环上两个点的最短路径。考虑这样设定边权,首先显然圆圆边的边权就是原图的边权,然后设一个环在搜索树中深度最小的点为这个环的根,则方圆边的边权是环的根到这个点的最短距离,这个可以在Tarjan的时候直接求出。

但是圆方树问题通常需要在LCA处分圆方点讨论。首先如果LCA是圆点,那么直接做即可。如果是方点,就需要决定要不要走环的另一侧,这个同样直接讨论即可。

#include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=l; i<=r; i++)
using namespace std;

const int N=20010;
int n,m,Q,u,v,w,tot,tim,top,dep[N],len[N],type[N],stk[N];
int dfn[N],low[N],dis[N],lst[N],fa[N][16],sm[N][16];
//dis 维护搜索树根节点到每个节点的距离
//lst 维护一个节点上一次被访问时,到达该节点的边的长度
struct E{
    int cnt,h[N],to[N<<1],nxt[N<<1],val[N<<1];
    void add(int u,int v,int w){ to[++cnt]=v; val[cnt]=w; nxt[cnt]=h[u]; h[u]=cnt; }
}G,G1;

void work(int x,int k){
    tot++; int t; len[tot]=dis[stk[top]]-dis[x]+lst[stk[top]];//环上最早被访问的元素作为环的根,环的总长=dis[环上最后被访问的元素]-dis[根]+lst[根]
    do{
        t=stk[top--];
        int A=dis[t]-dis[x],B=len[tot]-A;
        G1.add(tot,t,min(A,B)); type[t]=(A<=B);
    }while (t!=k);
    G1.add(x,tot,0);
}

void Tarjan(int x,int pre){
    //printf("%d\n",x);
    dfn[x]=low[x]=++tim; stk[++top]=x;
    for (int i=G.h[x],k; i; i=G.nxt[i]){
        if ((k=G.to[i])==pre) continue;
        if (!dfn[k]){
            dis[k]=dis[x]+G.val[i]; Tarjan(k,x);
            //printf("%d %d %d %d\n",x,k,dfn[x],low[k]);
            if (low[k]>dfn[x]) top--,G1.add(x,k,G.val[i]);//提前弹出不和x一个环的节点
            else if (low[k]==dfn[x]) work(x,k);//为什么可以直接弹出整个连通分量?因为仙人掌保证环和环之间没有公共边,一个环只会进栈一次,所以提前出栈
            low[x]=min(low[x],low[k]);
        }else low[x]=min(low[x],dfn[k]),lst[x]=G.val[i];
    }
}

void dfs(int x,int pre){
    for (int i=G1.h[x],k; i; i=G1.nxt[i])
        fa[k=G1.to[i]][0]=x,dep[k]=dep[x]+1,sm[k][0]=G1.val[i],dfs(k,x);
}

int lca(int u,int v){
    if (dep[u]<dep[v]) swap(u,v);
    int t=dep[u]-dep[v],res=0;
    for (int i=15; ~i; i--) if (t&(1<<i)) res+=sm[u][i],u=fa[u][i];
    if (u==v) return res;
    for (int i=15; ~i; i--) if (fa[u][i]!=fa[v][i])
        res+=sm[u][i]+sm[v][i],u=fa[u][i],v=fa[v][i];
    if (fa[u][0]<=n) return sm[u][0]+sm[v][0]+res;
    int A=sm[u][0],B=sm[v][0],mn;
    if (type[u]==type[v]) mn=min(abs(A-B),len[fa[u][0]]-abs(A-B));
        else mn=min(A+B,len[fa[u][0]]-A-B);
    return res+mn;
}

int main(){
    freopen("bzoj2125.in","r",stdin);
    freopen("bzoj2125.out","w",stdout);
    scanf("%d%d%d",&n,&m,&Q); tot=n;
    rep(i,1,m) scanf("%d%d%d",&u,&v,&w),G.add(u,v,w),G.add(v,u,w);
    Tarjan(1,0); dfs(1,0);
    //rep(i,1,tot) printf("%d ",low[i]); puts("");
    rep(j,1,15) rep(i,1,tot)
        fa[i][j]=fa[fa[i][j-1]][j-1],sm[i][j]=sm[i][j-1]+sm[fa[i][j-1]][j-1];
    rep(i,1,Q) scanf("%d%d",&u,&v),printf("%d\n",lca(u,v));
    return 0;
}