Luogu P5304 [GXOI/GZOI2019]旅行者

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

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

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

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

最后\(st\to tar\)的最短路即为答案,看起来十分简单

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

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

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

\[(\frac{1}{4})^{20}\approx 0.0031712\approx\frac{1}{315}\]

足以通过此题,而且最大点在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\)就放在起点一边,反之

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

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

二进制分组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 @ 2019-06-02 21:32 hl666 阅读(...) 评论(...) 编辑 收藏