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 }
View Code

正解:

这题如果数据做的厉害一点肯定也是一个巨坑的题,正解大概和上边的暴力思路相似,只是优化了一点,将与1相连的点分组(分组方法一会再说),一组作为起点,建立超级源点向这一组每个点连边权为0的边,建立超级汇点,从另外一组每个点向超级汇点连边权为0的边,跑最短路即可。如果我们可以保证最终答案的起点与终点分到了两组,求出的就是正确答案。

接下来说nb的分组方法:

将每个节点按照编号转化为二进制数,从第0位开始,编号为0的分一组,编号为1的分另一组,这样也就跑了十边最短路的样子。

但这样能确保最后答案的起点和终点分到两个组吗?显然可以,因为起点和终点的编号不同,二进制为至少有一位不同,也就至少有一次被分到不同组。

然而正解的代码好像并没有人打……

posted @ 2019-07-21 21:08  Al_Ca  阅读(261)  评论(0编辑  收藏  举报
ヾ(≧O≦)〃嗷~