BZOJ4349 最小树形图

Description

小C现在正要攻打科学馆腹地------计算机第三机房。而信息组的同学们已经建好了一座座堡垒,准备迎战。小C作为一种高度智慧的可怕生物,早已对同学们的信息了如指掌。
攻打每一个人的堡垒需要一个代价,而且必须攻打若干次才能把镇守之人灭得灰飞烟灭。
当小C在绞尽脑汁想攻打方案时,突然从XXX的堡垒中滚出来一个纸条:一个惊人的秘密被小C发现了:原来各个堡垒之间会相互提供援助,但是当一 个堡垒被攻打时,他对所援助的堡垒的援助就会停止,因为他自己已经自身难保了。也就是说,小C只要攻打某个堡垒一次之后,某些堡垒就只需要花更小的代价攻 击了。
现在,要你求消灭全机房要用掉代价最小多少。

Input

第一行一个数N,(N<=50),表示机房修建的堡垒数。
接下来N行,每行两个数,第一个实数Ai表示攻打i号堡垒需要的代价Ai(0<Ai<=1000)。第二个数Bi(0<Bi<100)表示i号堡垒需要被攻打Bi次。
接下来一个数k,表示总共有k组依赖关系。
接下来k行每行三个数x,y,z(x,y,为整数,z为实数),表示攻打过一次x号堡垒之后,攻打y号堡垒就只要花z的代价,保证z比y原来的代价小。
不需要攻打的城堡不允许攻打。

Output

一行,一个实数表示消灭全机房要用的最小代价,保留两位小数。

Sample Input

3
10.00 1
1.80 1
2.50 2
2
1 3 2.00
3 2 1.50

Sample Output

15.50
 
 
正解:最小树形图(朱-刘算法)
解题报告:

  显然,我们肯定是只需要考虑第一次是如何买的,今后的都是按最低价格即可。正解是朱-刘算法,也就是最小树形图裸题。调了我好久,结果又是一个低级错误...

  朱-刘算法求得是有向图的最小树形图,有向图的最小树形图类似于最小生成树,只不过因为是有向图,所以定义改成了:最小树形图中存在一个点,可以到达全图的所有点,并且边权和最小。

  简单讲一下构造方法,首先构出一张这样的图:新建一个虚拟结点,把每个结点的父亲定为入边权值最小的边的那个点,然后我们把构出来的图的所有边权加起来,显然我们会得到n-1条边,那么这些边只有两种情况,要么已经构成树,那么此时可以结束;否则一定存在环,那么我们把环上的点全都缩成一个点,并且把所有边中原本不属于环中的边,且指向环内的点的这些边权值修改为原边权减去这个环内的点的环内入边边权。然后反复执行上述操作,直到不存在环时即可停止并得到答案。

  代码如下:

 1 //It is made by jump~
 2 #include <iostream>
 3 #include <cstdlib>
 4 #include <cstring>
 5 #include <cstdio>
 6 #include <cmath>
 7 #include <algorithm>
 8 #include <ctime>
 9 #include <vector>
