zoukankan      html  css  js  c++  java
  • UVa10859 Placing Lampposts(双元限制的dp)

    题目链接

    分析:
    改改题面我就能A了 —————yyp

    题面说给出一个无向无环图
    实际上这就是“森林”啊,ta由多棵树组成

    首先,本题的优化目标有两个:
    (我们做过这样的啊)
    放置的灯数a尽量少,被两盏灯照亮的边b尽量多,
    为了统一起见,我们把第二个条件变成:被一盏灯照亮的边c尽量少

    这样有什么用呢

    解决双元限制的dp

    一般来说,如果有两个变量v1和v2需要优化,
    要求首先满足v1最小,在v1相同的情况下v2最小,
    这个时候我们一般会把这两个变量合成一个量:

    M*v1+v2

    (其中M是一个很大的量)
    x=M*v1+v2取最小值的时候
    v1=x/M
    v2=x%M

    M准确的说,是一个比“v2的最大理论值和v2的最小理论值之差”还要大的数
    这样,只要两个解得v1不同,则不管v2差多少,都是v1起决定性作用
    只有v1相同时,才取决于v2
    在本题中,M可以取2000(防止运算时溢出)

    每棵树的决策互不影响,所以我们单独处理,最后加和即可

    每一个节点只会有两个决策,所以我们设计状态f[i][0/1]
    表示i节点的状态是0/1时,i这棵子树的最小x
    但是我们发现当前点的状态与父亲的状态有关,没有单独记录当前点的状态
    所以新的状态变成了:
    f[i][0/1] :表示父亲结点的状态是0/1,i这棵子树的最小x

    每个结点只有两种决策

    • 结点i不放灯
      只有i是根节点或者i的父亲有灯才有这个选择
      此时f[i][1]等于sum{f[k][0]|k取遍i的儿子}
      如果i不是根节点,还需要+1,因为i和父亲的连边只被照亮了一次
    • 结点i放灯
      f[i][j]等于sum{f[k][1]|k取遍i的儿子}+M
      (因为多放了一盏灯,所以M的系数v1++)
      如果j=0且i不为根,还需要+1,因为i和父亲的这条边只被照亮了一次

    Q.

    不是说f[i][0/1]只管i这棵子树的信息,为什么还要维护ta和父亲连边的状态呢?

    A.

    注意这两个+1,都有i不为根的限制
    维护和父亲的关系,是方便dp的时候的回溯

    //这里写代码片
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    
    using namespace std;
    
    const int M=2000;
    const int N=1003;
    int n,m;
    struct node{
        int x,y,nxt;
    };
    node way[N<<1];
    int st[N],tot=0,ans;
    bool vis[N][N];
    int f[N][N];
    
    void add(int u,int w)
    {
        tot++;
        way[tot].x=u;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
        tot++;
        way[tot].x=w;way[tot].y=u;way[tot].nxt=st[w];st[w]=tot;
    }
    
    int dfs(int now,int zt,int fa)   //zt  fa的状态 
    {
        if (vis[now][zt]) return f[now][zt];   //记忆化 
        vis[now][zt]=1;
        int ans;
    
        ans=M;                                 //放灯总是合法的 
        for (int i=st[now];i;i=way[i].nxt)
            if (way[i].y!=fa)
                ans+=dfs(way[i].y,1,now);
        if (zt==0&&fa!=-1) ans++;
        //当前点的父亲没放灯 而且now不为根 
    
        if (zt||fa==-1)          //fa放灯了,now就可以不放  或者now是根节点 
        {
            int sum=0;
            for (int i=st[now];i;i=way[i].nxt)
                if (way[i].y!=fa)
                    sum+=dfs(way[i].y,0,now);
            if (zt&&fa!=-1) sum++;    //now不为根 
            ans=min(ans,sum);
        }
    
        f[now][zt]=ans;
        return ans;
    }
    
    int main()
    {
        int T;
        scanf("%d",&T);
        while (T--)
        {
            memset(st,0,sizeof(st));
            memset(f,0,sizeof(f));
            tot=0; ans=0;
            scanf("%d%d",&n,&m);
            for (int i=1;i<=m;i++)
            {
                int u,w;
                scanf("%d%d",&u,&w);
                u++; w++;
                add(u,w);
            }
            memset(vis,0,sizeof(vis));
            for (int i=1;i<=n;i++)
                if (!vis[i][0])
                    ans+=dfs(i,0,-1);
            printf("%d %d %d
    ",ans/M,m-ans%M,ans%M);   //灯,照亮两次,照亮一次 
        }
    }
  • 相关阅读:
    gcc帮助资料搜找
    由去掉word文档中的一个GoLand复制后残留的底纹说起
    记录一些有趣网站的链接
    linux cpu调度算法发展过程
    啥叫内核线程-搜集
    了解了下啥叫cfs/bfs
    c++重载运算符两种形式的选择
    概念-乐观锁、悲观锁
    go 移位操作的简单自测-移33或65位
    Shell脚本:(delayexec)Cygwin下调用schtasks创建Windows任务计划,实现延迟或定时执行某一命令
  • 原文地址:https://www.cnblogs.com/wutongtong3117/p/7673035.html
Copyright © 2011-2022 走看看