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();
}