bzoj 4479: [Jsoi2013]吃货jyy

题意

给你一个无向图,并告诉你有k条边是一定要走的,有m条边是可走可不走。然后问你从1号点出发,经过所有的k条边,最后回到1号点的最小代价

前言

这题我做了很久,好像差不多有一个下午加晚上吧。。
一开始找到Claris的题解。。不是很敢弄。。
然后发现dis里面似乎有新大陆,于是又弄了很久。。但是一直没有弄出什么东西来
最后还是回去了Claris的做法
然后弄懂了之后,还是不是很敢再打一次,因为上一次大部分都是参考Claris的。
我就来认真地口胡一篇博客来假装自己会了吧。。

题解

我们分成两种情况讨论,K 15,和k>15

为什么要分这两种情况

因为我们可以发现,当k > 15的时候,由于n的最大值只有13,所以他至多只会有7个联通块,达到最大联通块的方案是,6个点里面互相连边,然后这里是15条边,此时有 剩下的7个点+1个联通块=8个联通块,然后无论你下一个加到哪里,比减少一个联通块,于是就是7个了。。
我们考虑最后的答案,如果所有包含关键边的联通块都和1号点属于联通块的话,那么至少说明,这个方案有可能可行的
但是还是有一种不存在回路的情况。。
根据无向图是否存在欧拉回路的判定方法,如果所有点的度数都是偶数,那么就存在一条欧拉回路。。
于是我们可以断定,如果在最后的状态里面,如果该状态的关键边与1号点连通且度数均为偶数,那么就是可行的

K15

这个很好办,考虑到k很小,我们不妨状压一下每一条关键边的经过状态,然后搭上当前在哪个点,就可以出来一个最短路了,f[i][j]表示当前在i号点,经过状态为j的最小代价
时间复杂度 O(n215) 然后用dij跑的话应该还是多一个log的
代码实现:

int f[N][N][2],g[N][N],d[N][(1<<LIM)];
    priority_queue<PI,vector<PI>,greater<PI> >q;
    void ext (int x,int y,int z)//看看之前是否存在这个状态
    //x现在在哪一个点   走的那些边状态是什么 
    {
        if (d[x][y]<=z) return ;
        q.push(PI(d[x][y]=z,P(x,y)));
    }
    void solve ()
    {
        for (int u=0;u<n;u++)
            for (int i=0;i<=n;i++)
                f[u][i][0]=-1;
        for (int u=0;u<n;u++)
            for (int i=0;i<n;i++)
                g[u][i]=MAX;
        for (int u=0;u<k;u++)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            x--;y--;
            f[x][y][0]=f[y][x][0]=u;
            f[x][y][1]=f[y][x][1]=z;
        }
        scanf("%d",&m);
        while (m--)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            x--;y--;
            up(g[x][y],z);up(g[y][x],z);
        }
        for (int u=0;u<n;u++)
            for (int i=0;i<(1<<k);i++)
                d[u][i]=MAX;
        ext(0,0,0);
        while (!q.empty())
        {
            PI t=q.top();q.pop();
            int x=t.second.first,y=t.second.second,z=t.first;
            if (z>d[x][y]) continue;
            for (int u=0;u<n;u++)
            {
                if (f[x][u][0]!=-1)//有这条边
                    ext(u,y|(1<<f[x][u][0]),z+f[x][u][1]);
                ext(u,y,z+g[x][u]); 
            }
        }
        printf("%d\n",d[0][(1<<k)-1]);
    }

K>15

这种情况就不是特别好办了。。
但是读了第一部分的人都知道,此时我们用尽各种奇技淫巧,可以吧一个图缩成7个联通块。而7个联通块的连接状态,其实就是贝尔数,这个如果有研究过数论的就都知道。感觉在看斯特林数的时候经常可以看到这个(暴露智商) Bell(7) 是只有877个状态的,于是我们就可以位压了。。
不妨DPf[i][j][k]表示用了前i条边,然后连通状态为j,每个点的奇偶状态为k的最小代价是什么。。
然后DP一下就就可以了
同时为了处理方便,我们可以引入一个g数组,和q数组
q预先吧所有状态都处理出来,放在里面, 也就是上面的j不是一个状态,只是状态的一个编号而已
然后g表示某一个状态加上某一条边会到达什么状态,这就可以实现了
代码实现:

