BZOJ4349 最小树形图
Description
Input
Output
Sample Input
10.00 1
1.80 1
2.50 2
2
1 3 2.00
3 2 1.50
Sample Output
显然,我们肯定是只需要考虑第一次是如何买的,今后的都是按最低价格即可。正解是朱-刘算法,也就是最小树形图裸题。调了我好久,结果又是一个低级错误...
朱-刘算法求得是有向图的最小树形图,有向图的最小树形图类似于最小生成树,只不过因为是有向图,所以定义改成了:最小树形图中存在一个点,可以到达全图的所有点,并且边权和最小。
简单讲一下构造方法,首先构出一张这样的图:新建一个虚拟结点,把每个结点的父亲定为入边权值最小的边的那个点,然后我们把构出来的图的所有边权加起来,显然我们会得到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 }