[JSOI2008]小店购物 & bzoj4349:最小树形图 最小树形图
其实是同一道题,,,样例都一模一样
题解:
一开始看想了好久,,,还想到了最短路和最小生成树,,然而写的时候才意识到最小生成树应该要用无向边
其实这题是最小树形图
细节还是挺多了,,,感觉做了一天,,,,
表示做得有点失智,不想码字了,这里就放上我代码里的注释吧,
一些小细节和易错点代码里面也有详细注释,注意看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\n",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 }