码迷,mamicode.com
首页 > 其他好文 > 详细

新访问计划

时间:2018-03-22 22:34:52      阅读:215      评论:0      收藏:0      [点我收藏+]

标签:next   opera   scan   mat   play   多少   post   分享图片   状态   

技术分享图片

技术分享图片
  
?  
  
  
  
  
  
  
  

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\)是否满足\(y\leq 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\),有如下转移:
  
  
\[ \begin{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\n",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\n",sumw+g[0].x-(ans-C)*K);
        printf("%d\n",sumw+g[0].x-(ans-C)*(K-g[0].y)-(ans-C)*g[0].y);
    }
    return 0;
}

新访问计划

标签:next   opera   scan   mat   play   多少   post   分享图片   状态   

原文地址:https://www.cnblogs.com/RogerDTZ/p/8626641.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!