• NOIP 2017 宝藏


    题目链接

    https://www.luogu.org/problemnew/solution/P3959

    这道题学到很多很多东西

    还是很多收获的,虽然花了非常长的时间

    首先直接写prim可以得45分,还是很多的

    貌似不需要long long

    #include<bits/stdc++.h>
    #define REP(i, a, b) for(register int i = (a); i < (b); i++)
    #define _for(i, a, b) for(register int i = (a); i <= (b); i++)
    using namespace std;
    
    typedef long long ll;
    const int MAXN = 20;
    int g[MAXN][MAXN]; 
    int vis[MAXN], n, m;
    ll dis[MAXN], d[MAXN], ans;
    
    ll prim(int u)
    {
        memset(vis, 0, sizeof(vis));
        vis[u] = 1;
        _for(i, 1, n) d[i] = i == u ? 0 : 1e9;
        _for(i, 1, n) dis[i] = i == u ? 0 : 1e9;
        
        _for(v, 1, n)
            if(g[u][v] != -1)
            {
                d[v] = min(d[v], (ll)g[u][v]);
                dis[v] = 1;
            }
        
        ll res = 0;
        REP(k, 1, n)
        {
            ll mint = 1e18;
            int v;
            _for(i, 1, n)
                if(!vis[i] && d[i] != 1e9 && mint > d[i])
                    mint = d[i], v = i;
    
            vis[v] = 1;
            res += mint;
            
            _for(i, 1, n)
            {
                if(vis[i] || g[v][i] == -1) continue;
                dis[i] = min(dis[i], dis[v] + 1);
                if(d[i] > g[v][i] * (dis[v] + 1))
                {
                    d[i] = g[v][i] * (dis[v] + 1);
                    dis[i] = dis[v] + 1;
                }
            }
        }
        
        return res;
    }
    
    int main()
    {
        memset(g, -1, sizeof(g));
        scanf("%d%d", &n, &m);
        
        _for(i, 1, m)
        {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            if(g[u][v] == -1) g[u][v] = g[v][u] = w;
            else g[u][v] = g[v][u] = min(g[u][v], w);
        }
        
        ans = 1e18;
        _for(i, 1, n) ans = min(ans, prim(i));
        printf("%lld
    ", ans);
        
        return 0;
    }

    然后直接打搜索竟然可以拿到70分!!

    #include<bits/stdc++.h>
    #define REP(i, a, b) for(register int i = (a); i < (b); i++)
    #define _for(i, a, b) for(register int i = (a); i <= (b); i++)
    using namespace std;
    
    const int MAXN = 20;
    int g[MAXN][MAXN]; 
    int vis[MAXN], n, m;
    int d[MAXN], ans;
    
    void dfs(int cnt, int now)
    {
        if(now >= ans) return;
        if(cnt == n)
        {
            ans = min(ans, now);
            return;
        }
        
        _for(i, 1, n)
        {
            if(!vis[i]) continue;
            _for(j, 1, n)
                if(!vis[j] && g[i][j] != -1)
                {
                    d[j] = d[i] + 1;
                    vis[j] = 1;
                    dfs(cnt + 1, now + g[i][j] * d[j]);
                    d[j] = vis[j] = 0; 
                }
        }
    }
    
    int main()
    {
        memset(g, -1, sizeof(g));
        scanf("%d%d", &n, &m);
        
        _for(i, 1, m)
        {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            if(g[u][v] == -1) g[u][v] = g[v][u] = w;
            else g[u][v] = g[v][u] = min(g[u][v], w);
        }
        
        ans = 1e9;
        _for(i, 1, n)
        {
            vis[i] = 1;
            dfs(1, 0);
            vis[i] = 0;
        }
        printf("%d
    ", ans);
       
        return 0;
    }

    然后接下来要AC这道题就有很多方法了

    有一个错误的状压可以AC,比搜索快在记忆化上

    #include<bits/stdc++.h>
    #define REP(i, a, b) for(register int i = (a); i < (b); i++)
    #define _for(i, a, b) for(register int i = (a); i <= (b); i++)
    using namespace std;
    
    const int MAXN = 5000;
    int g[MAXN][MAXN]; 
    int dp[MAXN], n, m;
    int d[MAXN];
    
    void dfs(int S)
    {
        REP(i, 0, n)
        {
            if(!(S & (1 << i))) continue;
            REP(j, 0, n)
            {
                if(S & (1 << j) || g[i][j] == -1) continue;
                if(dp[S] + g[i][j] * d[i] < dp[S | (1 << j)])
                {
                    dp[S | (1 << j)] = dp[S] + g[i][j] * d[i];
                    d[j] = d[i] + 1;
                    dfs(S | (1 << j));
                }
            }
        }
    }
    
    int main()
    {
        memset(g, -1, sizeof(g));
        scanf("%d%d", &n, &m);
        
        _for(i, 1, m)
        {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w); u--; v--;
            if(g[u][v] == -1) g[u][v] = g[v][u] = w;
            else g[u][v] = g[v][u] = min(g[u][v], w);
        }
    
        int ans = 1e9;
        REP(i, 0, n)
        {
            memset(dp, 0x3f, sizeof(dp));
            memset(d, 0, sizeof(d));
            d[i] = 1;
            dp[1 << i] = 0;
            dfs(1 << i);
            ans = min(ans, dp[(1 << n) - 1]);
        }
        printf("%d
    ", ans);
       
        return 0;
    }

    还有一个骗分的random_shuffle可以AC掉

    因为这道题做一次贪心的时间非常非常小,所以可以随机化做非常非常多次的贪心,取最优

    随机化的一个方法就是random_shuffle,重置排列

    对于这道题可以拿这个排列作为开拓的顺序,然后贪心就好了。

    #include<bits/stdc++.h>
    #define REP(i, a, b) for(register int i = (a); i < (b); i++)
    #define _for(i, a, b) for(register int i = (a); i <= (b); i++)
    using namespace std;
    
    const int MAXN = 20;
    int g[MAXN][MAXN]; 
    int vis[MAXN], n, m;
    int d[MAXN], ans;
    
    void dfs(int cnt, int now)
    {
        if(now >= ans) return;
        if(cnt == n)
        {
            ans = min(ans, now);
            return;
        }
        
        _for(i, 1, n)
        {
            if(!vis[i]) continue;
            _for(j, 1, n)
                if(!vis[j] && g[i][j] != -1)
                {
                    d[j] = d[i] + 1;
                    vis[j] = 1;
                    dfs(cnt + 1, now + g[i][j] * d[j]);
                    d[j] = vis[j] = 0; 
                }
        }
    }
    
    int main()
    {
        memset(g, -1, sizeof(g));
        scanf("%d%d", &n, &m);
        
        _for(i, 1, m)
        {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            if(g[u][v] == -1) g[u][v] = g[v][u] = w;
            else g[u][v] = g[v][u] = min(g[u][v], w);
        }
        
        ans = 1e9;
        _for(i, 1, n)
        {
            vis[i] = 1;
            dfs(1, 0);
            vis[i] = 0;
        }
        printf("%d
    ", ans);
       
        return 0;
    }

    然后是正解,比较难理解

     据说这道题本来是T3??

    思路网上都有,我讲几个我一直卡住的点

    正解是按照深度一层一层来dp的

    这个dp是不关注根在哪里的,只关注点的集合和深度

    这点非常重要

    首先不一定深度浅的优,因为边权不一样

    然后这个深度只可能算大,不可能算小,因为转移的时候要用到一个cost数组(具体见程序)

    如果算小的话说明这个集合不可能是这个深度,那么这个cost数组的值也就会非常大,表示不存在

    所以顶多就是没有这么深但是你算得时候算这么深

    不过这样不会漏掉最优解,因为你把所有的情况都枚举了一遍

    还学到了许多技巧,如3^n枚举子集,用从小到大用lowbit作dp

    虽然写了一天,但还是收获很大的。不求写题的数量,但求学到知识,有进步

    #include<bits/stdc++.h>
    #define REP(i, a, b) for(register int i = (a); i < (b); i++)
    #define _for(i, a, b) for(register int i = (a); i <= (b); i++)
    using namespace std;
    
    const int MAXN = 13;
    const int MAXM = (1 << 12) + 10;
    int lg[MAXN], cost[MAXM][MAXM];
    int g[MAXN][MAXN], dp[MAXN][MAXM];
    int n, m;
    
    inline int lowbit(int x) { return x & (-x); }
    
    inline void down(int& a, int b) { a = min(a, b); }
    
    int main()
    {
        memset(g, -1, sizeof(g));
        scanf("%d%d", &n, &m);
        
        _for(i, 1, m)
        {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w); u--; v--;
            if(g[u][v] == -1) g[u][v] = g[v][u] = w;
            else g[u][v] = g[v][u] = min(g[u][v], w);
        }
        
        int Smax = (1 << n) - 1;
        REP(i, 0, n) lg[1 << i] = i;
        
        REP(S, 1, 1 << n)
        {
            int T = Smax ^ S; //表示补集 
            stack<int> st;
            for(int S0 = T; S0; S0 = (S0-1) & T) st.push(S0); //S0从小到大枚举,这样dp顺序才对 
            
            while(!st.empty())
            {
                int S0 = st.top(); st.pop();  
                int u = lg[lowbit(S0)], mint = 5e5 + 10; //如果这个mint要加很多次的话,就按题目的最大来 
                REP(v, 0, n)                              //不要省事写1e9,否则40分没了!! 
                    if(S & (1 << v) && g[u][v] != -1) // 这里只转移lowbit这个点就好了,之前的最小值已经转移过了 
                        down(mint, g[u][v]);
                cost[S][S0] = cost[S][S0 ^ (1 << u)] + mint;  //注意这里是S转移到S0,通过S0中的点向S连边 
            }
        }
        
        memset(dp, 0x3f, sizeof(dp));
        REP(i, 0, n) dp[0][1 << i] = 0;
        _for(h, 1, n)
            REP(S, 1, 1 << n)
                for(int S0 = S; S0; S0 = (S0 - 1) & S)
                    down(dp[h][S], dp[h-1][S ^ S0] + cost[S ^ S0][S0] * h); 
        
        int ans = 1e9;
        _for(h, 0, n) //注意这里深度可能为0,即一个点没有边,很坑 
            down(ans, dp[h][Smax]);
        printf("%d
    ", ans);
       
        return 0;
    }
  • 相关阅读:
    mysql数据库管理工具(navicat for mysql)
    一次测试岗位针对Java和接口的面试题
    接口测试 rest-assured 使用指南
    简单实现接口自动化测试(基于python+unittest)
    负载测试、压力测试和性能测试的异同
    【Excle数据透视表】如何新建数据透视表样式
    【Excle数据透视表】如何为数据透视表应用样式
    【Linux】Linux删除指定文件夹下面 名称不包含指定字符的文件
    【Excle数据透视表】如何水平并排显示报表筛选区域的字段
    【Excle数据透视表】如何在数据透视表中使用合并单元格标志
  • 原文地址:https://www.cnblogs.com/sugewud/p/9888228.html
Copyright © 2020-2023  润新知