BZOJ_4398_福慧双修&&BZOJ_2407_探险_分治+dij
BZOJ_4398_福慧双修&&BZOJ_2407_探险_分治+dij
Description
菩萨为行,福慧双修,智人得果,不忘其本。
——唐朠立《大慈恩寺三藏法师传》
有才而知进退,福慧双修,这才难得。
——乌雅氏
如何福慧双修?被太后教导的甄嬛徘徊在御花园当中。突然,她发现御花园中的花朵全都是红色和蓝色的。她冥冥之中得到了响应:这就是指导她如何福慧双修的!
现在御花园可以看作是有N块区域,M条小路,两块区域之间可通过小路连接起来。现在甄嬛站在1号区域,而她需要在御花园中绕一绕,且至少经过1个非1号区
域的区域。但是恰好1号区域离碎玉轩最近,因此她最后还是要回到1号区域。由于太后教导她要福慧双修,因此,甄嬛不能走过任何一条她曾经走过的路。但是,
御花园中来往的奴才们太多了,而且奴才们前行的方向也不一样,因此甄嬛在走某条小路的时候,方向不同所花的时间不一定一样。天色快暗了,甄嬛需要尽快知道
至少需要花多少时间才能学会如何福慧双修。如果甄嬛无法达到目的,输出“-1”。
Input
第一行仅2个正整数n,m,意义如题。
接下来m行每行4个正整数s,t,v,w,其中s,t为小路所连接的两个区域的编号,v为甄嬛从s到t所需的时间,w为甄嬛从t到s所需的时间。数据保证无重边。
Output
仅一行,为甄嬛回到1号区域所需的最短时间,若方案不存在,则输出-1
Sample Input
3 3
1 2 2 3
2 3 1 4
3 1 5 2
1 2 2 3
2 3 1 4
3 1 5 2
Sample Output
8
简单地说就是求一个从1开始从1结束的最短环,要求不能经过重复边。
我的做法比较菜,和网上的题解比多个log。
考虑暴力怎么做:假设和1相连的点集为a,枚举一个a中的点x,保留1到x的边,对于点集内其他的点y,保留y到1的边。
然后每次跑一下dij,用点集内其他点的dis[]+val更新答案即可。最终的答案一定对应一个二元组<u,v>,表示1->u....v->1.
显然当数据出现菊花时这个做法效率很低,考虑优化这个暴力。
先把a中的点分成两组P,Q,其中P组保留1到x的边,Q组保留x到1的边,然后再反过来求一遍。这样求出的最短的答案是P中选一个和Q中选一个的最优解。
显然最优解可能是两个都在P或两个都在Q,即对于P和Q中的点u,只枚举了一半的v。
递归下去,对于P,Q点集,各自分成两个集合,然后做上述的事情。
这样递归到最底层时就相当于每个点都枚举了全部的v。
一共logn层,每层放在一起处理mlogm。
总时间复杂度$O(mlogmlogn)$。
另外,实际上最优解在前几层就会找到,如果logn会T的话,就强制让他跑3~4层即可。
假设层数为dep,得到最优解的概率是$1-2^{dep}$。如果出题人卡这个,把答案强行放在最后一层的话,我们可以把a集合random_shuffle一下(笑
更新:亲测递归3层就可以通过本题。
代码(这里是跑满logn的):
#include <cstdio> #include <string.h> #include <algorithm> #include <cstdlib> #include <ext/pb_ds/priority_queue.hpp> using namespace std; using namespace __gnu_pbds; #define N 40050 #define M 200050 __gnu_pbds::priority_queue<pair<int,int> >q; int head[N],to[M],nxt[M],cnt,n,m,xx[M],yy[M],zz[M],ww[M],dis[N],vis[N],a[N],val[M],b[N]; int ans=1<<30; inline void add(int u,int v,int w) { to[++cnt]=v; nxt[cnt]=head[u]; head[u]=cnt; val[cnt]=w; } void dij() { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); dis[1]=0; q.push(make_pair(0,1)); int i; while(!q.empty()) { int x=q.top().second; q.pop(); if(vis[x]) continue; vis[x]=1; for(i=head[x];i;i=nxt[i]) { if(dis[to[i]]>dis[x]+val[i]) { dis[to[i]]=dis[x]+val[i]; q.push(make_pair(-dis[to[i]],to[i])); } } } } void init(bool g) { memset(head,0,sizeof(head)); cnt=0; int i; for(i=1;i<=a[0];i++) { if(b[i]^g) add(1,yy[a[i]],zz[a[i]]); else add(yy[a[i]],1,ww[a[i]]); } for(i=1;i<=m;i++) if(xx[i]!=1) { add(xx[i],yy[i],zz[i]); add(yy[i],xx[i],ww[i]); } } void work2() { int i; int len=a[0]/2; for(;len>=1;len>>=1) { for(i=1;i<=a[0];i++) { if((i/len)&1) b[i]=0; else b[i]=1; } init(0); dij(); for(i=1;i<=a[0];i++) { if(b[i]==0) ans=min(ans,dis[yy[a[i]]]+ww[a[i]]); } init(1); dij(); for(i=1;i<=a[0];i++) { if(b[i]==1) ans=min(ans,dis[yy[a[i]]]+ww[a[i]]); } } if(ans>40000040) ans=-1; printf("%d\n",ans); } int main() { //freopen("both1.in","r",stdin); // freopen("both1.ans","w",stdout); scanf("%d%d",&n,&m); int i; for(i=1;i<=m;i++) { scanf("%d%d%d%d",&xx[i],&yy[i],&zz[i],&ww[i]); if(xx[i]>yy[i]) swap(xx[i],yy[i]),swap(zz[i],ww[i]); if(xx[i]==1) { a[++a[0]]=i; } } if(a[0]<=1) { printf("%d\n",-1); return 0; } //if(a[0]<=30) work1(); // random_shuffle(a+1,a+a[0]+1); work2(); }