int a[N];//前期:作为并查集   后期:辅助数组
    int dp[2][1<<N];//滚动   前k条边   然后是每个点的奇偶性的  最小代价 
    bool must[N];//这个是不是一个必要点
    int g[M][N][N];//这个状态加上这条边是什么状态 
    int e[N][N];//这两个点之间的最优距离
    char T;//时间轴
    int w[2][M][1<<N];//第n条边,这个连通状态,每个点奇偶性为0  滚动 
    char v[2][M][1<<N];//时间轴 
    short s[2][M][1<<N],cnt[2][M];//状态 对于这种连通状态有多少种情况 
    map<LL,int>id;
    int o=0,h=0,t=0;
    LL q[M];
    void Merge (int x,int y)//这两个点连在一起 
    {
        x=a[x];y=a[y];
        for (int u=0;u<n;u++)
            if (a[u]==x)
                a[u]=y;
    }
    LL encode ()//吧当前的a压进t 
    {
        //具体思路:就是把每一块的祖先都标出来
        //然后对于每一个块,用他最小编号的点将他编号,这样的话就不会有两种重复的情况
        //然后对于每一个点,用他所在的编号就可以知道他的联通块人了 
        int m=0;
        LL t=0;
        int v[N];
        for (int u=0;u<n;u++) v[a[u]]=-1;
        for (int u=0;u<n;u++)
        {
            if (v[a[u]]<0) v[a[u]]=m++;
            t=t<<4|v[a[u]];
        }
        return t;
    }
    void decode (LL f)//将这个状态变回a里面 
    {
        for (int u=n-1;u>=0;u--)
            a[u]=(f&15),f>>=4;
    }
    int ext (LL x)
    {
        //看看是否存在x这个状态,并给他标号 
        if (id[x]!=0) return id[x];
        id[x]=++t;
        q[id[x]]=x;
        return id[x];
    }
    void clr ()
    {
        T++;
        for (int u=1;u<=t;u++) cnt[o^1][u]=0;
    }
    void add (int x,int y,int z)
    {
        if (z>=MAX) return ;
        if (v[o^1][x][y]<T)
        {
            v[o^1][x][y]=T;
            w[o^1][x][y]=z;
            s[o^1][x][cnt[o^1][x]++]=y;
            return ;
        }
        up(w[o^1][x][y],z);
    }
    bool check(LL f)
    {
        decode(f);
        for(int i=0; i<n; i++) if(must[i]==true&&a[i]!=a[0])return 0;
        return 1;
    }
    void solve ()
    {
        for (int u=0;u<n;u++) a[u]=u;
        for (int u=1;u<(1<<n);u++) dp[0][u]=MAX; 
        memset(must,false,sizeof(must));
        while (k--)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            x--;y--;
            must[x]=true;must[y]=true;
            Merge(x,y);
            for (int u=0;u<(1<<n);u++) dp[o^1][u]=MAX;  
            for (int u=0;u<(1<<n);u++)
                if (dp[o][u]<MAX)
                {
                    up(dp[o^1][u^(1<<x)^(1<<y)],dp[o][u]+z);
                    up(dp[o^1][u],dp[o][u]+z+z);
                }
            o^=1;
        }
        decode(encode());
        h=1;
        ext(encode());
        while (h<=t)//这个就是建立那877个状态 
        {
            LL x=q[h];
            for (int u=0;u<n;u++)
                for (int i=0;i<n;i++)
                {
                    decode(x);
                    Merge(u,i);
                    g[h][u][i]=ext(encode()); 
                }
            h++;
        }
        scanf("%d",&m);
        for (int u=0;u<n;u++)
            for (int i=0;i<n;i++)
                e[u][i]=MAX;
        while (m--)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            x--;y--;
            if (x==y) continue;
            if (x>y) swap(x,y);
            up(e[x][y],z);
        }
        clr();
        for (int u=0;u<(1<<n);u++) 
            add(1,u,dp[o][u]);
        o^=1;
        for (int x=0;x<n;x++)
            for (int y=0;y<n;y++)
                if (e[x][y]<MAX)
                 {
                    int z=e[x][y];
                    clr();
                    for (int i=1;i<=t;i++)
                        for (int j=0;j<cnt[o][i];j++)
                        {
                            int S=s[o][i][j],f=w[o][i][S];
                            add(i,S,f);//不用这条边 
                            add(g[i][x][y],S^(1<<x)^(1<<y),f+z);//加入一次 
                            add(g[i][x][y],S,f+z+z);//加入两次 
                        }
                    o^=1;
                 } 
        int ans=MAX;
        for (int u=1;u<=t;u++)
            if (check(q[u])==true)
            {
                for (int i=0;i<cnt[o][u];i++)
                    if (s[o][u][i]==0)
                    {
                        up(ans,w[o][u][0]);
                        break;
                    }
            }
        printf("%d\n",ans);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值