传送门:POJ 3013 Big Christmas Tree
描述:
Time Limit: 3000MS | Memory Limit: 131072K | |
Total Submissions: 23338 | Accepted: 5042 |
Description
Christmas is coming to KCM city. Suby the loyal civilian in KCM city is preparing a big neat Christmas tree. The simple structure of the tree is shown in right picture.
The tree can be represented as a collection of numbered nodes and some edges. The nodes are numbered 1 through n. The root is always numbered 1. Every node in the tree has its weight. The weights can be different from each other. Also the shape of every available edge between two nodes is different, so the unit price of each edge is different. Because of a technical difficulty, price of an edge will be (sum of weights of all descendant nodes) × (unit price of the edge).
Suby wants to minimize the cost of whole tree among all possible choices. Also he wants to use all nodes because he wants a large tree. So he decided to ask you for helping solve this task by find the minimum cost.
Input
The input consists of T test cases. The number of test cases T is given in the first line of the input file. Each test case consists of several lines. Two numbers v,e (0 ≤ v, e ≤ 50000) are given in the first line of each test case. On the next line, v positive integers wi indicating the weights of v nodes are given in one line. On the following e lines, each line contain three positive integers a, b, c indicating the edge which is able to connect two nodes a and b, and unit price c.
All numbers in input are less than 216.
Output
For each test case, output an integer indicating the minimum possible cost for the tree in one line. If there is no way to build a Christmas tree, print “No Answer” in one line.
Sample Input
2 2 1 1 1 1 2 15 7 7 200 10 20 30 40 50 60 1 2 1 2 3 3 2 4 2 3 5 4 3 7 2 3 6 3 1 5 9
Sample Output
15 1210
Source
题意:
给n个点从1到n标号,下面一行是每个点的权,另外给出m条边,下面是每条边的信息,两个端点+权值,边是无向边。你的任务是选出一些边,使这个图变成一棵树。这棵树的花费是这样算的,1号固定为树根,树中每个双亲节点下面的边都有个单价(即边权),然后单价乘上这条边的下面所有的子孙后代的点权和(看sample2,只要除掉边 1 5 9 按照这个方法就能算出1210)
思路:
把sample2用式子列一下就能发现,每个点的权都要乘上好几条边的权,是哪几条边呢,就是这个点回到点1的路径上的那些边
所以最后的树的花费可以写成 res = sum{ (点权) * (该点回到点1的路径的边权和) } ,这些点是2到n,1是不用算的
所以决定这条式子大小的,只有 (该点回到点1的路径的边权和) , 只要能让它最小即可。 令到每个点回到点1的路径边权和最小是什么?就是最短路啊!
所以从点1运行一次最短路,就可以知道1到每个点的最短路(也就是每个点到点1的最短路,因为无向图)
但是要注意一个坑,没看清楚题意,超时了很多次,就是点数可能为0(太坑了),点数为0运行不了最短路一直卡在里面,所以特判一下,输出0,如果点数1同样也是0的,因为整个树不会有边
还要注意用long long,写代码的太急了,直接套模板就没改数据类型,WA了好几发_(:зゝ∠)_
另外注意网上的各种说法
1.有人说dij过不了,是能过的
2.有人说dij要手写heap才能过,直接用STL的优先队列也是能过的
3.spfa能过
4.用vector可以建图,建议不要,确实效率不太高,毕竟点很多。下面就给出两个不同建图方式的代码
代码一:
(vector建图)
//G++ 2516ms 4760k #include <iostream> #include <cstdio> #include <cstring> #include<vector> #include<queue> #define ll __int64 using namespace std; /*vector建图 点很多的话效率会比较低 */ const ll inf= 0x3f3f3f3f3f3f3f3fLL; const int maxn=50010; vector<pair<int,int> >E[maxn];//邻接表 ll d[maxn]; bool done[maxn]; int val[maxn]; int v,e; void init(){ for(int i=0; i<maxn; i++)E[i].clear(); for(int i=0; i<maxn; i++)done[i]=false; for(int i=0; i<maxn; i++)d[i]=inf; } ll dijkstra(int s,int t){ priority_queue<pair<ll,int> >q; int cnt=0; ll res=0; d[s]=0; q.push(make_pair(-d[s],s));//优队返回的是最大值,取负就返回的最小值 while(!q.empty()){ int now=q.top().second; q.pop(); if(done[now]) continue; done[now]=true; cnt++; res+=d[now]*val[now]; for(int i=0; i<E[now].size(); i++){ int v=E[now][i].first; if(d[v]>d[now]+E[now][i].second){ d[v]=d[now]+E[now][i].second; q.push(make_pair(-d[v],v)); } } } if(cnt<v) return -1; else return res; //熟悉heap+dij的本质,可以在dij过程中就统计出所有点的最短路和并乘上他们的权 //不需要dij结束后去扫描一次所有点来计算,并且可以在dij过程判断是否能构成一棵树 } int main(){ int T; scanf("%d",&T); while(T--){ init(); scanf("%d%d",&v,&e); for(int i=1; i<=v; i++)scanf("%d",&val[i]); for(int i=1; i<=e; i++){ int x,y,z;scanf("%d %d %d",&x,&y,&z); E[x].push_back(make_pair(y,z)); //边x,y,边权z E[y].push_back(make_pair(x,z)); } if(v == 0 || v == 1){ //注意这个坑,会超时,出现n=0点的时候 printf("0\n"); continue; } ll ans=dijkstra(1, v); if(ans != -1)printf("%I64d\n",ans); else puts("No Answer"); } return 0; }
代码二:
显然这种不用vector建图效率更高
//G++ 594ms 3076k #include <iostream> #include <cstdio> #include <queue> #include <algorithm> #include <cstring> #define ll __int64 using namespace std; /*结构体建图 *tol记得初始化 */ const int N = 50010; const ll inf= 0x3f3f3f3f3f3f3f3fLL; int n,m,tol; ll d[N]; int head[N]; bool done[N]; int val[N]; struct edge{ int u,v,w,next; }e[2*N]; typedef pair<ll,int>P; priority_queue<P, vector<P>, greater<P> > q;//取最小值 void add(int u,int v,int w){ e[tol].u=u; e[tol].v=v; e[tol].w=w; e[tol].next=head[u]; head[u]=tol++; u=u^v; v=u^v; u=u^v;//异或 e[tol].u=u; e[tol].v=v; e[tol].w=w; e[tol].next=head[u]; head[u]=tol++; } ll Dij(){ int cnt=0; ll res=0; d[1]=0; q.push(make_pair(d[1], 1)); while(!q.empty()){ int u,v,w; P x=q.top(); q.pop(); u=x.second; if(done[u]) continue; done[u]=true; cnt++; res+=d[u]*val[u]; for(int i=head[u]; i!=-1; i=e[i].next){ v=e[i].v; w=e[i].w; if(d[u]+w<d[v]){ d[v]=d[u]+w; q.push(make_pair(d[v], v)); } } } if(cnt<n) return -1; else return res; } int main(){ int T; scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); for(int i=1; i<=n; i++) scanf("%d",&val[i]); while(!q.empty()) q.pop(); tol=0; for(int i=1; i<=n; i++){ head[i]=-1; d[i]=inf; done[i]=false; } for(int i=1; i<=m; i++){ int u,v,w; scanf("%d%d%d",&u,&v,&w); add(u,v,w); } if(n == 0 || n == 1){ //注意这个坑,会超时,出现n=0点的时候 printf("0\n"); continue; } ll ans=Dij(); if(ans != -1) printf("%I64d\n",ans); else printf("No Answer\n"); } return 0; }