Luogu P5304 [GXOI/GZOI2019]旅行者

有趣的题目,又好想又好码,可谓是省选题中的一股清流

考虑如果我们枚举一个点作为起点,然后暴力求出到其它点的最短路,那么可以暴力解决问题

但是我们稍微转化一下问题,同时把一些点的集合作为起点,跑出到其它剩下所有点的最短路,取出其中最小的一条,就相当于同时做了多次猜测

具体实现也非常简单,直接建个超级起点st和终点tar,如果这个关键点x位于起点集合那么连一条stx,val=0的边,在终点集合就连一条xtar,val=0的边

最后sttar的最短路即为答案,看起来十分简单

但是我们细细一想发现这个方法好像很优秀,如果最后答案是xy,那么如果x被分到起点集合里而y被分到终点集合里那么这个算法就直接跑出了答案!

根据上面的分析我们很容易得到一个随机化的做法,我们每次给所有关键点随机分配到两边然后跑最短路,这样正确率就是14

由于跑一次的复杂度是O(nlogn)(用DJ),因此5s的时限我们稳妥的跑20次左右,这样错误的概率就是:

(14)200.00317121315

足以通过此题,而且最大点在Luogu上仅用时2100+ms,因此跑更大的30,40次都不成问题(如果比赛的时候还是求稳为主,后面全T了就很尴尬)

随机化CODE

#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<queue>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=1e5+5,M=5e6+5;
const long long INF=1e18;
struct edge
{
    int to,nxt,v;
}e[N+M]; int n,t,thead[N],head[N],a[N],cnt,tcnt,m,k,x,y,z,st,tar; long long dis[N];
class FileInputOutput
{
    private:
        static const int S=1<<21;
        #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
        char Fin[S],*A,*B;
    public:
        Tp inline void read(T& x)
        {
            x=0; char ch; while (!isdigit(ch=tc()));
            while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
        }
        #undef tc
}F;
class SSSP
{
    private:
        struct data
        {
            int id; long long val;
            friend inline bool operator < (const data& A,const data& B)
            {
                return A.val>B.val;
            }
        }; priority_queue <data> hp; bool vis[N];
    public:
        #define to e[i].to
        inline void Dijkstra(void)
        {
            RI i; for (i=st;i<=tar;++i) dis[i]=INF,vis[i]=0; dis[st]=0;
            hp.push((data){st,0}); while (!hp.empty())
            {
                int now=hp.top().id; hp.pop(); if (vis[now]) continue;
                vis[now]=1; for (i=head[now];i;i=e[i].nxt)
                if (dis[to]>dis[now]+e[i].v) hp.push((data){to,dis[to]=dis[now]+e[i].v});
            }
        }
        #undef to
}G;
inline void taddedge(CI x,CI y,CI z)
{
    e[++tcnt]=(edge){y,thead[x],z}; thead[x]=tcnt;
}
inline void addedge(CI x,CI y,CI z)
{
    e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
}
inline long long solve(long long ret=INF)
{
    RI i,j; for (F.read(n),F.read(m),F.read(k),i=1;i<=m;++i)
    F.read(x),F.read(y),F.read(z),taddedge(x,y,z);
    for (tar=n+1,i=1;i<=k;++i) F.read(a[i]);
    for (i=1;i<=20;++i)
    {
        for (cnt=tcnt,j=st;j<=tar;++j) head[j]=thead[j];
        for (j=1;j<=k;++j) if (rand()&1) addedge(st,a[j],0);
        else addedge(a[j],tar,0); if (G.Dijkstra(),dis[tar]<ret) ret=dis[tar];
    }
    return ret;
}
inline void clear(void)
{
    for (RI i=st;i<=tar;++i) thead[i]=0; tcnt=0;
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    for (srand(20030909),F.read(t);t;--t) printf("%lld\n",solve()),clear(); return 0;
}

好吧接下来讲一下正确做法,大致建模和上面一样,就是分配点集的时候稍作修改,枚举n范围内的每一个二进制位,如果这个点编号这一位上为1就放在起点一边,反之

正确性证明也很好理解,假设答案为xy,那么xy,因此x,y必然有至少一位在二进制下不同,故至少有一次被分在了两侧,结论成立

这样就可以以O(nlog2n)的复杂度通过此题了,注意答案为有向点对,因此要枚举两边

二进制分组CODE

#include<cstdio>
#include<cctype>
#include<queue>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=1e5+5,M=5e6+5;
const long long INF=1e18;
struct edge
{
    int to,nxt,v;
}e[N+M]; int n,t,thead[N],head[N],a[N],cnt,tcnt,m,k,x,y,z,st,tar; long long dis[N];
class FileInputOutput
{
    private:
        static const int S=1<<21;
        #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
        char Fin[S],*A,*B;
    public:
        Tp inline void read(T& x)
        {
            x=0; char ch; while (!isdigit(ch=tc()));
            while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
        }
        #undef tc
}F;
class SSSP
{
    private:
        struct data
        {
            int id; long long val;
            friend inline bool operator < (const data& A,const data& B)
            {
                return A.val>B.val;
            }
        }; priority_queue <data> hp; bool vis[N];
    public:
        #define to e[i].to
        inline void Dijkstra(void)
        {
            RI i; for (i=st;i<=tar;++i) dis[i]=INF,vis[i]=0; dis[st]=0;
            hp.push((data){st,0}); while (!hp.empty())
            {
                int now=hp.top().id; hp.pop(); if (vis[now]) continue;
                vis[now]=1; for (i=head[now];i;i=e[i].nxt)
                if (dis[to]>dis[now]+e[i].v) hp.push((data){to,dis[to]=dis[now]+e[i].v});
            }
        }
        #undef to
}G;
inline void taddedge(CI x,CI y,CI z)
{
    e[++tcnt]=(edge){y,thead[x],z}; thead[x]=tcnt;
}
inline void addedge(CI x,CI y,CI z)
{
    e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
}
inline long long solve(long long ret=INF)
{
    RI i,j; for (F.read(n),F.read(m),F.read(k),i=1;i<=m;++i)
    F.read(x),F.read(y),F.read(z),taddedge(x,y,z);
    for (tar=n+1,i=1;i<=k;++i) F.read(a[i]);
    for (i=1;i<=(n<<1);i<<=1)
    {
        for (cnt=tcnt,j=st;j<=tar;++j) head[j]=thead[j];
        for (j=1;j<=k;++j) if (a[j]&i) addedge(st,a[j],0);
        else addedge(a[j],tar,0); if (G.Dijkstra(),dis[tar]<ret) ret=dis[tar];
    }
    for (i=1;i<=(n<<1);i<<=1)
    {
        for (cnt=tcnt,j=st;j<=tar;++j) head[j]=thead[j];
        for (j=1;j<=k;++j) if (a[j]&i) addedge(a[j],tar,0);
        else addedge(st,a[j],0); if (G.Dijkstra(),dis[tar]<ret) ret=dis[tar];
    }
    return ret;
}
inline void clear(void)
{
    for (RI i=st;i<=tar;++i) thead[i]=0; tcnt=0;
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    for (F.read(t);t;--t) printf("%lld\n",solve()),clear(); return 0;
}
posted @   空気力学の詩  阅读(121)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示