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


    题目描述

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

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

      你最多做(k)次车。

      问最短时间。

      (kleq nleq 20000,w,cleq 50000)

    题解

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

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

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

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

      可以用树上背包做。

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

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

      正解:

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

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

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

      然后随便DP一下就行了。

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

      时间复杂度:(O(nlog 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
    ",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
    ",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;
    }
    
  • 相关阅读:
    【转载】C/C++预处理器
    【转载】C/C++内存管理详解
    Spring知识点整理
    Hibernate知识点整理
    MyBatis知识点整理
    数据可视化(三)- Seaborn简易入门
    数据可视化(二)
    数据可视化(一)-Matplotlib简易入门
    Pandas之容易让人混淆的行选择和列选择
    Pandas简易入门(四)
  • 原文地址:https://www.cnblogs.com/ywwyww/p/8622573.html
Copyright © 2020-2023  润新知