TopCoder SRM477C: KingdomTour 题解

很难的dp题

如果不加shortcut,那么显然只有一种遍历方案,每条边都走了两遍

shortcut只能走一次,而树边可以走无数次,不统一不方便,考虑转化模型

可以想到每条树边最多走两次,一上一下,走更多的次数显然没有好处

可以把每条树边拆成两条边,规定每条边只能走一次,这样这些边的性质就和shortcut一样了

我们可以从欧拉图的角度来考虑这件事情,拆边以后,每个点的度数都是偶数,所以能满足从1出发回到1

那么添加shortcut的本质就是在树中加一条边,然后为了满足欧拉图的性质删去一些边

还可以发现一些关于shortcut的性质

设shortcut的两个端点为u和v,且depth[u] < depth[v]

1.shortcut不会重合,这是可以进行树型dp的基础

2.添加shortcut之后删去的那些边一定是u到v路径上的边,而不会是u的祖先和v的孩子,因为如果是后者,最后一定会有一个孩子的度数是奇数,这样虽然也能一笔画但是不能从1出发返回1

有了这两个性质我们考虑树型dp

dp[i][j]表示当前考虑到i节点以及它的子树,在子树中被shortcut连到的节点数是j个时的最小方案

注意我们这时已经不关心king走的路径了,我们只要能构造出欧拉回路就一定有解,把边权加一加就好

从i的孩子转移,设dp2[i][c][j]表示在i,已经考虑了前k个子树,有j个点被shortcut连到的最小方案

当前考虑i的第c颗子树,如果从dp[c][k],k是奇数的状态转移来,那么说明有一条shortcut从c的子树出发,要连到c的外面,那么根据shortcut的性质2,从i到c的两条边中一定要删去一条;如果从k是偶数的状态转移来,那么为了保证c的度数是偶数且图联通,那么i到c的两条边必须全部保留

所以我们有了dp2的状态转移方程:

dp2[i][c][j]=k=0jdp2[i][c1][jk]+dp[c][k]+(k?f[i][c]:f[i][c]2) d p 2 [ i ] [ c ] [ j ] = ∑ k = 0 j d p 2 [ i ] [ c − 1 ] [ j − k ] + d p [ c ] [ k ] + ( k 是 奇 数 ? f [ i ] [ c ] : f [ i ] [ c ] ∗ 2 )

其中 f[i][c] f [ i ] [ c ] 表示从i到c的边的权值

最后 dp[i][j]=dp[cc][j] d p [ i ] [ j ] = d p [ c c ] [ j ] ,其中 cci c c 是 i 孩 子 的 个 数

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+9;
const LL LINF=2e16;
const int INF=2e9;
const int magic=348;
const double eps=1e-10;
const double pi=3.14159265;

inline int getint()
{
    char ch;int res;bool f;
    while (!isdigit(ch=getchar()) && ch!='-') {}
    if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
    while (isdigit(ch=getchar())) res=res*10+ch-'0';
    return f?res:-res;
}

class KingdomTour
{
    public:
        vector<Pair> v[1048];
        int dp[248][248],tmp[248][248];
        int N,M;
        inline void Clear(int n) {for (int i=1;i<=n;i++) v[i].clear();}
        inline int STRING_TO_INT(string s)
        {
            int res=0,i;
            for (i=0;i<int(s.size());i++) res=res*10+s[i]-'0';
            return res;
        }
        inline void addedge(string s)
        {
            int pos,pos1,i,a[5];s+=" ";int len=int(s.size());
            for (i=1,pos=0;pos<=len-1;pos=pos1+1,i++)
            {
                pos1=s.find(' ',pos);
                a[i]=STRING_TO_INT(s.substr(pos,pos1-pos))+(i<3?1:0);
            }
            v[a[1]].pb(mp(a[2],a[3]));v[a[2]].pb(mp(a[1],a[3]));
        }
        inline void doit_roads(vector<string> roads)
        {
            int i,pos,pos1;string s="";
            for (i=0;i<int(roads.size());i++) s+=roads[i];
            s+=",";pos=0;int len=int(s.size());
            while (pos<=len-1)
            {
                pos1=s.find(',',pos);
                addedge(s.substr(pos,pos1-pos));
                pos=pos1+1;
            }
        }
        inline void dfs(int cur,int father)
        {
            int i,j,k,y,cc=0;
            for (i=0;i<int(v[cur].size());i++)
            {
                y=v[cur][i].x;
                if (y!=father) dfs(y,cur);
            }
            for (i=0;i<=M;i++) tmp[0][i]=0;
            for (i=0;i<int(v[cur].size());i++)
            {
                y=v[cur][i].x;
                if (y!=father)
                {
                    cc++;
                    for (j=0;j<=M;j++)
                    {
                        tmp[cc][j]=INF;
                        for (k=0;k<=j;k++)
                            tmp[cc][j]=min(tmp[cc][j],tmp[cc-1][j-k]+dp[y][k]+((k&1)?v[cur][i].y:v[cur][i].y*2));
                    }
                }
            }
            for (i=0;i<=M;i++) dp[cur][i]=tmp[cc][i];
        }
        inline int minTime(int n,vector<string> roads,int K,int L)
        {
            int i,j;
            Clear(n);
            doit_roads(roads);
            N=n;M=K*2;
            for (i=1;i<=N;i++)
                for (j=0;j<=M;j++)
                    dp[i][j]=INF;
            dfs(1,-1);
            int res=INF;
            for (i=0;i<=M;i+=2) res=min(res,dp[1][i]+L*(i>>1));
            return res;
        }
};

/*---Debug Part---*/
/*
int main ()
{
    vector<string> v;
    int nn,kk,ll;string s;
    KingdomTour A;
    while (scanf("%d%d%d",&nn,&kk,&ll)!=EOF)
    {
        int num;num=getint();v.clear();
        while (num--) getline(cin,s),v.pb(s);
        cout<<A.minTime(nn,v,kk,ll);
    }
    return 0;
}
10 2 4 3
1 2 2,4 1 9,2 5 5,6 5 4,
1 7 7,7 3 1,2 0 2 
,5 8 5,9 5 6
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
08-11 1091

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值