Project Euler 186

Project Euler 186

题目

Connectedness of a network

Here are the records from a busy telephone system with one million users:

Rec Nr Caller Called
\(1\) \(200007\) \(100053\)
\(2\) \(600183\) \(500439\)
\(3\) \(600863\) \(701497\)
\(\dots\) \(\dots\) \(\dots\)

The telephone number of the caller and the called number in record \(n\) are \(\text{Caller}(n) = S_{2n-1}\) and \(\text{Called}(n) = S_{2n}\) where \(S_{1,2,3,\dots}\) come from the “Lagged Fibonacci Generator”:

For \(1 \le k \le 55, S_k = [100003 - 200003k + 300007 k^3] (\text{modulo\ } 1000000)\)
For \(56 \le k, S_k = [S_k-24 + S_k-55] (\text{modulo\ } 1000000)\)

If \(\text{Caller(n)} = \text{Called(n)}\) then the user is assumed to have misdialled and the call fails; otherwise the call is successful.

From the start of the records, we say that any pair of users \(X\) and \(Y\) are friends if \(X\) calls \(Y\) or vice-versa. Similarly, \(X\) is a friend of a friend of \(Z\) if \(X\) is a friend of \(Y\) and \(Y\) is a friend of \(Z\); and so on for longer chains.

The Prime Minister’s phone number is \(524287\). After how many successful calls, not counting misdials, will \(99\%\) of the users (including the PM) be a friend, or a friend of a friend etc., of the Prime Minister?

并查集

并查集:用于高效处理一些不相交集合的数据结构。一般有两个操作:合并两个集合;查询两个元素是否在同一个集合中。

可以通过优化将并查集这两种单次操作优化到\(O(\alpha(n))\)级别,其中\(\alpha(n)\)反阿克曼函数(一个增长速率非常接近于零的函数)。

解决方案

本题为并查集的模板题,直接用并查集数据结构实现。

代码中需要维护每个集合的大小。为了避免错误,只有两个元素不属于同一个集合时才需要合并。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000000,P=524287,A=N/100*99;
const int M=N*5;
int fa[N],sz[N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void merge(int x,int y){
int u=find(x),v=find(y);
if(u!=v){
fa[u]=v;
sz[v]+=sz[u];
}
}
int s[M];
int main(){
int cnt=0,ans=0;
for(int i=0;i<N;i++)
fa[i]=i,sz[i]=1;
for(int i=1;i<=55;i++)
s[i]= (1ll * 300007 * i * i * i + 100003 - 200003 * i) % N;
for(int i=56;i<M;i++)
s[i]= (s[i - 24] + s[i - 55]) % N;
for(int i=2;i<M;i+=2){
if(s[i] == s[i - 1]) ++cnt;
else{
merge(s[i], s[i - 1]);
if(sz[find(P)]>=A){
ans=i/2-cnt;
break;
}
}
}
printf("%d\n",ans);
}