10 #include <queue>
11 #include <map>
12 #include <set>
13 using namespace std;
14 typedef long long LL;
15 const int MAXN = 52;
16 const int MAXM = 10011;
17 int n,m,ecnt;
18 int num[MAXN],father[MAXN],vis[MAXN],belong[MAXN];
19 double price[MAXN];
20 double low[MAXN];
21 int first[MAXN],match[MAXN],gong;
22 double ans;
23 int root;
24 struct edge{
25     int x,y;
26     double z;
27 }e[MAXM];
28 
29 inline int getint()
30 {
31        int w=0,q=0; char c=getchar();
32        while((c<'0' || c>'9') && c!='-') c=getchar(); if(c=='-') q=1,c=getchar(); 
33        while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); return q ? -w : w;
34 }
35 
36 inline void min_dir_build(){//相当于是求第一次选取的先后顺序
37     int k,cnt=0;
38     while(1) {
39     for(int i=1;i<=n;i++) low[i]=1e20;
40     for(int i=1;i<=n;i++) father[i]=belong[i]=vis[i]=0;    
41     for(int i=1;i<=m;i++) if(e[i].x!=e[i].y && e[i].z<low[e[i].y]) father[e[i].y]=e[i].x,low[e[i].y]=e[i].z;
42     low[root]=0; cnt=0; //for(int i=1;i<=n;i++) if(low[i]==1e20) return ;    
43     for(int i=1;i<n;i++) {
44         ans+=low[i];
45         for(k=i;(belong[k]==0)&&(vis[k]!=i)&&(k!=root);k=father[k]) vis[k]=i;
46         if((k!=root)&&(belong[k]==0)) {
47         belong[k]=++cnt;
48         for(int j=father[k];j!=k;j=father[j]) belong[j]=cnt;
49         }
50     }
51     if(!cnt) return ;
52     for(int i=1;i<=n;i++) if(!belong[i]) belong[i]=++cnt;
53     double t;//!!!double
54     for(int i=1;i<=m;i++) {
55         t=low[e[i].y];
56         e[i].x=belong[e[i].x];e[i].y=belong[e[i].y];
57         if(e[i].x!=e[i].y) e[i].z-=t;
58     }
59     n=cnt; root=belong[root];//!!!!!!
60     }
61 }
62 
63 inline void work(){
64     n=getint(); double t; int nn;
65     for(int i=1;i<=n;i++) { 
66     scanf("%lf",&t);
67     nn=getint(); 
68     if(nn) {
69         match[i]=++gong; low[gong]=1e20; 
70         price[gong]=t; num[gong]=nn; 
71         e[gong].y=gong; //不是i!!!
72         e[gong].z=price[gong];
73     }
74     }
75     n=gong+1; root=n; m=gong; 
76     for(int i=1;i<n;i++) e[i].x=n;
77     int nowm=getint(); for(int i=1;i<n;i++) low[i]=price[i];
78     int x,y;
79     while(nowm--){
80     x=getint(),y=getint(),scanf("%lf",&t);
81     if(!match[x] || !match[y]) continue;  
82     //if(x==y) continue;//去掉自环
83     e[++m].x=match[x]; e[m].y=match[y]; e[m].z=t;
84     low[e[m].y]=min(low[e[m].y],e[m].z);
85     }
86     for(int i=1;i<n;i++) if(num[i]>1) ans+=(num[i]-1)*low[i];//显然是先买掉所有需要买的,然后剩下的都是可以享受最优折扣的。
87     min_dir_build();    
88     printf("%.2lf",ans);
89 }
90 
91 int main()
92 {
93   work();
94   return 0;
95 }

 

 
 
  然而我其实用搜索艹掉了这道题。

  首先第一步剪枝,我们可以考虑对于所有商品预处理一下他能够到达的最便宜的状态是多少,如果当前已经能够取到最便宜了的话,就把所有已经取到最便宜的先全部买掉,这肯定是最优的,然后如果发现当前状态下只要有一个是最优的了,就没必要逐个一个一个买了,因为把当前最优的留到后面去买是完全没必要的,所以只要发现了之后可以直接return。另外还有一个估价的设计,如果当前花费加上还没买的所有的去最优情况都超过了ans那么一定无贡献,也可以直接return。

  还有一个优秀的优化,我们会考虑那些尚未取到最优解的情况一个一个买,事实上,我们只需要记一下这个商品以前有没有过只买一次的情况,如果有的话那么以后完全没必要再一次一次买,可以一口气把剩下的全买了,这个剪枝非常有效。

  接着,我试着在进行各种常数优化的时候,我发现我只需要再加一个优化就可以更进一步(极其有效)。那就是邻接矩阵改成邻接表,搜索能有这么优秀已经很不错了啦,不过我的短小简练的程序变长了几倍。

  搜索代码:
  
  1 //It is made by jump~
  2 #include <iostream>
  3 #include <cstdlib>
  4 #include <cstring>
  5 #include <cstdio>
  6 #include <cmath>
  7 #include <algorithm>
  8 #include <ctime>
  9 #include <vector>
 10 #include <queue>
 11 #include <map>
 12 #include <set>
 13 using namespace std;
 14 typedef long long LL;
 15 const int MAXN = 51;
 16 int n,k,total,ecnt;
 17 int next[100000],first[MAXN],to[100000];
 18 double mp[MAXN][MAXN],ans;
 19 int pd[MAXN];
 20 double d[MAXN];
 21 bool ff[MAXN];
 22 struct node{
 23     int num;
 24     double price;
 25 }a[MAXN];
 26 
 27 inline int getint()
 28 {
 29        int w=0,q=0; char c=getchar();
 30        while((c<'0' || c>'9') && c!='-') c=getchar(); if(c=='-') q=1,c=getchar(); 
 31        while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); return q ? -w : w;
 32 }
 33 
 34 inline void dfs(int x,double cost){
 35     if(x>=total+1) {
 36     if(cost<ans) ans=cost;
 37     return ;
 38     }
 39     double gu=0;int dui[51];   int cnt=0;
 40     for(int i=1;i<=n;i++) if(a[i].num!=0) dui[++cnt]=i,gu+=(double)d[i]*a[i].num;
 41     if(cost+gu>=ans) return ;//对其他所有的进行估价,如果取到最优的情况都无法更新就不可能对答案有贡献
 42     double now=1e20;  bool use[51]; memset(use,0,sizeof(use)); 
 43     double pric[51]; double cun[51];
 44 
 45     int zong=0; double zong_ans=0;
 46 
 47     for(int i=1;i<=cnt;i++) {
 48     int u=dui[i];
 49     now=pric[u]=a[u].price;
 50     for(int j=first[u];j;j=next[j]) if(pd[to[j]]!=0) now=min(now,mp[to[j]][u]);
 51     pric[u]=now;
 52     if(now==d[u]) {
 53         use[u]=1; cun[u]=a[u].num; pd[u]+=a[u].num; a[u].num=0;
 54         zong+=cun[u]; zong_ans+=cun[u]*d[u];
 55     }
 56     }
 57     if(zong!=0){//取到最优的先全部都买掉
 58     dfs(x+zong,zong_ans+cost); 
 59     for(int i=1;i<=cnt;i++) {
 60         int u=dui[i];
 61         if(use[u]) {
 62         pd[u]-=cun[u]; a[u].num+=cun[u];
 63         }
 64     }
 65     return ;//可以return,因为最优的肯定要先买掉,不可能留到后面
 66     }
 67 
 68     for(int i=1;i<=cnt;i++) {
 69     int u=dui[i];
 70     if(use[u]) continue;
 71     now=pric[u]; 
 72     if(ff[u]){//之前买过一次了,那么以后都不需要一次一次买,可以一口气买完
 73         cun[u]=a[u].num; pd[u]+=a[u].num; a[u].num=0; 
 74         dfs(x+cun[u],cost+cun[u]*now);
 75         a[u].num+=cun[u]; pd[u]-=a[u].num;
 76     }
 77     else{
 78         a[u].num--; pd[u]++; ff[u]=1;
 79         gu-=d[u]; if(cost+gu+now<ans) dfs(x+1,cost+now);
 80         a[u].num++; pd[u]--; ff[u]=0; gu+=d[u];
 81     }
 82     }    
 83 }
 84 
 85 inline void work(){
 86     n=getint(); for(int i=1;i<=n;i++) scanf("%lf",&a[i].price),a[i].num=getint(),total+=a[i].num,ans+=a[i].price*a[i].num;
 87     int x,y; double z;
 88     k=getint();for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) mp[i][j]=1e20;
 89     for(int i=1;i<=k;i++) { 
 90     x=getint(); y=getint(); scanf("%lf",&z);
 91     next[++ecnt]=first[y]; first[y]=ecnt; to[ecnt]=x;
 92     if(mp[x][y]!=0) mp[x][y]=min(mp[x][y],z);
 93     else mp[x][y]=z;
 94     }
 95     for(int i=1;i<=n;i++) d[i]=a[i].price;
 96     for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(a[j].num!=0) d[i]=min(mp[j][i],d[i]); 
 97     dfs(1,0);
 98     printf("%.2lf",ans);
 99 }
100 
101 int main()
102 {
103   work();
104   return 0;
105 }

 

 
posted @ 2016-09-14 17:24  ljh_2000  阅读(1237)  评论(0编辑  收藏  举报