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

原文地址:https://www.cnblogs.com/cjjsb/p/10964483.html

时间: 2024-10-12 01:48:45

Luogu P5304 [GXOI/GZOI2019]旅行者的相关文章

[GXOI/GZOI2019]旅行者

就我感觉这道题很神仙吗/kel 仔细想想应该也是一种适用范围挺广的做法. 考虑我们可以通过dijkstra在O(nlogn)求出一个点集到另外一个点集的最短路. 那么我们可以通过一些划分点集的方式使得每一对点都被计算一次. 考虑按照二进制划分. 两个不同的数至少有一个二进制位不同. 按照每一个二进制位01分组,跑logn次dijkstra即可得出答案. #include<bits/stdc++.h> #define N 220000 #define M 1100000 #define eps

题解:[GXOI/GZOI2019]旅行者

调这个题调了两个月,被自己蠢哭 题意: 给一个有向图,一组关键点,求关键点之间的最短的距离 Solution: 这个题目有两种做法,分别是 $nlogn$ 和 $nlog^2n$ 的 首先说 $nlogn$ 的官方做法,我们考虑多源迪杰斯特拉 正图上从 k 个关键点出发跑 $dijkstra$ ,记某个点离最近的关键点距离为 $dis[0][i]$ 反图上也从 k 个关键点出发跑 $dijkstra$ ,距离记为 $dis[1][i]$ 枚举正图中的边 $u->v: w$, 用 $dis[0][

[GXOI/GZOI2019旅行者]

#include <queue> #include <cstdio> #include <cctype> #include <cstring> #include <iostream> #include <algorithm> typedef long long LL; using namespace std; const int maxn = 1e5+6,inf = 1e9; int T,n,m,k; int city[maxn],f

题解-GXOI/GZOI2019 特技飞行

Problem loj3085 bzoj不放题面差评 题意概要:给出两条竖直直线,再给出 \(n\) 架飞机的初始航线:一条接通这两条直线的线段,保证航线交点不在两条直线上.现要求安排所有飞机在航线相交处做特技: 擦身而过:两架飞机按原方向线路继续前进,一次得分 \(b\) 对向交换:两架飞机交换线路继续前进,一次得分 \(a\) 另外,给定 \(k\) 个边界与坐标轴成 \(45°\)角 的正方形,若一次特技被至少一个正方形囊括,则总得分加 \(c\) 现要求决策每次相遇做的特技,求最大/最小

Loj #3085. 「GXOI / GZOI2019」特技飞行

Loj #3085. 「GXOI / GZOI2019」特技飞行 题目描述 公元 \(9012\) 年,Z 市的航空基地计划举行一场特技飞行表演.表演的场地可以看作一个二维平面直角坐标系,其中横坐标代表着水平位置,纵坐标代表着飞行高度. 在最初的计划中,这 \(n\) 架飞机首先会飞行到起点 \(x = x_{st}\) 处,其中第 \(i\) 架飞机在起点处的高度为 \(y_{i,0}\).它们的目标是终点 \(x = x_{ed}\) 处,其中第 \(i\) 架飞机在终点处的高度应为 \(y

[LuoguP5305][GXOI/GZOI2019]旧词 (树链剖分)

[GXOI/GZOI2019]旧词 (树链剖分) 题面 给定一棵 \(n\)个点的有根树,节点标号 \([1,n]\),1号节点为根. 给定常数\(k\) 给定\(Q\)个询问,每次询问给定\(x,y\),求:\(\sum_{i=1}^x \mathrm{deep}(\mathrm{lca}(i,y)) \mod 998244353\) 分析 此题为[BZOJ3626] [LNOI2014]LCA(树链剖分)的加强版. 考虑原来的做法(k=1):我们把i到根的路径上所有点+1,y到根路径上的权值

[GXOI/GZOI2019]宝牌一大堆(dp)

luogu     bzoj 这个麻将题还算挺友善的,比隔壁zjoi的要好得多... 比较正常的做法是五维dp 但事实上六维dp也是完全不会被卡的 七对子选权值最高的七个,国士无双直接$13^2$暴力 $dp[i][j][0/1][k][l][m]$表示枚举到了第i张牌,已经凑了j个面子,有无雀头,第i张牌已经用了k张,第i+1张牌用了l张,第i+2张牌用了m张,直接暴力转移... 然后你会得到50... 当然需要优化. 优化1: 枚举到dp值为0的直接continue,这样的不合法牌型有很多可

题解 P5301 【[GXOI/GZOI2019]宝牌一大堆】

这道题除了非常恶心以外也没有什么非常让人恶心的地方 当然一定要说有的话还是有的,就是这题和咱 ZJOI 的 mahjong 真的是好像的说~ 于是就想说这道题出题人应该被 锕 掉 noteskey 整体的思路就是特判国士无双和七对子,然后 dp 搞普通的胡牌 dp 状态设计和楼上大佬说的一样,就是用一个五维的 \(f[i][j][k][l][p]\) 表示当前处理了前 i 种类型的牌,存在 j 个 面子/杠子 ,以 i-1 开头的顺子要选 k 个,以 i 开头的面子要选 l 个,以及当前是否有

[GXOI/GZOI2019]与或和

/* 显然位之间会互不影响, 然后分位来统计, 显然&只有全1才有贡献, 显然|只有全0才没贡献 分别n^2处理即可 */ #include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<queue> #define ll long long #define M 1010 using namespace std; int read() {