• 新访问计划


    img

    img
      
    ​  
      
      
      
      
      
      
      

    Solution

      
      记总边权为(len)
      
    ​  如果不可以乘车,那么答案就是(len*2),因为最优解时每条边都被经过两次。
      
    ​  如果可以乘车,那么每次乘车等价于将两个点之间的路径的经过次数-1,并给总花费加上(C)
      
    ​   又由于必须走着经过每一条边至少一次,所以乘车的路径不可以相交。
      
      答案必须至少有(len)
      
      随后问题等价于用不超过(K)条路径覆盖原树,每条路径花费为(C),未覆盖的边的花费为原边权的最小总花费,加上(len)就是答案。
       
       
      
      先考虑前55分的做法。
      
    ​   设(f[i][j])表示在(i)子树内乘坐了(j)次巴士,且有一条巴士路径的一端是(i)的最小花费。
      
    ​   设(g[i][j])表示在(i)子树内乘坐了(j)次巴士,不要求(i)是某条巴士路径的一段的最小花费。
      
      ​ 这样可以用(O(n^2))的树型DP解决,在这里不详细阐述。
      
      
      
      ​ 如何改进?
      
    ​   DP的第一维状态难以省去,而表示坐车次数的第二维状态或许可以用其他方式代替。
      
    ​   设函数(F(cost)=(x,y))表示不限坐车次数,单次坐车花费改成(cost)时,最小总花费为(x),坐车次数为(y)
      
      ​ 其中,如果存在一条路径使得路径边权和等于(cost),那么选择走路而不坐车。
      
    ​   可以发现(cost)增大时,(y)单调不增,且(y)不是连续的。但是(cost)(x)没有单调关系。
      
    ​   根据前一个性质,如果对(F)(y)进行一次差分,(F(cost-1)_y-F(cost)_y)会等于(F(cost))方案中未被覆盖的路径长等于(cost)的路径数,它们处于一个均衡点上,(cost)变大必定走路,(cost)变小必定尽量坐车。
      
    ​   有一种方式理解(x)(y)的关系,即(x=y*cost+sumw),其中(sumw)是未被覆盖的边的边权之和。
      
    ​   (F)的求值是通过(O(n))的DP实现的,做法下面再讲。
      
      
      
    ​   首先判断(F(C))(y)是否满足(yleq K),显然这样直接找到了最优解,输出(x)
      
    ​   否则此时(y > K)。可以证明坐满(K)次车一定是最优的:因为要从(y-K)条路径中选一些改成走路,已经割舍一部分利益了,那么剩下的(K)条路径一定要选择坐车,不然答案将更劣。
      
    ​   我们二分(cost)的取值,直到(y)为最大的,小于等于(K)的值为止。
      
    ​   此时(cost)是大于(C)的(因为(K)要减小,(cost)必须要增大)。
      
    ​   此时(F(cost))对应着一种坐车方案,坐了(y)次车。
      
    ​   题解给出了一个奇怪定理,可是我不会证明,只能感性理解:
      
      如果我有一种在费用为(c)时乘坐(k)次巴士的最佳方案,不论坐车费用改为多少,如果一定要坐(k)次巴士,按这种方案坐(k)次车一定是最优的。
       
    ​   将这种方案的坐车费用直接修改成(C),其总费变为(y*C+sumw). 那么此时答案为(len+F(cost)_x+(C-cost)*y).
      
      ​ 注意这依然是不对的,如果二分出来的(F(cost))(y)小于(K),那么说明若(cost)再减小1,(y)将直接跳过(K)变成大于(K)。根据(F)的性质,(F(cost))的方案存在(F(cost-1)_y-F(cost)_y)条不坐车的路径的边权之和为(cost)。此时我们多出了(K-y)次坐车机会可以使用而且(C<cost),那当然是尽可能把这些长度为(cost)的路径换成一次只需(C)的巴士!
      
    ​   那么修正后的答案是(len+F(cost)_x+(C-cost)*y+(C-cost)*(K-y))
      
      ​ 化简得到(len+F(cost)_x+(C-cost)*K)
      
      
      
      ​ 简单讲讲(F(cost))的求法:
      
      ​ 记(f[i])表示(i)子树内有一条巴士路径的一端是(i)的最小花费及其乘坐巴士次数;
      
      ​ (g[i])表示(i)子树内不要求有一条巴士路径一端为(i)的最小花费及其乘坐巴士次数。
      
      ​ 设当前遍历到(i),则有初始状态(f[i]=(cost,1))(g[i]=(0,0))
      
      ​ 接着枚举(i)的儿子(j),有如下转移:
      
      

    [egin{aligned} f[i]&=min{(f[i]_x+g[j]_x+w_j,f[i]_y+g[j]_y);,;(g[i]_x+f[j]_x,g[i]_y+f[j]_y)}\ g[i]&=min{(f[i]_x+f[j]_x-cost,f[i]_y+f[j]_y-1);,;(g[i]_x+g[j]_x+w_j,g[i]_y+g[j]_y)} end{aligned} ]

      ​ 计算完成后还有(f[i]=min{f[i];,;(g[i]_x+cost,g[i]_y+1)}),最后还有(g[i]=min{g[i];,;f[i]})
      
    ​   最终(F(cost)=g[root])
      
      
      

    #include <cstdio>
    using namespace std;
    const int N=100005,inf=2000000000;
    int n,K,C,sumw;
    int h[N],tot;
    struct Edge{int v,w,next;}G[N*2];
    int cost;
    struct Data{
    	int x,y;
    	Data(){}
    	Data(int _x,int _y){x=_x;y=_y;}
    	friend bool operator < (const Data &u,const Data &v){
    		if(u.x!=v.x) return u.x<v.x;
    		return u.y<v.y;
    	}
    };
    Data f[N],g[N];
    inline Data min(Data x,Data y){return x<y?x:y;}
    inline void addEdge(int u,int v,int w){
    	G[++tot].v=v; G[tot].w=w; G[tot].next=h[u]; h[u]=tot;
    }
    void init(){
    	sumw=0;
    	tot=0;
    	for(int i=0;i<=n;i++) h[i]=0;
    }
    void dfs(int u,int fa){
    	f[u]=Data(cost,1); g[u]=Data(0,0);
    	for(int i=h[u],v;i;i=G[i].next)
    		if((v=G[i].v)!=fa){
    			dfs(v,u);
    			Data tf=min(Data(f[u].x+g[v].x+G[i].w,f[u].y+g[v].y),
    						Data(g[u].x+f[v].x,g[u].y+f[v].y));
    			Data tg=min(Data(f[u].x+f[v].x-cost,f[u].y+f[v].y-1),
    						Data(g[u].x+g[v].x+G[i].w,g[u].y+g[v].y));
    			f[u]=min(tf,Data(tg.x+cost,tg.y+1));
    			g[u]=min(tf,tg);
    		}
    }
    int solve(int cst){
    	cost=cst;
    	dfs(0,-1);
    	return g[0].y;
    }
    int main(){
    	while(~scanf("%d%d%d",&n,&K,&C)){
    		init();
    		for(int i=1;i<n;i++){
    			int u,v,w;
    			scanf("%d%d%d",&u,&v,&w);
    			addEdge(u,v,w); addEdge(v,u,w);
    			sumw+=w;
    		}
    		if(solve(C)<=K){
    			printf("%d
    ",sumw+g[0].x);
    			continue;
    		}
    		int l=C+1,r=inf,mid,ans;
    		while(l<=r){
    			int mid=(l+r)>>1;
    			if(solve(mid)>K) l=mid+1;
    			else ans=mid,r=mid-1;
    		}
    		solve(ans);
    		//printf("%d
    ",sumw+g[0].x-(ans-C)*K);
    		printf("%d
    ",sumw+g[0].x-(ans-C)*(K-g[0].y)-(ans-C)*g[0].y);
    	}
    	return 0;
    }
    
  • 相关阅读:
    [BJDCTF 2nd]fake google
    flask之ssti模板注入初窥
    ctfshow-web14
    ctfshow-web 13
    ctfshow-web12
    ctfshow-web 11
    ctfshow-web10
    ctfshow-web9
    python学习笔记(四)- 常用的字符串的方法
    python学习笔记(三)- 字典、集合
  • 原文地址:https://www.cnblogs.com/RogerDTZ/p/8626641.html
Copyright © 2020-2023  润新知