题目大意:
n个人最初都在1顶点,分别派去有向图中的其余n个顶点,然后所有人再回到出发点。要求所有人的最小费用。 n,m<1000000
分析:
可以看出,其实就是要求单源最短路径。分开两次求,把所有向边正向的图算一次,然后把边全部反过来,反向算一次即可。可以使用Dijkstra算法。由于数据量超大,所以只能用邻接表来存。
这里可以对普通Dijkstra进行改进。在每次确定当前顶点中的最近顶点时,我们使用堆,而不是顺序扫描。这样会快很多。
下面贴一下代码,在ZJU上用了1.41s
/*
ZJU2008 Invitation Cards
*/
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#define N 1000005
#define size sizeof(node)
#define newNode (node*)malloc(size)
#define clr(c) memset(c,0,sizeof(c))
struct linknode{
int x,p;
struct linknode *next;
};
typedef struct linknode node;
node *a[N],*b[N];
int m,n;
char e[N],u[N];
int v[N];
int d[N],dn;
void ins(node **a,int x,int p)
{
node *t,*s;
if(*a==0){
*a=newNode;
(*a)->x=x;
(*a)->p=p;
return;
}
s=newNode;
s->x=x;
s->p=p;
t=(*a)->next;
(*a)->next=s;
s->next=t;
}
void heap(int p)
{
int q,x=d[p];
while(p<=dn/2)
{
q=p+p;
if(q<dn&&v[d[q+1]]<v[d[q]]) q++;
if(v[x]<=v[d[q]]) break;
d[p]=d[q];
p=q;
}
d[p]=x;
return;
}
void reheap(int p)
{
int q,x=d[p];
while(p>1)
{
q=p/2;
if(v[x]>=v[d[q]]) break;
d[p]=d[q];
p=q;
}
d[p]=x;
return;
}
int dij(node *a[])
{
int i,j,k,x,sum=0;
node *p,*q;
d[1]=1; u[1]=1;
dn=1;
for(i=0;i<n;i++)
{
//get the top
x=d[1];
e[x]=1;
sum+=v[x];
d[1]=d[dn--];
heap(1);
//printf("get %d: %d/n",x,v[x]);
//add new
p=a[x];
while(p!=0){
k=p->x;
if(!e[k]){
if(v[k]>v[x]+p->p||!u[k]){
v[k]=v[x]+p->p;
}
if(!u[k])
{
dn++;
//printf("add %d : %d/n",k,dn);
d[dn]=k;
u[k]=1;
reheap(dn);
}
}
p=p->next;
}
}
return sum;
}
int main()
{
int i,j,k,T;
scanf("%d",&T);
while(T--)
{
int f,t,p,ans;
//init
clr(a); clr(b);
//input
scanf("%d%d",&n,&m);
for(i=0;i<m;i++){
scanf("%d%d%d",&f,&t,&p);
ins(&a[f],t,p);
ins(&b[t],f,p);
}
//dij
clr(e); clr(u);
clr(v); clr(d);
ans=dij(a);
clr(e); clr(u);
clr(v); clr(d);
ans+=dij(b);
//output
printf("%d/n",ans);
}
return 0;
}
/*
Sample Input
2
2 2
1 2 13
2 1 33
4 6
1 2 10
2 1 60
1 3 20
3 4 10
2 4 5
4 1 50
Sample Output
46
210
*/