题解 P5037 【抓捕】

照例先扯淡

不知道大家有没有做过P4473 [国家集训队]飞飞侠呢?如果没做过可以去看一下,那题的这道题就是用了飞飞侠的优化思想。有一次考试,我们考到了飞飞侠这道题,然后标程是分层图,由于我太菜了,没听懂就上网搜讲解,结果发现了一个神仙算法:Dijkstra+并查集优化(后来zcysky大佬也在洛谷上发了相关的题解,我也很不要脸的发了一篇,此处就不讲了),在看懂之后我就把那题的优化思路运用到了这里QAQ。

扯完了,下面开始讲题目。

先把题意简化一下:给你编号为$1$到$n$的$n$个点,两个编号互质的点可以连边,从相同的点出发的边权值相同,求给定两点之间最短路(其实除了$1$号点,其余每个点都是相互连通的,按照惯例输出$-1$的数据是不存在的)。

题目大致分成$2$部分:1.枚举$n$以内的互质点对;2.求最短路

对于第一部分,我本来是想卡一下暴力枚举,只让构造法过的,但是由于$n$太小,所以构造甚至跑的比暴力枚举还慢QAQ(难道是我写的太垃圾了???),所以......最后并没有卡。

但是还是讲一下构造法的思路吧(大家看看就好):

1.首先预处理$1$~$n$中所有的质数

2.枚举$i=1$~$n$

3.对于每一个$i$,分解质因数,求出它使用了那些质数

4.从$i$没有使用过的质数中选取一些进行组合并使其小于$i$

代码如下(最短路部分没有优化):

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f
using namespace std;
const int maxn = 105;
ll x[50010],Ans1,Su[10010],Fa[10010],Cnt,Ans,n,X,Dist[10010],w[10010];
bool Pd[10010],Used[10010],From[10010][10010];
int T,u,v,Head[10010],To[61000010],Next[61000010];
queue<ll> Q;
struct Node {
    int p,Dis;
} Now,N;
bool operator <(Node x,Node y) {
    return x.Dis>y.Dis;
}
priority_queue<Node> PQ;
template <typename T>
inline int Read(T &x) {
    x=0;
    int f=1;
    char c=getchar();
    while(c!='-'&&c>'9'&&c<'0')
        c=getchar();
    for(; !isdigit(c); c=getchar())
        if(c=='-')
            f=-f;
    for(; isdigit(c); c=getchar())
        x=x*10+c-'0';
    x*=f;
    if(c=='\n')
        return 1;
    else
        return 0;
}
inline void Write(long long x) {
    if(x<0) {
        putchar('-');
        x=-x;
    }
    if(x>9) {
        Write((x-x%10)/10);
    }
    putchar(x%10+'0');
}
inline ll Multi(ll a, ll b, ll p) {
    ll Ans1 = 0;
    while(b) {
        if(b & 1LL)	Ans1 = (Ans1+a)%p;
        a = (a+a)%p;
        b >>= 1;
    }
    return Ans1;
}
inline ll QPow(ll a, ll b, ll p) {
    ll Ans1 = 1;
    while(b) {
        if(b & 1LL)	Ans1 = Multi(Ans1, a, p);
        a = Multi(a, a, p);
        b >>= 1;
    }
    return Ans1;
}
inline bool MR(ll n) {
    if(n == 2)	return true;
    int s = 20, i, t = 0;
    ll u = n-1;
    while(!(u&1)) {
        t++;
        u >>= 1;
    }
    while(s--) {
        ll a = rand()%(n-2)+2;
        x[0] = QPow(a, u, n);
        for(i = 1; i <= t; i++) {
            x[i] = Multi(x[i-1], x[i-1], n);
            if(x[i] == 1 && x[i-1] != 1 && x[i-1] != n-1)	return false;
        }
        if(x[t] != 1)	return false;
    }
    return true;
}
inline ll Gcd(ll a, ll b) {
    if(b == 0)
        return a;
    else
        return Gcd(b, a%b);
}
inline ll Pollard_Rho(ll n, int c) {
    ll i = 1, k = 2, x = rand()%(n-1)+1, y = x;
    while(1) {
        i++;
        x = (Multi(x, x, n)+c)%n;
        ll p = Gcd((y-x+n)%n, n);
        if(p != 1 && p != n)	return p;
        if(y == x)	return n;
        if(i == k) {
            y = x;
            k <<= 1;
        }
    }
}
inline void Find(ll x, int c) {
    if(x == 1)	return;
    if(MR(x)) {
        Pd[x]=1;
        return;
    }
    ll p = x, k = c;
    while(p >= x) {
        p = Pollard_Rho(p, c--);
    }
    Find(p, k);
    Find(x/p, k);
}
inline void Add(int a,int b) {
    To[++Cnt]=a;
    Next[Cnt]=Head[b];
    Head[b]=Cnt;
    To[++Cnt]=b;
    Next[Cnt]=Head[a];
    Head[a]=Cnt;
}
void Dijkstra(int x) {
    Dist[x]=0;
    Now.Dis=0;
    Now.p=x;
    PQ.push(Now);
    while(!PQ.empty()) {
        Now=PQ.top();
        From[Now.p][0]=0;
        PQ.pop();
        if(!Pd[Now.p]) {
            Pd[Now.p]=1;
            for(int i=Head[Now.p]; i; i=Next[i]) {
                if(!Pd[To[i]]&&(!From[Now.p][Fa[To[i]]])&&(Dist[To[i]]>Dist[Now.p]+w[Now.p])) {
                    Fa[To[i]]=Now.p;
                    Dist[To[i]]=Dist[Now.p]+w[Now.p];
                    From[To[i]][Now.p]=1;
                    N.p=To[i];
                    N.Dis=Dist[Now.p]+w[Now.p];
                    PQ.push(N);
                }
            }
        }
    }
}
int main() {
    Read(T);
    Read(n);
    Cnt=0;
    for(int i=2; i<=5000; i++) {
        if(MR(i)) {
            Su[++Cnt]=i;
        }
    }
    Cnt=0;
    for(int i=1; i<=n; i++) {
        memset(Pd,0,sizeof(Pd));
        memset(Used,0,sizeof(Used));
        Find(i,57);
        for(int j=1; Su[j]<i; j++) {
            if(!Pd[Su[j]]) {
                Q.push(Su[j]);
                Ans++;
            }
        }
        while(!Q.empty()) {
            X=Q.front();
            Add(X,i);
            Q.pop();
            for(int j=1; Su[j]<=i; j++) {
                if(X*Su[j]<i&&(!Pd[Su[j]])&&(!Used[X*Su[j]])) {
                    Q.push(X*Su[j]);
                    Used[X*Su[j]]=1;
                    Ans++;
                }
            }
        }
    }
    while(T--) {
        fill(Dist,Dist+n+5,INF);
        memset(Fa,0,sizeof(Fa));
        memset(From,0,sizeof(From));
        memset(Pd,0,sizeof(Pd));
        Read(u);
        Read(v);
        for(int i=1; i<=n; i++) {
            Read(w[i]);
        }
        Dijkstra(u);
        Write(Dist[v]);
        putchar('\n');
    }
    return 0;
}

