【XSY1905】【XSY2761】新访问计划 二分 树型DP

题目描述

  给你一棵树,你要从\(1\)号点出发,经过这棵树的每条边至少一次,最后回到\(1\)号点,经过一条边要花费\(w_i\)的时间。

  你还可以乘车,从一个点取另一个点,需要花费\(c\)的时间。

  你最多做\(k\)次车。

  问最短时间。

  \(k\leq n\leq 20000,w,c\leq 50000\)

题解

  我们考虑把最终路线中坐车的部分替换成走路。

  那么显然不会经过一条边超过两次。

  但是每条边都要经过者少一次,所以每条边只能被一个坐车的路线覆盖。

  所以我们要选择不超过\(k\)条不相交的链,把这些链用\(c\)的代价覆盖掉。

  可以用树上背包做。

  时间复杂度:\(O(nk)\)

  还有有一个网络流的做法:每次找树上最长链,然后用\(c\)的代价覆盖掉,即把路径上的边权取反。

  正解:

  如果我们可以调整乘车的代价,并把乘车次数设为无限次,那么当最优方案的乘车次数不超过\(k\)时最优方案的路线就是最优路线。

  这个东西可以用一次树型DP解决。

  设\(f_i\)为从\(i\)开始遍历以\(i\)为根的子树并回到\(i\)的最小代价和乘车次数,\(g_i\)为从\(i\)开始遍历以\(i\)为根的子树并乘车回到\(i\)的最小代价和乘车次数。

  然后随便DP一下就行了。

  可以观察到,乘车次数是随着乘车代价单调下降的(可能是非连续的),所以可以二分乘车代价,得到答案。

  时间复杂度:\(O(n\log nw)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<utility>
using namespace std;
typedef unsigned un;
typedef pair<un,un> pii;
const int inf=0x3fffffff;
vector<pii> t[100010];
int n,k,c;
un cost;
pii f[100010];
pii g[100010];
pii operator +(pii a,pii b)
{
	return pii(a.first+b.first,a.second+b.second);
}
pii operator -(pii a,pii b)
{
	return pii(a.first-b.first,a.second-b.second);
}
void dp(int u,int fa)
{
	f[u]=pii(0,0);
	g[u]=pii(cost,1);
	for(auto a:t[u])
		if(a.first!=fa)
		{
			int v=a.first;
			int w=a.second;
			dp(v,u);
			pii f1=pii(inf,0);
			pii g1=pii(inf,0);

			f1=min(f1,f[u]+f[v]+pii(w,0));
			f1=min(f1,f[u]+f[v]+pii(cost,1));
			f1=min(f1,f[u]+g[v]);
			f1=min(f1,g[u]+f[v]);
			f1=min(f1,g[u]+g[v]-pii(cost,1));

			g1=min(g1,f[u]+f[v]+pii(cost,1));
			g1=min(g1,f[u]+g[v]);
			g1=min(g1,g[u]+f[v]+pii(w,0));
			g1=min(g1,g[u]+g[v]);

			f[u]=f1;
			g[u]=g1;
		}
}
void solve()
{
	for(int i=1;i<=n;i++)
		t[i].clear();
	int x,y,z;
	int sum=0;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		x++;
		y++;
		t[x].push_back(pii(y,z));
		t[y].push_back(pii(x,z));
		sum+=z;
	}
	cost=c;
	dp(1,0);
	if(f[1].second<=k)
	{
		printf("%d\n",sum+f[1].first);
		return;
	}
	int l=0,r=inf;
	while(l<r)
	{
		cost=(l+r)>>1;
		dp(1,0);
		if(f[1].second>k)
			l=cost+1;
		else
			r=cost;
	}
	cost=l;
	dp(1,0);
	int ans=f[1].first-k*(l-c)+sum;
	printf("%d\n",ans);
}
int main()
{
#ifndef ONLINE_JUDGE
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
#endif
	while(~scanf("%d%d%d",&n,&k,&c))
		solve();
	return 0;
}
posted @ 2018-03-22 11:36  ywwyww  阅读(194)  评论(0编辑  收藏  举报