• Codeforces Round #556 CF1149D Abandoning Roads


    这道题并不简单,要得出几个结论之后才可以做。首先就是根据Kruskal求最小生成树的过程,短边是首选的,那么对于这道题也是,我们先做一次直选短边的最小生成树这样会形成多个联通块,这些联通块内部由短边相连。那么接下来要形成完整的最小生成树,我们就得用长边把这些联通块连起来,因为要最短路径,所以我们用Dijkstra做连边的过程 这里给出一个结论:只要满足两个条件:第一,每个联通块内部不能连长边。第二,一个联通块不能被访问两遍。

    重点来了:只要是满足这两个条件下树就能保证它是一棵最小生成树。

    为什么呢?因为我们已经把所有能连的短边连起来了,接下来的联通块必须的用长边连起来,连起来之后就是用了尽量多的短边和尽量少的长边生成的树,那当然是最小生成树。

    那为什么又要用最短路跑呢?其实满足上诉两个条件下的最小生成树也有很多,因为题目的要求是最短路,所以我们只要在保证是最小生成树的条件下跑最短路那就是答案了。


    为了满足一个联通块不能访问两次,我们考虑用状态压缩来记录状态解决。但是因为最多能分成70个联通块,那么就得用2^70得数组取记录状态。显然爆空间+超时。

    这里有一个比较难想的优化:小于等于3个点的联通块是不会被访问两次的,为什么?这是因为Dijkstra会优先选择最短路,而小于等于3个点的联通块中的任两个点的距离都是短边,所以这种联通块内部有未访问的临近结点应该会优先访问,一旦Dijkstra离开了这个联通块,那么就不可能回来了,而大于等于4个点的联通块如果不加限制的话跑Dijkstra其实离开了有可能再次跑回来,但这是不满足上诉两个条件的,必须得用状态记录加以限制。

    所以现在联通块数量降到了70/4=17个,可以结束。那么用d[S][i]代表当前访问完成的联通块状态为S,现在在结点i的最短路,在上诉两个条件下跑一次最短路即可。答案就是所有情况下经过i点的最小的值。


    细节详见代码:

    #include<bits/stdc++.h>
    #define mp(x,y) make_pair(x,y)
    using namespace std;
    typedef pair<int,pair<int,int>> piii;
    const int N=110;
    int n,m,a,b,cnt,Newid,Size[N],id[N],nid[N],ans[N];
    struct edge{
        int x,y,z;
        bool operator < (const edge &rhs) const {
            return z<rhs.z;
        }
    }e[N<<2];
    
    int tot=0,head[N],to[N<<2],nxt[N<<2],len[N<<2];
    void add_edge(int x,int y,int z) {
        nxt[++tot]=head[x]; to[tot]=y; len[tot]=z; head[x]=tot;
    }
    
    int fa[N];
    int getfa(int x) { return x==fa[x] ? x : fa[x]=getfa(fa[x]); }
    
    void Krusual() {
        for (int i=1;i<=n;i++) fa[i]=i;
        for (int i=1;i<=m;i++) {
            int x=getfa(e[i].x),y=getfa(e[i].y);
            if (e[i].z==b || x==y) continue;
            fa[y]=fa[x];
        }
    }
    
    priority_queue<piii> q;
    int dis[1<<18][80]; bool vis[1<<18][80];
    void Dijkstra() {
        memset(dis,0x3f,sizeof(dis));
        memset(vis,0,sizeof(vis));
        q.push(mp(0,mp(0,1)));
        dis[0][1]=0;
        while (!q.empty()) {
            int now=-q.top().first; pair<int,int> u=q.top().second; q.pop();
            ans[u.second]=min(ans[u.second],now);
            if (vis[u.first][u.second]) continue;
            vis[u.first][u.second]=1;
            for (int i=head[u.second];i;i=nxt[i]) {
                int v=to[i];
                if (id[v]==id[u.second] && len[i]==b) continue;    //条件一:不能在内部走长边 
                if (nid[v] && (u.first&(1<<nid[v]))) continue;  //条件二:不能往回走 
                
                int S=u.first; 
                if (nid[u.second] && nid[v]!=nid[u.second]) S|=(1<<nid[u.second]);
                  
                if (dis[u.first][u.second]+len[i]<dis[S][v]) {
                    dis[S][v]=dis[u.first][u.second]+len[i];
                    q.push(mp(-dis[S][v],mp(S,v)));
                }
            }
        }
    }
    
    int main()
    {
        cin>>n>>m>>a>>b;
        for (int i=1;i<=m;i++) {
            scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
            add_edge(e[i].x,e[i].y,e[i].z);
            add_edge(e[i].y,e[i].x,e[i].z);
        }
        sort(e+1,e+m+1);
        Krusual();
        
        for (int i=1;i<=n;i++) {
            Size[getfa(i)]++;
            if (fa[i]==i) id[i]=++cnt; 
        }
        for (int i=1;i<=n;i++) id[i]=id[getfa(i)];
        for (int i=1;i<=n;i++)
            if (Size[i]>=4) nid[i]=++Newid;
        for (int i=1;i<=n;i++) nid[i]=nid[getfa(i)];    
        
        memset(ans,0x3f,sizeof(ans));
        Dijkstra();
        
        for (int i=1;i<=n;i++) printf("%d ",ans[i]);
        return 0;
    } 
  • 相关阅读:
    使用XStream解析xml
    分享功能
    上拉加载 下拉刷新
    点击button倒计时
    正则表达式验证手机号码
    第三方登陆
    test
    横向滑动菜单HorizontalScrollView
    slidingmenu侧滑侧单
    2017/4/25 afternoon
  • 原文地址:https://www.cnblogs.com/clno1/p/10821488.html
Copyright © 2020-2023  润新知