标签:概率 ++ 旅行 for stat 接下来 一个 n+1 集合
有趣的题目,又好想又好码,可谓是省选题中的一股清流
考虑如果我们枚举一个点作为起点,然后暴力求出到其它点的最短路,那么可以暴力解决问题
但是我们稍微转化一下问题,同时把一些点的集合作为起点,跑出到其它剩下所有点的最短路,取出其中最小的一条,就相当于同时做了多次猜测
具体实现也非常简单,直接建个超级起点\(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;
}
Luogu P5304 [GXOI/GZOI2019]旅行者
标签:概率 ++ 旅行 for stat 接下来 一个 n+1 集合
原文地址:https://www.cnblogs.com/cjjsb/p/10964483.html