• [JSOI2008]小店购物 & bzoj4349:最小树形图 最小树形图


    ~~~题面(洛谷)~~~

    ~~~题面(bzoj)~~~

    其实是同一道题,,,样例都一模一样

    题解:

    一开始看想了好久,,,还想到了最短路和最小生成树,,然而写的时候才意识到最小生成树应该要用无向边

    其实这题是最小树形图

    细节还是挺多了,,,感觉做了一天,,,,

    表示做得有点失智,不想码字了,这里就放上我代码里的注释吧,

    一些小细节和易错点代码里面也有详细注释,注意看error标注的地方就好了,

    具体操作也有很多注释,,,(没错我就是一个喜欢打注释的人)

    注意到一件事:优惠政策与买的件数无关,也就是说不管买了多少,只要买了就可以优惠,

    这就意味着构造最小树形图的时候边权应该按照1的来算,因为如果要购买多件的话,可以等到最后

    已经买完了所有物品再用最低价购买(肯定可以达到最低价格),

    因为第一次购买的时候是不允许出现环的,(不然的话先购买哪个?肯定有个先后顺序的啊)

    但是后来买就无所谓了,因为东西反正都买了,就比如说买a再买b可以优惠5元,买b再买a可以优惠10元,

    这个时候显然先买b再买a,但我们可以只买一个b,这样的话买完a和b后再购买剩下的b时就可以每件优惠5元了

    1,确定一个根(建立超级源点)

    2,找到除根外每一个点的最小入边,若这些边构成了环(此时必然不联通),则缩环成点,并将环内的每一个点的其他入边都减去环内的入边,

    3,重复步骤2直到没有环出现(构成了树)。

    或者说不用bool记录有没有被访问,而是用vis记录访问它的是谁,因为不能被同一个点多次访问,但被多个点一次访问是合法的

    放上自认为很好看的代码

     

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 #define R register int
      4 #define AC 60
      5 #define ac 10000
      6 int n, m, s, tot, cnt, tmp;
      7 int last[AC], id[AC], buy[AC], vis[AC]; 
      8 int Stack[AC], top;//栈,辅助找环
      9 double ans;
     10 double in[AC];
     11 
     12 struct road{
     13     int x,y;double Size;
     14 }way[ac];
     15 /*注意到一件事:优惠政策与买的件数无关,也就是说不管买了多少,只要买了就可以优惠,
     16 这就意味着构造最小树形图的时候边权应该按照1的来算,因为如果要购买多件的话,可以等到最后
     17 已经买完了所有物品再用最低价购买(肯定可以达到最低价格),
     18 因为第一次购买的时候是不允许出现环的,(不然的话先购买哪个?肯定有个先后顺序的啊)
     19 但是后来买就无所谓了,因为东西反正都买了,就比如说买a再买b可以优惠5元,买b再买a可以优惠10元,
     20 这个时候显然先买b再买a,但我们可以只买一个b,这样的话买完a和b后再购买剩下的b时就可以每件优惠5元了
     21 1,确定一个根(建立超级源点)
     22 2,找到除根外每一个点的最小入边,若这些边构成了环(此时必然不联通),则缩环成点,并将环内的每一个点的其他入边都减去环内的入边,
     23 3,重复步骤2直到没有环出现(构成了树)。
     24 
     25 因为边很少,所以只能形成一个很简单的环,又因为s没有入度,所以s不可能出现在环内,
     26 所以说可以从s出发dfs一遍,如果有点没被访问到就是有环????
     27 
     28 或者说不用bool记录有没有被访问,而是用vis记录访问它的是谁,因为不能被同一个点多次访问,
     29 但被多个点一次访问是合法的
     30 */
     31 
     32 inline void upmin(double &a,double b)
     33 {
     34     if(b < a) a = b;
     35 }
     36 
     37 void pre()
     38 {
     39     double a;int b,c;
     40     scanf("%d", &n);
     41     memset(in, 127, sizeof(in));//原来127是很大的?
     42     s = n + 1;
     43     for(R i = 1; i <= n; i++)
     44     {
     45         scanf("%lf%d", &a, &buy[++tot]);
     46         if(!buy[tot]) 
     47         {
     48             --tot;
     49             continue;
     50         }
     51         --buy[tot];//因为第一个是在建树的时候买的
     52         id[i] = tot;//防止不用买的商品占位置
     53         way[++cnt] = (road){s, tot, a};
     54         upmin(in[tot], a);//找到最低价
     55     }
     56     scanf("%d", &m);
     57     for(R i = 1; i <= m; i++)
     58     {
     59         scanf("%d%d%lf", &b, &c, &a);
     60         if(!id[b] || !id[c]) continue;
     61         way[++cnt] = (road){id[b], id[c], a};
     62         upmin(in[id[c]], a);//获取最低价格
     63     }
     64 }
     65 
     66 void init()
     67 {
     68     for(R i=1;i<=tot;i++)//因为每次标号都要重置,所以现在赶紧加上贡献
     69         if(buy[i]) ans += in[i] * (double)buy[i];//直接枚举标号
     70 }
     71 
     72 void find()//找环
     73 {//error!!!虽然说都是简单环,但是这并不妨碍环有出边,,,,因此还要判断不要误入之前进过的环了
     74     int x;
     75     for(R i = 1; i <= tot; i++)//直接枚举标号
     76     {
     77         top = 0;
     78         ans += in[x = i];//获取新最小边贡献 + 顺便赋值
     79         if(id[x]) continue;//如果已经被发现在环内就加上贡献走人
     80         while(1)//找环
     81         {
     82             if(vis[x] == i || x == s || id[x]) break;//error!!!不要误入之前进过的环了(id[x])
     83             vis[x] = i;
     84             Stack[++top] = x;//存入栈
     85             x = last[x];
     86         }
     87         if(x == i && !id[x])//如果终点被多次访问,error!!!之前进过的环就别去了(id[x])
     88         {//error!!!应该是起点被多次访问,而不是终点,回到起点才是一个环,不然一个环的外向边可能会导致有别的点“误入”环内
     89             ++tmp;
     90             while(x = Stack[top--]) id[x] = tmp;//给环内所有节点都赋上同一个编码
     91         }
     92     }
     93 }
     94         
     95 void get_in()//找最短边 & 前驱
     96 {
     97     int x;
     98     memset(in, 127, sizeof(in));//重置最短边
     99     for(R i = 1; i <= cnt; i++)//直接枚举边,这样更省时
    100     {
    101         x = way[i].y;//存下目标点
    102         if(way[i].x == x) continue;//如果在一个点里那就算了
    103         if(way[i].Size < in[x])
    104         {
    105             last[x] = way[i].x;
    106             in[x] = way[i].Size;//更新最短边
    107         }
    108     }
    109 }
    110         
    111 void work()
    112 {
    113     while(1)
    114     {
    115         get_in();
    116         memset(id, 0, sizeof(id));//重置标号
    117         memset(vis, 0, sizeof(vis));//重置访问标记
    118         id[s] = s, tmp = 0;//重置标号计数,error!!!注意id[s]永远是s
    119         find();
    120         if(!tmp)//如果没有找到环就退出
    121         {
    122             printf("%.2lf
    ",ans);
    123             return ;
    124         } 
    125         for(R i = 1; i <= tot; i++)
    126             if(!id[i]) id[i] = ++tmp;//如果还没有编号,就统一编号
    127         tot = tmp;
    128         for(R i = 1; i <= cnt; i++)//每次都更新所有边的所有信息
    129         {
    130             way[i].Size -= in[way[i].y];//权值减去入边的最小边权值
    131             way[i].x = id[way[i].x];//赋为新点
    132             way[i].y = id[way[i].y];
    133         }
    134     }
    135 }
    136 
    137 int main()
    138 {
    139     freopen("in.in","r",stdin);
    140     pre();
    141     init();
    142     work();    
    143     fclose(stdin);
    144     return 0;
    145 }
  • 相关阅读:
    spring boot 启动原理
    log4j相关配置
    JAVA多线程之volatile 与 synchronized 的比较
    Mybatis 一对一、一对多、多对多
    缓存
    spring boot 总结
    学习网站
    Kafka(一)
    hbase(二)
    Zookeeper那些事
  • 原文地址:https://www.cnblogs.com/ww3113306/p/9160211.html
Copyright © 2020-2023  润新知