zoukankan      html  css  js  c++  java
  • NOIP2017 宝藏

    传送门

    当时NOIP考试的时候我不会,现在我还是不会。

    因为数据范围很小所以能猜到是状压DP。不过平时我们状压DP都是在一个矩阵里面状压DP,不过这次因为我们要打通所有的宝藏屋,那么肯定最后打通的时候通路是一棵树,我们也就是相当于在树上DP。

    首先我们先说一种非常强势的做法,状压DP+dfs!(不过以我的智商估计是永远也想不到了)

    我们用dp[i]表示当前状态为i的时候的最小代价。我们每次选取一个点作为根节点,之后从这个点开始dfs。每次我们选取一个当前走过的点,之后再选取一个当前没走过的点,如果可以更新的话就更新,之后继续从这个状态dfs。在回溯的时候我们把深度重新设为原来保持的值就可以。

    看一下代码,还是比较好理解的……(不过这玩意咋想orz)

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<iostream>
    #include<cmath>
    #include<queue>
    #include<set>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    
    using namespace std;
    typedef long long ll;
    const int M = 50005;
    const int INF = 2147483646;
    
    int read()
    {
        int ans = 0,op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
        if(ch == '-') op = -1;
        ch = getchar();
        }
        while(ch >= '0' && ch <= '9')
        {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
        }
        return ans * op;
    }
    
    int dp[1<<15],dis[15],g[15][15],c[15][15],n,m,ans = INF,x,y,w;
    
    void add(int x,int y,int w)
    {
        g[x][y] = g[y][x] = w;
        c[x][y] = c[y][x] = 1;
    }
    
    void dfs(int x)
    {
        rep(i,1,n)
        {
        if(!(x & 1<<(i-1))) continue;
        rep(j,1,n)
        {
            if((1<<(j-1) & x) || !c[i][j]) continue;//c表示i,j之间有没有路,g存路径的长度
            if(dp[1<<(j-1)|x] > dp[x] + dis[i] * g[i][j])
            {
            int temp = dis[j];
            dis[j] = dis[i] + 1;
            dp[1<<(j-1)|x] = dp[x] + dis[i] * g[i][j];
            dfs(1<<(j-1)|x);
            dis[j] = temp;
            }
        }
        }
    }
    
    int main()
    {
        n = read(),m = read();
        memset(g,63,sizeof(g));
        rep(i,1,m)
        {
        x = read(),y = read(),w = read();
        if(w < g[x][y]) add(x,y,w);
        }
        rep(i,1,n)
        {
        memset(dis,63,sizeof(dis));
        rep(j,1,(1<<n)-1) dp[j] = INF;
        dis[i] = 1,dp[1<<(i-1)] = 0;
        dfs(1<<(i-1));
        ans = min(ans,dp[(1<<n)-1]);
        }
        printf("%d
    ",ans);
        return 0;
    }

    之后我们再说另一种方法。因为其实dfs的复杂度难以保证,所以我们考虑最稳定的一种做法,就是状压DP。我们是在一棵树上进行状压的,不过因为这棵树不是定型的,我们可以把一个根节点的深度设为0,之后每次向下一个深度更新的时候,对于当前的一个状态,我们先求它的补集,之后枚举其所有子集进行转移。

    首先使用pval表示从点i到集合j的最短距离,之后使用sval表示集合i到集合j的最短距离,也就是集合i中的每一个点到集合j的最短距离之和。这个更新还是很好更新的。

    之后dp的方程就是,设当前的状态为s,它的补集是C,枚举C的所有子集j,用i表示当前的层数,那么dp[i+1][s|j] = min(dp[i+1][s|j],dp[i][s] + sval[s][j] * (i+1));

    我们并不需要考虑实际上两层之间是怎么相连的,因为我们肯定会枚举到所有的情况,即使当前是按照错误的方法相连,在之后肯定会有一种情况将其更新。

    之后就可以做了。答案是min{dp[i][u]},其中u = (1<<n)-1,i取0~n-1

    有两个小技巧,一个是计算补集,就是直接^.另外一个是枚举一个集合的所有子集,具体看下面代码实现。

    然而其实这个状压DP跑的要比上面的dfs慢一倍……但是稳啊。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<iostream>
    #include<cmath>
    #include<queue>
    #include<set>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    
    using namespace std;
    typedef long long ll;
    const int M = 50005;
    const int INF = 10000000;
    
    ll read()
    {
        ll ans = 0,op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
        if(ch == '-') op = -1;
        ch = getchar();
        }
        while(ch >= '0' && ch <= '9')
        {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
        }
        return ans * op;
    }
    
    ll dp[13][1<<13],g[15][15],pval[13][1<<13],sval[1<<13][1<<13],n,m,ans = 1e15,x,y,w,u;
    
    void initp()
    {
        rep(i,1,n)
        rep(j,0,u) pval[i][j] = INF;
        rep(i,1,n)
        rep(j,0,u)
        rep(k,1,n) if(j & (1<<(k-1))) pval[i][j] = min(pval[i][j],g[i][k]*1ll);//更新pval,方法就是枚举j里面的所有点k
    }
    
    void inits()
    {
        rep(i,0,u)
        rep(j,0,u) sval[i][j] = INF;
        rep(i,0,u)
        {
        int q = i^u;
        for(int s = q;s;s = (s-1) & q)//枚举所有子集并更新
        {
            ll temp = 0;
            rep(j,1,n) if(s & (1<<(j-1))) temp += pval[j][i];
            sval[i][s] = temp >= INF? INF : temp;
        }
        }
    }
    
    void init(int x)
    {
        rep(i,0,n)
        rep(j,0,u) dp[i][j] = INF;
        dp[0][1<<(x-1)] = 0;//每次改变根节点都要更新
    }
    
    int main()
    {
        n = read(),m = read(),u = (1<<n)-1;
        rep(i,1,n)
        rep(j,1,n) if(i^j) g[i][j] = INF;//如果不相同就把距离设为INF
        rep(i,1,m) x = read(),y = read(),w = read(),g[x][y] = g[y][x] = min(g[x][y],w);
        initp();
        inits();
        rep(r,1,n)
        {
        init(r);
        rep(i,0,n-1)
        rep(s,0,u)
        {
            if(dp[i][s] == INF) continue;
            int q = s ^ u;
            for(int j = q;j;j = (j-1) & q)
            dp[i+1][s|j] = min(dp[i+1][s|j],dp[i][s] + sval[s][j] * (i+1));//dp转移
        }
        rep(i,0,n-1) ans = min(ans,dp[i][u]);//计算答案
        }
        printf("%lld
    ",ans);
        return 0;
    }
  • 相关阅读:
    ACdream群赛(4) B Double Kings
    ACdream群赛(4)总结
    250E Mad Joe
    ZOJ Monthly, November 2012 I Search in the Wiki
    251C Number Transformation
    253D Table with Letters 2
    Codeforces Round #153 (Div. 2) 总结
    ACdream群赛(4) D Draw a Mess
    ZOJ Monthly, November 2012 G Gao The Sequence
    在vs2005/c++中捕获浮点数异常
  • 原文地址:https://www.cnblogs.com/captain1/p/9604880.html
Copyright © 2011-2022 走看看