zoukankan      html  css  js  c++  java
  • [NOIP10.3模拟赛]3.w题解--神奇树形DP

    题目链接:

    闲扯:

    这题考场上把子任务都敲满了,5个namespace,400行11k

    结果爆0了哈哈,因为写了个假快读只能读入一位数,所以手测数据都过了,交上去全TLE了

    把边分成三类:0. 需要染色的 1. 不需要染色的 2. 染不染色无所谓

    考场上首先发现一个性质,就是一定存在一种最优解没有染任何一条本来不需要的染色边。

    为啥?其实也挺显然的,因为你染色跨过这条边还得染这条边一次,不如直接只染左右的联通块,这样总路径长度还能更小

    但是第三种边的呢?有个子任务就是枚举它染不染。

    然后链上的情况就搞了个贪心的做法,如果对于一条第三种情况的边,如果两边的联通块是需要染色的,显然选这条边是更优的.但是注意考虑多条这种边连在一起的情况.

    然后树上的版本贪心似乎就GG了

    然后晚上盯着毫无注释std和仅有三行的题解,画了一面的草稿纸,终于看懂了...

    分析:

    首先我们需要假如我们已经有了最优情况的一种边集,怎么求出最小操作次数,其实是边集的点集中度数为奇数的点的个数除以2。为什么?显然最优情况下,每一个奇数度数点恰好是一条操作路径的结尾,由于一条操作路径连接两个点所以除以2

    然后对于一个以x为根的子树,如果已经取得了最优解,可以通过分情况考虑更新父亲,显然是具有最优子结构的.于是使用树形DP

    定义(f[x][0/1])表示在以x为根的子树中,不染色/染色x与其父亲相连的最优代价

    (注意代价是一个二元组(cost(x,y))代表奇数点个数和染色路径总长度,这里比较代价大小根据题意,就不赘述了)

    为了分情况转移我们需要求出两个值,(npt)是x在x为根的子树中假如x不是任意一条染色路径的端点的最优代价;(pt)是x在x为根的子树中假如x是某一条染色路径的端点时的最优代价(注意这里的路径端点都是从子树引出来的路径)

    怎么求出(npt)(pt)呢?我们可以将所有(x)的儿子(v)回溯到(x)的过程中逐个统计,接下来比较神奇可以借助图像理解

    (npt=min(npt+f[v][0],pt+f[v][1]))

    解释: 画图,若(v)到其父亲(x)边没有染色,说明原来如果x不是路径端点的话现在还不会是端点;类似的,若(v)到父亲(x)的边染色了,并且(x)此时是一条路径的端点,那么这时候我们可以把路径延长到(v)的子树中,这样是解更优并且(x)这样就不会是路径端点了

    (pt = min(npt+f[v][1],pt+f[v][0]))

    解释:与上面类似就太懒不想打了,有问题可以luogu或QQ联系我

    在考虑DP中(f)数组的转移

    再强调一下定义:定义(f[x][0/1])表示在以x为根的子树中,不染色/染色x与其父亲相连的最优代价

    (x)与其父亲相连的边的属性为(p)

    首先根据在"闲扯"中的性质以及题意,若(p=1,f[now][1]=(inf,inf));若(p=0,f[now][0]=(inf,inf))

    然后现在先考虑(f[now][0]),(f[now][0]=min(npt,cost(pt.x+1,pt.y))) , 这里的(cost)可以理解为(make)_(pair)

    解释:如果(x)不是染色路径的端点,而且x到父亲的边也不染色,那么显然代价不变.但是如果(x)是某一条路径的端点,那么这时候我们需要加上(x)这个点的贡献(它是个端点那肯定是奇数度数)

    再考虑(f[now][1],f[now][1]=min(cost(npt.x+1,npt+1),cost(pt.x,pt.y+1)))

    解释:如果(x)不是路径端点,那么(x)如果和父亲相连的边染色的需要新开一条路径,所以见上;如果(x)是路径端点,那么我们可以把这条路径引上去,x点的贡献就不用计算,只需要让路径长度加1就好了

    然后就没了,代码很短但很难想

    代码:

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cctype>
    #include <iostream>
    #include <vector>
    #define ll long long 
    #define ri register int 
    using std::min;
    using std::max;
    template <class T>inline void read(T &x){
        x=0;int ne=0;char c;
        while(!isdigit(c=getchar()))ne=c=='-';
        x=c-48;
        while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
        x=ne?-x:x;return ;
    }
    const int maxn=100005;
    const int inf=0x7ffffff;
    struct Edge{
        int ne,to,w;
    }edge[maxn<<1];
    int h[maxn],num_edge=1;
    inline void add_edge(int f,int to,int x){
        edge[++num_edge].ne=h[f];
        edge[num_edge].to=to;
        edge[num_edge].w=x;
        h[f]=num_edge;
    }
    struct Dat{
        int x,y;
        Dat(){x=y=inf;}
        Dat(int _x,int _y){x=_x,y=_y;}
        Dat operator +(const Dat &b)const{
            return Dat(x+b.x,y+b.y);
        }
        bool operator <(const Dat &b)const{
            return x==b.x?y<b.y:x<b.x;
        }
    }f[maxn][2];
    int n;
    void dfs(int now,int fa,int t){
        int v;
        Dat npt=Dat(0,0),pt=Dat(inf,inf);//npt--不是路径端点  pt是路径端点
        Dat pa=Dat(0,0),pb=Dat(0,0);
        for(ri i=h[now];i;i=edge[i].ne){
            v=edge[i].to;
            if(v==fa)continue;
            dfs(v,now,edge[i].w);
            pa=min(npt+f[v][0],pt+f[v][1]);
            pb=min(npt+f[v][1],pt+f[v][0]);
            npt=pa,pt=pb;
        }
        if(t==1)f[now][0]=Dat(inf,inf);//必须要翻转
        else {
            f[now][0]=min(npt,Dat(pt.x+1,pt.y));
        }
        if(t==0)f[now][1]=Dat(inf,inf);//性质--翻转1边一定更不优
        else {
            f[now][1]=min(Dat(npt.x+1,npt.y+1),Dat(pt.x,pt.y+1));
        }
        return;
    }
    int main(){
        int x,y,c,d;
        freopen("w.in","r",stdin);
        freopen("w.out","w",stdout);
        read(n);
        for(ri i=1;i<n;i++){
            read(x),read(y),read(c),read(d);
            if(d==2){
                add_edge(x,y,2),add_edge(y,x,2);
            }
            else {
                d=c^d;
                add_edge(x,y,d),add_edge(y,x,d);
            }
        }
        dfs(1,0,0);
        Dat ans=min(f[1][0],f[1][1]);
        printf("%d %d
    ",ans.x/2,ans.y);
        return 0;
    }
    
    
  • 相关阅读:
    C# Sleep延时方法
    浅谈模糊测试
    python time模块常用方法小结
    Markdown使用小结
    关于测试用例设计、评审及用例质量评估的思考
    关于评估软件产品质量的思考
    关于软件测试工程师进阶提升的思考
    关于软件测试中回归测试的思考
    测试技术的思考 ---- 读《微软的软件测试之道》有感系列
    vue-learning:22
  • 原文地址:https://www.cnblogs.com/Rye-Catcher/p/9743917.html
Copyright © 2011-2022 走看看