照例先扯淡
不知道大家有没有做过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
#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 Q;
struct Node {
int p,Dis;
} Now,N;
bool operator <(Node x,Node y) {
return x.Dis>y.Dis;
}
priority_queue PQ;
template
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]
对于第二部分,相信大家一定都会写Dijkstra+堆优化,那么你就可以轻易得到50分了。
那剩下的50分呢?
其实仔细观察题目我们就会发现,从同一个点出发的边权值都相同,那么我们在把点压进优先队列时可以直接加上当前点的点权(即从当前点出发的边的权值),由于优先队列的性质,每次从优先队列中取出的点,其状态一定是最优解,而且其拓展出的状态也是最优解。证明如下:
假设点$x$已经得到了最短路,证明用该点更新的$y$也得到了最短路
反证,假设存在路径$x$′→$y$
使$Dist[y]$ 更小,且在$x$更新$y$之后,那么有$Dist[x$′$]+w[x] 其实和飞飞侠的证明是一样的(所以我就直接搬运了2333)。 这样的话,每个点第一次被访问到就一定是最优解,所以只要找到需要的点就可以直接退出了。 代码如下:#include