很难的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的状态转移方程:
其中 f[i][c] f [ i ] [ c ] 表示从i到c的边的权值
最后 dp[i][j]=dp[cc][j] d p [ i ] [ j ] = d p [ c c ] [ j ] ,其中 cc是i孩子的个数 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
*/