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

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

 

转载于:https://www.cnblogs.com/suika/p/9107778.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值