BZOJ 5329: [Sdoi2018]战略游戏 圆方树+虚树Dp

title

BZOJ 5329

LUOGU 4606

双倍经验(弱化版):LUOGU 4320 道路相遇

简化题意:

\(T\) 组数据,每组数据有一张无向图,\(q\) 个询问,每次给定一个集合 \(S\),求有多少个点满足删除这个点后,\(S\) 中至少有两个点不连通。

\(1\leqslant T\leqslant 10\)

\(2\leqslant n\leqslant 10^5,且n-1\leqslant m\leqslant 2×10^5\)

\(1\leqslant q\leqslant 10^5\)

对于每组测试数据,有 \(\sum|S|\leqslant 2 × 10^5\)

(弱化版中,\(|S|=2\) ,在本题中也能拿到 \(45pts\))

analysis

不得不说,这道题的模型还是比较好看出来的:无向图联通问题,还是和点有关的,那就想到了 \(tarjan\)求点双,再往下一想,哦,圆方树;每次询问又有关键点集合,那肯定是要虚树了。这样一来,这道题的模型就全部剖析完毕了。


第一种思路:

这种思路比较麻烦,因为上面的东西都要写,把原树的圆方树建出来,然后在圆方树上把每次询问的虚树建出来,然后在虚树上 \(dfs\),把所有 \(dis\) 都累加起来,就 \(OK\) 了。(嘴上 \(AC\) 还是挺简单的)


第二种思路:

这种思路和道路相遇一脉相承,所以先来看一下这个弱化版。

很明显,\(ans=\) 两点在圆方树上圆点的个数 \(=(dep[x]+dep[y]-dep[LCA(x,y)]*2)/2+1\)。

那么战略游戏呢?

很明显就是 \(S\) 在圆方树上虚树的圆点数 \(-|S|\)。

那么考虑把一个圆点的权值放在它到父亲方点的边上,那么把 \(S\) 按 \(dfs\) 序排序后,答案为
\[ \frac{dis(S_1,S_2)+dis(S_2,S_3)+...+dis(S_{n-1},S_n)+dis(S_n,S_1)}{2}-|S| \]
这样的话,其实实际上就不需要把虚树求出来了,代码量大大减少。

code

战略游戏第一种思路:比较毒瘤的圆方树+虚树 \(Dp\),崩溃

#include<bits/stdc++.h>

const int maxn=2e5+10;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }

    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    template<typename T>inline void write(T x)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++='\n';
    }
}

using IO::read;
using IO::write;

template<typename T>inline T min(T a,T b) { return a<b ? a : b; }

struct Graph
{
    int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
    inline void Clear()
    {
        memset(head,0,sizeof(head));
        len=0;
    }

    inline void add(int x,int y)
    {
        ver[++len]=y,Next[len]=head[x],head[x]=len;
    }
}G, A, T;

int n,m,Q,K,ans;

namespace Square
{
    int dfn[maxn],low[maxn],id;
    int Stack[maxn],top,tot;
    inline void tarjan(int x)
    {
        dfn[x]=low[x]=++id;
        Stack[++top]=x;
        for (int i=G.head[x]; i; i=G.Next[i])
        {
            int y=G.ver[i];
            if (!dfn[y])
            {
                tarjan(y);
                low[x]=min(low[x],low[y]);
                if (low[y]>=dfn[x])
                {
                    int k;
                    ++tot;
                    do
                    {
                        k=Stack[top--];
                        A.add(tot,k), A.add(k,tot);
                    } while (k!=y);
                    A.add(tot,x), A.add(x,tot);
                }
            }
            else low[x]=min(low[x],dfn[y]);
        }
    }
}

using namespace Square;

namespace Virtual
{
    int f[maxn][21];
    int dist[maxn],dep[maxn];
    inline void dfs(int x,int fa)
    {
        dfn[x]=++id;
        for (int i=1; i<=20; ++i) f[x][i]=f[f[x][i-1]][i-1];
        for (int i=A.head[x]; i; i=A.Next[i])
        {
            int y=A.ver[i];
            if (y==fa) continue;
            dist[y]=dist[x]+(y<=n);
            f[y][0]=x;
            dep[y]=dep[x]+1;
            dfs(y,x);
        }
    }