对于第二部分,相信大家一定都会写Dijkstra+堆优化,那么你就可以轻易得到50分了。

那剩下的50分呢?

其实仔细观察题目我们就会发现,从同一个点出发的边权值都相同,那么我们在把点压进优先队列时可以直接加上当前点的点权(即从当前点出发的边的权值),由于优先队列的性质,每次从优先队列中取出的点,其状态一定是最优解,而且其拓展出的状态也是最优解。证明如下:

假设点$x$已经得到了最短路,证明用该点更新的$y$也得到了最短路

反证,假设存在路径$x$′→$y$

使$Dist[y]$ 更小,且在$x$更新$y$之后,那么有$Dist[x$′$]+w[x]<Dist[x]+w[x]$,因为$x$′在$x$之后,有$Dist[x$′$]+w[x$′$]≥Dist[x]+w[x]$,两式矛盾,运用数学归纳法,可知上述结论成立,以及起点$s$到每一点的最短路径就是$Dist[i]$

其实和飞飞侠的证明是一样的(所以我就直接搬运了2333)。

这样的话,每个点第一次被访问到就一定是最优解,所以只要找到需要的点就可以直接退出了。

代码如下:

#include<bits/stdc++.h>
#define ll long long
#define INF 0x7f7f7f7f
using namespace std;
const int maxn = 105;
ll x[50010],Ans1,Su[5010],Cnt,Ans,n,X,Dist[5010],w[5010],Da;
bool Pd[5010],Used[5010];
int T,u,v,Head[5010],To[15000010],Next[15000010];
queue<ll> Q;
struct Node {
    int p,Dis;
} Now,N;
bool operator <(Node x,Node y) {
    return x.Dis>y.Dis;
}
priority_queue<Node> PQ;
template <typename T>
inline int Read(T &x) {
    x=0;
    int f=1;
    char c=getchar();
    while(c!='-'&&c>'9'&&c<'0')
        c=getchar();
    for(; !isdigit(c); c=getchar())
        if(c=='-')
            f=-f;
    for(; isdigit(c); c=getchar())
        x=x*10+c-'0';
    x*=f;
    if(c=='\n')
        return 1;
    else
        return 0;
}
inline void Write(long long x) {
    if(x<0) {
        putchar('-');
        x=-x;
    }
    if(x>9) {
        Write((x-x%10)/10);
    }
    putchar(x%10+'0');
}
//这回是正常的快速输入输出
inline ll Gcd(ll a, ll b) {
    if(b == 0)
        return a;
    else
        return Gcd(b, a%b);
}
inline void Add(int a,int b) {
    To[++Cnt]=a;
    Next[Cnt]=Head[b];
    Head[b]=Cnt;
    To[++Cnt]=b;
    Next[Cnt]=Head[a];
    Head[a]=Cnt;
}
void Dijkstra(int x) {
    Dist[x]=w[x];
    Now.Dis=w[x];
    Now.p=x;
    PQ.push(Now);
    while(!PQ.empty()) {
        Now=PQ.top();
        PQ.pop();
        if(!Pd[Now.p]) {
            Pd[Now.p]=1;
            for(int i=Head[Now.p]; i; i=Next[i]) {
                if(!Pd[To[i]]&&(Dist[To[i]]>Now.Dis)) {
                    if(To[i]==v){
                    	Da=Now.Dis;
                        return;
                    }
                    Dist[To[i]]=Now.Dis;
                    N.p=To[i];
                    N.Dis=Dist[To[i]]+w[To[i]];
                    PQ.push(N);
                }
            }
        }
    }
}
int main() {
    Read(T);
    Read(n);
    Cnt=0;
    for(int i=2;i<=n;i++){
    	for(int j=i+1;j<=n;j++){
    		if(Gcd(i,j)==1){
    			Add(i,j);
            }
        }
    }
    while(T--) {
        fill(Dist,Dist+n+5,INF);
        memset(Pd,0,sizeof(Pd));
        Read(u);
        Read(v);
        for(int i=1; i<=n; i++) {
            Read(w[i]);
        }
        Dijkstra(u);
        Write(Da);
        putchar('\n');
        while(!PQ.empty()){
        	PQ.pop();
        }
    }

    return 0;
}
posted @ 2019-09-07 21:05  泉眼无声惜细流  阅读(138)  评论(0编辑  收藏  举报