• CCPC-Wannafly Winter Camp Day3 Div1


    题目链接:https://zhixincode.com/contest/14/problem/D?problem_id=206

    样例输入 1 

    5 5
    1 2 1
    1 3 1
    2 4 1
    2 5 1
    1 5 1

    样例输出 1

    20

    样例输入 2 

    5 10
    1 2 1
    1 3 2
    1 4 3
    1 5 4
    2 3 5
    2 4 6
    2 5 7
    3 4 8
    3 5 9
    4 5 10

    样例输出 2

    146

    题解:

    首先,删除一条边不可能使得任意两座城市的最短距离变得更近,所以尽可能地多删除边不会有任何坏处,因此最后得到的道路系统应该就是一棵树。

    (吉老师表示,这道题是生成树上状压dp的经典套路。我这只蒟蒻听得一愣一愣的,感觉自己dp确实太薄弱了……)

    jls表示套路是:用 $dp[i][S]$ 来表示一棵子树的某些属性,这棵子树的所有节点组成点集 $S$,并且该子树以 $i$ 节点为根。

    首先将要计算的 $sum_{i=1}^{n} sum_{j=i+1}^{n} d(i,j)$ 转变成另一种计算方法,考虑一条边 $(u_i,v_i,w_i)$ 对这个答案的贡献;

    考虑到一棵树上任意两个点间的路径都是唯一的,所以最短路就是唯一的那一条路径。因此一条边 $(u_i,v_i,w_i)$ 在 $sum_{i=1}^{n} sum_{j=i+1}^{n} d(i,j)$ 中被计算几次,就等于以这条边两端的两个子树内节点数的乘积。我们把这个值叫做这条边的贡献。

    然后用 $dp[i][S]$ 表示在点集 $S$ 上构造了一棵根为 $i$ 的生成树,它的所有边的贡献之和是最大的。我们不需要关心树的具体结构,我们只要知道在点集 $S$ 上可以搞出一棵生成树,这棵树的复杂程度是最大就好了。

    那么如何进行转移呢?很容易想到需要枚举子集,我们可以枚举 $S$ 的真子集 $T$,显然由于 $|T|<|S|$,所以对于任意的 $j in T$,$dp[j][T]$ 肯定已经被计算好了;同样的,对于集合 $S-T$,对于任意的 $i in S-T$,$dp[i][S-T]$ 肯定也是计算好了的。除非这是一个非法状态,换句话说即在原图上 $T$ 或者 $S-T$ 是不连通的。

    那么,状态转移方程即:对于在原图中存在的 $forall edge(i,j)$,其中 $i in S-T, j in T$,有 $dp[i][S] = max(dp[i][S],dp[j][T]+dp[i][S-T]+ w(i,j) cdot |T| cdot |n-T|)$。(这里有一个易错的点是误认为是 $dp[j][T]+dp[i][S-T]+ w(i,j) cdot |T| cdot |S-T|$,错误的理由是 $S$ 并不是全集 $V$)

    注:这个状压dp涉及到枚举子集的运算, x = x & (x-1) 是把 $x$ 二进制下最靠右的第一个 $1$ 变为 $0$, for(int t=s;t;t=(t-1)&s){} 则可以枚举 $s$ 的子集 $t$,而对于 $s$ 的一个子集 $t$ 求其补集 $s_t$ 则可以用 s_t = s ^ t 。

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=15;
    
    int n,m;
    ll mp[maxn][maxn];
    ll dp[maxn][1<<maxn];
    vector<int> poi[1<<maxn];
    
    int main()
    {
        ios::sync_with_stdio(0);
        cin.tie(0), cout.tie(0);
    
        cin>>n>>m;
        for(int i=1,u,v,w;i<=m;i++)
        {
            cin>>u>>v>>w;
            mp[u][v]=mp[v][u]=(ll)w;
        }
    
        memset(dp,-1,sizeof(dp));
        for(int s=1;s<(1<<n);s++)
        {
            for(int i=1;i<=n;i++) if(s&(1<<(i-1))) poi[s].push_back(i);
            if(poi[s].size()==1) dp[poi[s].front()][s]=0; //对于只有一个点的状态进行初始化
        }
    
        for(int s=1;s<(1<<n);s++)
        {
            for(int t=(s-1)&s;t;t=(t-1)&s)
            {
                if(dp[poi[t].front()][t]==-1) continue; //在原图上集合T不连通
                if(dp[poi[s-t].front()][s-t]==-1) continue; //在原图上集合S-T不连通
                for(auto i:poi[s-t])
                {
                    for(auto j:poi[t])
                    {
                        if(!mp[i][j]) continue;
                        ll T=poi[t].size();
                        dp[i][s]=max(dp[i][s],dp[j][t]+dp[i][s-t]+T*(n-T)*mp[i][j]);
                    }
                }
            }
        }
    
        ll ans=0;
        for(int i=1;i<=n;i++) ans=max(ans,dp[i][(1<<n)-1]);
        cout<<ans<<endl;
    }
  • 相关阅读:
    mvn 配置修改
    进制换算
    AT指令(中文详解版)二 [转载]
    Linux内核数据包处理流程-数据包接收(3)[转载]
    Linux内核数据包处理流程-数据包接收(2)[转载]
    Linux内核数据包处理流程-数据包接收(1)[转载]
    linux 内核模块 dumpstack
    linux c 用户态调试追踪函数调用堆栈以及定位段错误[转载]
    预处理命令详解(转载)
    [记录]博客开通
  • 原文地址:https://www.cnblogs.com/dilthey/p/10446824.html
Copyright © 2020-2023  润新知