• P5304 [GXOIGZOI2019]旅行者


    题目描述

    题目链接

    前置知识

    二进制拆分

    要枚举同一个集合里的两个不同的元素,我们可以将每个元素设一个编号,然后枚举二进制位(或者直接枚举元素),当前位数是 (1) 的放入一个集合,是 (0) 的放入另一个集合,设要枚举的元素为 (x)(y) ,因为 (x ot=y) ,所以它们的二进制肯定有至少一位不同,所以肯定会被枚举到。

    超级源与超级汇

    这是原来是在网络流里的一些东西,但现在好像成了图论里烂大街的东西。

    超级源与超级汇在普通图论中被用来求像一些多起点最短路的问题,其实想一下还是比较简单的,就是将这些点看做一个点(向超级源或超级汇连权值为 (0) 的边), 然后直接用普通的单源最短路算法就可以求解。

    其实在实际应用中,超级源和超级汇主要是一种思想,很多情况下不必把图建出来,直接在初始化单源最短路的时候就可以操作,例如这道题。

    思路

    二进制拆分

    有了上面的前置知识,这道题目就比较简单了,先把 (k) 个点二进制拆分一下,然后每次把两个集合各自连一个超级源,跑两遍堆优化的 (Dijkstra) ,取个 (min) 就可以了,因为是有向图 ,(x->y)(y->x) 的答案不一样,所以要跑两边。

    其实超级源不用建出来,直接在初始化的时候将要连接到超级源上的点的 (dis) 赋值为 (0) ,压入优先队列就可以了。

    时间复杂度 (Theta(T*n*log_2n*log_2k)) ,需要吸氧。

    随机化

    我们发现 (n^2) 枚举哪两个点肯定会超时,我们可以和上面的二进制拆分一样,每次随机化将 (k) 个点分为两个集合,然后的操作是一样的。随机个 (20) 次答案正确率就很高了。随机化的好处是容错率高,开始的时候对于两个集合,只是跑了一个集合到另一个集合的,没有反过来,竟然有 (90) 分,还有几次 (100) 分,如果二进制拆分忘了这样的话, (30) 来分走人。

    缺点是随机化是要看脸的,虽然概率很低,但万一就过不了呢。

    代码

    Code 1 二进制拆分

    #include<bits/stdc++.h>
    #include<queue>
    #define one first
    #define two second
    #define mp make_pair
    #define pii pair<ll,int>
    #define ll long long
    using std::mp;
    using std::min;
    using std::pair;
    using std::vector;
    using std::greater;
    using std::priority_queue;
    const int N=1e5+100,M=5e5+100;
    const ll INF=LLONG_MAX-INT_MAX;
    struct edge{
    	int s,e;
    	ll v;
    	int net;
    }ed[M];
    priority_queue<pii,vector<pii>,greater<pii> >q;
    int T,n,m,tot,k;
    ll minn;
    int head[N],point[N];
    bool mark[N];
    ll dis[N];
    inline void clear()
    {
    	tot=0;
    	memset(head,0,sizeof(head));
    	return ;
    }
    inline void add(int s,int e,ll v)
    {
    	ed[++tot]=(edge){s,e,v,head[s]};
    	head[s]=tot;
    	return ;
    }
    inline void Dijkstra()
    {
    	while (!q.empty())
    	{
    		pii now=q.top();
    		q.pop();
    		if (now.one!=dis[now.two]) continue;
    		for (int i=head[now.two];i;i=ed[i].net)
    		if (dis[ed[i].e]>dis[ed[i].s]+ed[i].v)
    		{
    			dis[ed[i].e]=dis[ed[i].s]+ed[i].v;
    			q.push(mp(dis[ed[i].e],ed[i].e));
    		}
    	}
    	return ;
    }
    inline void check(bool x)
    {
    	for (int j=0;(1<<j)<=k;j++)
    	{
    		for (int i=1;i<=n;i++) dis[i]=INF;
    		for (int i=1;i<=k;i++)
    		{
    			bool now=i&(1<<j);
    			if (now==x)
    			{
    				int to=point[i];
    				dis[to]=0;
    				q.push(mp(dis[to],to));
    				mark[i]=1;
    			}
    		}
    		Dijkstra();
    		for (int i=1;i<=k;i++)
    		{
    			if (!mark[i]) minn=min(minn,dis[point[i]]);
    			mark[i]=0;
    		}
    	}
    	return ;
    }
    inline void Solve()
    {
    	clear();
    	minn=INF;
    	scanf("%d%d%d",&n,&m,&k);
    	for (int i=1;i<=m;i++)
    	{
    		int s,e;ll v;
    		scanf("%d%d%lld",&s,&e,&v);
    		add(s,e,v);
    	}
    	for (int i=1;i<=k;i++)
    	scanf("%d",point+i);
    	check(1);check(0);
    	printf("%lld
    ",minn);
    	return ;
    }
    int main()
    {
    	scanf("%d",&T);
    	while (T--) Solve();
    	return 0;
    }
    

    Code 2 随机化

    #include<bits/stdc++.h>
    #include<queue>
    #define one first
    #define two second
    #define mp make_pair
    #define pii pair<ll,int>
    #define ll long long
    using std::mp;
    using std::min;
    using std::pair;
    using std::vector;
    using std::greater;
    using std::priority_queue;
    const int N=1e5+100,M=5e5+100;
    const ll INF=LLONG_MAX-INT_MAX;
    struct ios {
        inline char gc(){
            static const int IN_LEN=1<<18|1;
            static char buf[IN_LEN],*s,*t;
            return (s==t)&&(t=(s=buf)+fread(buf,1,IN_LEN,stdin)),s==t?-1:*s++;
        }
    
        template <typename _Tp> inline ios & operator >> (_Tp&x){
            static char ch,sgn; ch = gc(), sgn = 0;
            for(;!isdigit(ch);ch=gc()){if(ch==-1)return *this;sgn|=ch=='-';}
            for(x=0;isdigit(ch);ch=gc())x=x*10+(ch^'0');
            sgn&&(x=-x); return *this;
        }
    } cin;
    struct edge{
    	int s,e;
    	ll v;
    	int net;
    }ed[M<<1],id[M];
    priority_queue<pii,vector<pii>,greater<pii> >q;
    ll minn;
    int T,n,m,tot,k;
    int head[N],point[N],mark[N],rk[N];
    ll dis[N];
    inline void clear()
    {
    	tot=0;
    	for (int i=0;i<=n;i++)
    	{
    		mark[i]=0;
    		head[i]=0;
    		dis[i]=INF;
    	}
    	return ;
    }
    inline void add(int s,int e,ll v)
    {
    	ed[++tot]=(edge){s,e,v,head[s]};
    	head[s]=tot;
    	return ;
    }
    inline void Dijkstra(int x)
    {
    	while (!q.empty()) q.pop();
    	dis[x]=0;
    	q.push(mp(dis[x],x));
    	while (!q.empty())
    	{
    		pii now=q.top();
    		q.pop();
    		if (now.one!=dis[now.two]) continue;
    		for (int i=head[now.two];i;i=ed[i].net)
    		if (dis[ed[i].e]>dis[ed[i].s]+ed[i].v)
    		{
    			dis[ed[i].e]=dis[ed[i].s]+ed[i].v;
    			q.push(mp(dis[ed[i].e],ed[i].e));
    		}
    	}
    	return ;
    }
    inline void check(bool x)
    {
    	clear();
    	for (int i=1;i<=m;i++)
    	add(id[i].s,id[i].e,id[i].v);
    	int sz=0;
    	for (int i=1;i<=k;i++)
    	if (rk[i]%2==x)
    	{
    		add(0,point[i],0);
    		mark[i]=1;
    		sz++;
    	}
    	if (sz==0) return ;
    	Dijkstra(0);
    	for (int i=1;i<=k;i++)
    	if (!mark[i]) minn=min(minn,dis[point[i]]);
    	return ;
    }
    inline void Solve()
    {
    	minn=INF;
    	cin>>n>>m>>k;
    	for (int i=1;i<=m;i++)
    	{
    		int s,e;ll v;
    		cin>>s>>e>>v;
    		id[i]=(edge){s,e,v,0};
    	}
    	for (int i=1;i<=k;i++)
    	cin>>point[i];
    	int num=20;
    	while (num--)
    	{
    		for (int i=1;i<=k;i++)
    		rk[i]=rand();
    		check(1);
    		check(0);
    	}
    	printf("%lld
    ",minn);
    	return ;
    }
    int main()
    {
    	srand(time(NULL));
    	scanf("%d",&T);
    	while (T--) Solve();
    	return 0;
    }
    
  • 相关阅读:
    安全攻防技能——安全基础概念
    解决linux下中文文件名显示乱码问题
    yaml封装
    IIS挂载网站一键更新备份
    MySQL 聚集索引和二级索引
    redolog落盘机制
    MySQL中Redo Log相关的重要参数总结
    mysql之innodb_buffer_pool
    xshell ssh 登录慢
    记录pg
  • 原文地址:https://www.cnblogs.com/last-diary/p/11735334.html
Copyright © 2020-2023  润新知