【BZOJ4484】最小表示(JSOI2015)-贪心+拓扑排序+bitset

测试地址:最小表示
做法:本题需要用到贪心+拓扑排序+bitset。
显然,如果一条边对连通性没有影响,那肯定是要删掉的。现在的问题就是如何找到这些边。
我们考虑在反拓扑序上求。考虑一个点的所有出边,对于每个指向的点,如果当前还没有找到从当前点到这个点的路径,那么当前的边就要保留,并用这个点能到达的点的集合更新当前点的集合,这个显然能用bitset做到O(nm32)的复杂度。
但这样做有一个问题,如果12有边,23有边,13有边,那么13这条边是需要被删的,但如果在考虑点1时使用了3,2的顺序,我们就无法求出这样的边。实际上,这是因为从2能到达3,所以我们应该先连接2,就能求出这样的边了。
上面讨论的特殊情况的通用形式就是,我们贪心地先考虑最“顶端”的那些点,就能考虑到所有要删的边了。这其实就是把所有指向的点按拓扑序排序。那么总复杂度就是O(nm32+mlogm),可以通过此题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,tot=0,first[30010]={0},in[30010]={0},q[30010],pos[30010];
int son[30010];
struct edge
{
    int v,next;
}e[100010];
bitset<30010> S[30010];

void insert(int a,int b)
{
    e[++tot].v=b;
    e[tot].next=first[a];
    first[a]=tot;
    in[b]++;
}

void toposort()
{
    int t=0;
    for(int i=1;i<=n;i++)
        if (!in[i])
        {
            q[++t]=i;
            pos[i]=t;
        }
    for(int i=1;i<=n;i++)
    {
        int v=q[i];
        for(int j=first[v];j;j=e[j].next)
        {
            in[e[j].v]--;
            if (!in[e[j].v])
            {
                q[++t]=e[j].v;
                pos[e[j].v]=t;
            }
        }
    }
}

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

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        insert(a,b);
    }

    toposort();
    int ans=0;
    for(int i=n;i>=1;i--)
    {
        S[q[i]].set(q[i]);
        int siz=0;
        for(int j=first[q[i]];j;j=e[j].next)
            son[++siz]=e[j].v;
        if (!siz) continue;
        sort(son+1,son+siz+1,cmp);
        for(int j=1;j<=siz;j++)
        {
            if (S[q[i]][son[j]]) ans++;
            else S[q[i]]|=S[son[j]];
        }
    }
    printf("%d",ans);

    return 0;
}
posted @ 2018-08-20 20:08  Maxwei_wzj  阅读(148)  评论(0编辑  收藏  举报