HZOJ 那一天她离我而去
一个数据水到不行的题,各路大佬用各种方法A掉了这个题(比如A*,最短路,dfs……)。
这里只说一下我的暴力和被碾压的正解。
暴力AC系列:
要找过1点的最小环,那么这个环可以拆成两部分,与1相连的两点经过1的距离和不过一的最短路,那么我们就可以将1的入边截断(出边当然也可以截断,这里是为了方便枚举)并记录这些点到1的距离,枚举与1相连的点,对于每个点跑最短路,再次枚举每个点,ans取min即可。
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<queue> 5 #define LL long long 6 #define MAXN 10010 7 #define min(a,b) ((a)<(b)?(a):(b)) 8 #define ma(x) memset(x,0,sizeof(x)) 9 #define MP(a,b) make_pair(a,b) 10 using namespace std; 11 struct edge 12 { 13 int u,v,w,nxt; 14 #define u(x) ed[x].u 15 #define v(x) ed[x].v 16 #define w(x) ed[x].w 17 #define n(x) ed[x].nxt 18 }ed[MAXN*8]; 19 int first[MAXN],num_e; 20 #define f(x) first[x] 21 int T,n,m; 22 const int t=10001; 23 int dis[MAXN]; 24 bool v[MAXN]; 25 void dist(int st) 26 { 27 memset(dis,0x7f,sizeof(dis));ma(v); 28 dis[st]=0; 29 priority_queue<pair<int,int> >q; 30 q.push(MP(0,st)); 31 while(!q.empty()) 32 { 33 int k=q.top().second;q.pop(); 34 if(v[k])continue;v[k]=1; 35 for(int i=f(k);i;i=n(i)) 36 if(dis[v(i)]>dis[k]+w(i)) 37 dis[v(i)]=dis[k]+w(i), 38 q.push(MP(-dis[v(i)],v(i))); 39 } 40 } 41 int tem[MAXN]; 42 inline void add(int u,int v,int w); 43 signed main() 44 { 45 cin>>T; 46 while(T--) 47 { 48 memset(tem,0x7f,sizeof(tem)); 49 ma(first);num_e=0; 50 cin>>n>>m; 51 int u,v,d; 52 for(int i=1;i<=m;i++) 53 { 54 cin>>u>>v>>d; 55 if(u!=1)add(v,u,d); 56 if(v!=1)add(u,v,d); 57 if(u==1)tem[v]=d; 58 if(v==1)tem[u]=d; 59 } 60 LL ans=0x7fffff; 61 for(int i=f(1);i;i=n(i)) 62 { 63 dist(v(i)); 64 for(int j=f(1);j;j=n(j)) 65 if(v(i)!=v(j)) 66 ans=min(ans,dis[v(j)]+tem[v(j)]+tem[v(i)]); 67 } 68 printf("%lld\n",ans==0x7fffff?-1:ans); 69 } 70 } 71 inline void add(int u,int v,int w) 72 { 73 ++num_e; 74 u(num_e)=u; 75 v(num_e)=v; 76 w(num_e)=w; 77 n(num_e)=f(u); 78 f(u)=num_e; 79 }
正解:
这题如果数据做的厉害一点肯定也是一个巨坑的题,正解大概和上边的暴力思路相似,只是优化了一点,将与1相连的点分组(分组方法一会再说),一组作为起点,建立超级源点向这一组每个点连边权为0的边,建立超级汇点,从另外一组每个点向超级汇点连边权为0的边,跑最短路即可。如果我们可以保证最终答案的起点与终点分到了两组,求出的就是正确答案。
接下来说nb的分组方法:
将每个节点按照编号转化为二进制数,从第0位开始,编号为0的分一组,编号为1的分另一组,这样也就跑了十边最短路的样子。
但这样能确保最后答案的起点和终点分到两个组吗?显然可以,因为起点和终点的编号不同,二进制为至少有一位不同,也就至少有一次被分到不同组。
然而正解的代码好像并没有人打……
波澜前,面不惊。