• 【洛谷P2515【HAOI2010】】软件安装


    题目描述

    现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi。我们希望从中选择一些软件安装到一台磁盘容量为M计算机上,使得这些软件的价值尽可能大(即Vi的和最大)。

    但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件j(包括软件j的直接或间接依赖)的情况下才能正确工作(软件i依赖软件j)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为0。

    我们现在知道了软件之间的依赖关系:软件i依赖软件Di。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则Di=0,这时只要这个软件安装了,它就能正常工作。

    输入输出格式

    输入格式:

    第1行:N, M (0<=N<=100, 0<=M<=500)

    第2行:W1, W2, ... Wi, ..., Wn (0<=Wi<=M )

    第3行:V1, V2, ..., Vi, ..., Vn (0<=Vi<=1000 )

    第4行:D1, D2, ..., Di, ..., Dn (0<=Di<=N, Di≠i )

    输出格式:

    一个整数,代表最大价值

    输入输出样例

    输入样例#1

    3 10
    5 5 6
    2 3 4
    0 1 1

    输出样例#1

    5

    算法:

    树形DP

     

    分析:

    一看到这道题,就感觉和选课很相似,不过再仔细一看,就发现其实之间大有不同。

    选课一题,是不可以成环的,因为先修课的先修课不可能成为后面的后修课,所以,判环是没必要的。

    但是这道题,非常坑爹,它可以出现环。若出现a依赖b,b依赖c,c依赖a,那么我们究竟怎么搜索呢,而且转移方程也不好写。

    我们通过观察发现,这种环状的问题,要是选了其中一个,那么就意味着要把整个环给选了,要么不选,就得整个都不选。

    所以,我们就可以从这里入手,把一个个的环缩成一个个新的点,一个点代表了环的整体。

    接下来就可以通过选课的后续思路,建造一棵左儿子右兄弟的二叉树,通过多叉树转二叉树进行记忆化搜索。

     

    后面的就很简单了,状态转移方程就是要不要当前的兄弟,还是把机会分给兄弟和孩子。

     

    不过中途有很多小点需要注意。

    判环要用floyed的方法三重for来判断环,记得三个循环的变量顺序不能错,否则后果会判不到环。

    其次,缩点建点的时候,主体分三个情况来判断:

    1、这是新环,我需要建点来记录,他的价值和体积我都直接相加就好了。不过我们要记得把已经转移的新点的原来两个旧点序号变成负数,而新点的下标+旧点的内容恰好是等于n的,每一个环一一对应,不多也不会少,详情见代码操作。

    2、这是旧环,我发现了新的点,这个点在环内。那么我就把这个点并到环中,把价值和体积也加到新点上,然后把原来这个旧环的密码推到这个新点上。

    3、找到一个新点,他和一个旧环有联系,但不属于这个环的一部分,于是把它的从属关系推到新的点上。

     

     

    步骤简化为:判环+缩点+多叉转二叉+记忆化搜索。

     

    上代码:

     

      1 #include<cstdio>
      2 #include<iostream>
      3 #include<cctype>
      4 using namespace std;
      5 
      6 const int size=600;
      7 int n,m,ans,sum,d[size],v[size],w[size],f[size][4*size],b[size],c[size];
      8 bool a[size][size];                                //是否连通
      9 
     10 inline int read()                                //读入优化
     11 {
     12     int f=1,x=0;
     13     char c=getchar();
     14     while (!isdigit(c))
     15         f=c=='-'?-1:1,c=getchar();
     16     while (isdigit(c))
     17         x=(x<<1)+(x<<3)+(c^48),c=getchar();
     18     return x*f;
     19 }
     20 
     21 void floyed()                                    //Floyd判环
     22 {
     23     int i,j,k;
     24     for (k=1;k<=n;k++)
     25         for (i=1;i<=n;i++)
     26             for (j=1;j<=n;j++)
     27                 if (a[j][k]&&a[k][i])
     28                     a[j][i]=1;
     29 }
     30 
     31 void connect()                                //缩点建点
     32 {
     33     ans=n;                                //ans表示新点的下标
     34     int i,j;
     35     for (i=1;i<=ans;i++)
     36         for (j=1;j<=ans;j++)
     37         {
     38             if (a[i][j]&&a[j][i]&&i!=j&&w[i]>0&&w[j]>0)        //发现新环
     39             {
     40                 sum--;                    //sum是旧点的密码
     41                 ans++;
     42                 w[ans]=w[i]+w[j];
     43                 v[ans]=v[i]+v[j];
     44                 w[i]=w[j]=sum;
     45             }
     46             else
     47             if (w[d[j]]<0&&w[j]>0)                    //发现旧环
     48             {
     49                 if (a[d[j]][j]&&a[j][d[j]])                //属于环
     50                 {
     51                     w[n-w[d[j]]]+=w[j];
     52                     v[n-w[d[j]]]+=v[j];
     53                     w[j]=w[d[j]];
     54                 }
     55                 else                                    //不属于环
     56                 if (a[d[j]][j]&&!a[j][d[j]]||!a[d[j]][j]&&a[j][d[j]])
     57                     d[j]=n-w[d[j]];
     58             }
     59         }
     60 }
     61 
     62 int dfs(int root,int val)                                //记忆化搜索
     63 {
     64     if (f[root][val]>0)
     65         return f[root][val];
     66     if (root==0||val<=0)
     67         return 0;
     68     f[b[root]][val]=dfs(b[root],val);
     69     f[root][val]=f[b[root]][val];
     70     int i,k=val-w[root];
     71     for (i=0;i<=k;i++)
     72     {
     73         f[b[root]][k-i]=dfs(b[root],k-i);
     74         f[c[root]][i]=dfs(c[root],i);
     75         f[root][val]=max(f[root][val],v[root]+f[b[root]][k-i]+f[c[root]][i]);
     76     }
     77     return f[root][val];
     78 }
     79 
     80 int main()
     81 {
     82     int i;
     83     n=read();
     84     m=read();
     85     for (i=1;i<=n;i++)
     86         w[i]=read();
     87     for (i=1;i<=n;i++)
     88         v[i]=read();
     89     for (i=1;i<=n;i++)
     90     {
     91         d[i]=read();
     92         a[d[i]][i]=1;
     93     }
     94     floyed();
     95     connect();
     96     for (i=1;i<=ans;i++)                            //模拟链表
     97         if (w[i]>0)
     98         {
     99             b[i]=c[d[i]];
    100             c[d[i]]=i;
    101         }
    102     printf("%d",dfs(c[0],m));
    103     return 0;
    104 }

    这道题其实难度很大,不过只要把原理弄懂,代码还是很好打的,主要就是围绕着如何更加简便地进行记忆化搜索,然后一层层地进行优化,这道题用到的就是判环和缩点的知识。

     

    嗯,就这样了。

  • 相关阅读:
    设计模式(简述)
    sql注入防御
    两个防SQL注入过滤代码
    SQL注入实战利用“dbo”获得SQL管理权限和系统权限
    SQL注入技术和跨站脚本攻击的检测
    蓝雨设计整站SQL注入漏洞
    SQL注入攻击零距离
    菜鸟入门级:SQL注入攻击
    三步堵死SQL注入漏洞
    终极防范SQL注入漏洞
  • 原文地址:https://www.cnblogs.com/Ronald-MOK1426/p/8449686.html
Copyright © 2020-2023  润新知