    inline int LCA(int x,int y)
    {
        if (dep[x]>dep[y]) std::swap(x,y);
        for (int i=20; i>=0; --i)
            if (dep[y]-(1<<i)>=dep[x]) y=f[y][i];
        if (x==y) return x;
        for (int i=20; i>=0; --i)
            if (f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
        return f[x][0];
    }

    inline bool cmp(int a,int b)
    {
        return dfn[a]<dfn[b];
    }

    inline void insert(int x)
    {
        if (top==1) { Stack[++top]=x; return ; }
        int lca=LCA(Stack[top],x);
        if (lca==Stack[top]) { Stack[++top]=x; return ; }
        while (dfn[Stack[top-1]]>=dfn[lca] && top>1)
        {
            T.add(Stack[top-1],Stack[top]), --top;
        }
        if (lca!=Stack[top]) T.add(lca,Stack[top]), Stack[top]=lca;
        Stack[++top]=x;
    }

    int is[maxn];
    inline void build(int *c,int k)
    {
        std::sort(c+1,c+k+1,cmp);
        T.len=top=0;
        if (is[1]^Q) Stack[top=1]=1;
        for (int i=1; i<=k; ++i) insert(c[i]);
        while (top>1) T.add(Stack[top-1],Stack[top]), --top;
    }

    int siz[maxn];
    inline void Dp(int x)
    {
        siz[x]=(is[x]==Q);
        int flag=0;
        for (int i=T.head[x]; i; i=T.Next[i])
        {
            int y=T.ver[i];
            Dp(y);
            if (siz[y] && siz[y]!=K) ans+=dist[f[y][0]]-dist[x],++flag;
            siz[x]+=siz[y];
        }
        if ( (siz[x]<K || flag>1) && x<=n && is[x]!=Q) ++ans;
        T.head[x]=0;
    }
}

using namespace Virtual;

inline void Clear()
{
    G.Clear(), A.Clear();
    id=top=tot=0;
    memset(dep,0,sizeof(dep));
    memset(f,0,sizeof(f));
    memset(is,0,sizeof(is));
    memset(dist,0,sizeof(dist));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(Stack,0,sizeof(Stack));
}

int main()
{
    int t;read(t);
    while (t--)
    {
        Clear();
        read(n);read(m); tot=n;
        for (int i=1,x,y; i<=m; ++i) read(x),read(y),G.add(x,y),G.add(y,x);
        tarjan(1);
        dist[1]=dep[1]=1, id=0; dfs(1,0);
        for (read(Q); Q; --Q)//坑死人,要用未执行减减操作的 Q
        {
            read(K);
            ans=0;
            int *c=new int[K+10];
            for (int i=1; i<=K; ++i) read(c[i]),is[c[i]]=Q;
            build(c,K);
            Dp(1);
            write(ans);
            delete[] c;
        }
    }
    IO::flush();
    return 0;
}

这是道路相遇的 \(code\)

#include<bits/stdc++.h>

const int maxn=2e6+10;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }

    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    template<typename T>inline void write(T x)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++='\n';
    }
}

using IO::read;
using IO::write;

template<typename T>inline T min(T a,T b) { return a<b ? a: b; }

struct Graph
{
    int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
    inline void Clear()
    {
        memset(head,0,sizeof(head));
        len=0;
    }

    inline void add(int x,int y)
    {
        ver[++len]=y,Next[len]=head[x],head[x]=len;
    }
}G, A;

namespace Square
{
    int dfn[maxn],low[maxn],id;
    int Stack[maxn],top,tot;
    inline void tarjan(int x)
    {
        dfn[x]=low[x]=++id;
        Stack[++top]=x;
        for (int i=G.head[x]; i; i=G.Next[i])
        {
            int y=G.ver[i];
            if (!dfn[y])
            {
                tarjan(y);
                low[x]=min(low[x],low[y]);
                if (low[y]>=dfn[x])
                {
                    int k;
                    ++tot;
                    do
                    {
                        k=Stack[top--];
                        A.add(tot,k), A.add(k,tot);
                    } while (k!=y);
                    A.add(tot,x), A.add(x,tot);
                }
            }
            else low[x]=min(low[x],dfn[y]);
        }
    }
}

using namespace Square;

namespace lca
{
    int f[maxn][21],dep[maxn];
    inline void dfs(int x,int fa)
    {
        dfn[x]=++id;
        for (int i=1; i<=20; ++i) f[x][i]=f[f[x][i-1]][i-1];
        for (int i=A.head[x]; i; i=A.Next[i])
        {
            int y=A.ver[i];
            if (y==fa) continue;
            f[y][0]=x;
            dep[y]=dep[x]+1;
            dfs(y,x);
        }
    }

    inline int LCA(int x,int y)
    {
        if (dep[x]>dep[y]) std::swap(x,y);
        for (int i=20; i>=0; --i)
            if (dep[y]-(1<<i)>=dep[x]) y=f[y][i];
        if (x==y) return x;
        for (int i=20; i>=0; --i)
            if (f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
        return f[x][0];
    }
}

using namespace lca;

int main()
{
    int n,m;read(n);read(m); tot=n;
    for (int i=1,x,y; i<=m; ++i) read(x),read(y),G.add(x,y),G.add(y,x);
    tarjan(1);
    id=0; dfs(1,0);
    int Q;read(Q);
    while (Q--)
    {
        int x,y;read(x);read(y);
        int ans=( (dep[x]+dep[y]-(dep[LCA(x,y)]<<1) ) >>1)+1;
        write(ans);
    }
    IO::flush();
    return 0;
}

战略游戏第二种思路:和道路相遇思路一样

#include<bits/stdc++.h>

const int maxn=2e5+10;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }

    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    template<typename T>inline void write(T x)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++='\n';
    }
}

using IO::read;
using IO::write;

template<typename T>inline T min(T a,T b) { return a<b ? a : b; }

struct Graph
{
    int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
    inline void Clear()
    {
        memset(head,0,sizeof(head));
        len=0;
    }

    inline void add(int x,int y)
    {
        ver[++len]=y,Next[len]=head[x],head[x]=len;
    }
}G, A;

namespace Square
{
    int dfn[maxn],low[maxn],id;
    int Stack[maxn],top,tot;
    inline void tarjan(int x)
    {
        dfn[x]=low[x]=++id;
        Stack[++top]=x;
        for (int i=G.head[x]; i; i=G.Next[i])
        {
            int y=G.ver[i];
            if (!dfn[y])
            {
                tarjan(y);
                low[x]=min(low[x],low[y]);
                if (low[y]>=dfn[x])
                {
                    int k;
                    ++tot;
                    do
                    {
                        k=Stack[top--];
                        A.add(tot,k), A.add(k,tot);
                    } while (k!=y);
                    A.add(tot,x), A.add(x,tot);
                }
            }
            else low[x]=min(low[x],dfn[y]);
        }
    }
}

using namespace Square;

int n,m;

namespace lca
{
    int f[maxn][21];
    int dist[maxn],dep[maxn];
    inline void dfs(int x,int fa)
    {
        dfn[x]=++id;
        for (int i=1; i<=20; ++i) f[x][i]=f[f[x][i-1]][i-1];
        for (int i=A.head[x]; i; i=A.Next[i])
        {
            int y=A.ver[i];
            if (y==fa) continue;
            dist[y]=dist[x]+(y<=n);
            f[y][0]=x;
            dep[y]=dep[x]+1;
            dfs(y,x);
        }
    }

    inline int LCA(int x,int y)
    {
        if (dep[x]>dep[y]) std::swap(x,y);
        for (int i=20; i>=0; --i)
            if (dep[y]-(1<<i)>=dep[x]) y=f[y][i];
        if (x==y) return x;
        for (int i=20; i>=0; --i)
            if (f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
        return f[x][0];
    }

    inline bool cmp(int a,int b)
    {
        return dfn[a]<dfn[b];
    }

    inline int Dis(int x,int y)
    {
        return dist[x]+dist[y]-(dist[LCA(x,y)]<<1);
    }
}

using namespace lca;

inline void Clear()
{
    G.Clear(), A.Clear();
    id=top=tot=0;
    memset(dep,0,sizeof(dep));
    memset(f,0,sizeof(f));
    memset(dist,0,sizeof(dist));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(Stack,0,sizeof(Stack));
}

int main()
{
    int t;read(t);
    while (t--)
    {
        Clear();
        read(n);read(m); tot=n;
        for (int i=1,x,y; i<=m; ++i) read(x),read(y),G.add(x,y),G.add(y,x);
        tarjan(1);
        dist[1]=dep[1]=1, id=0; dfs(1,0);
        int Q;read(Q);
        while (Q--)
        {
            int k;read(k);
            int *c=new int[k+10];
            for (int i=1; i<=k; ++i) read(c[i]);
            std::sort(c+1,c+k+1,cmp);
            int ans=-2*k;
            for (int i=1; i<k; ++i) ans+=Dis(c[i],c[i+1]);
            ans+=Dis(c[1],c[k]);
            if (LCA(c[1],c[k])<=n) ans+=2;
            write(ans>>1);
            delete[] c;
        }
    }
    IO::flush();
    return 0;
}
上一篇:直二面角与直三面角


下一篇:perp系列之二:perp源码README