1. 程式人生 > >HDU 5514.Frogs-尤拉函式 or 容斥原理

HDU 5514.Frogs-尤拉函式 or 容斥原理

Frogs

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 4904    Accepted Submission(s): 1631


Problem Description There are  m stones lying on a circle, and n frogs are jumping over them.
The stones are numbered from 0
 to m1 and the frogs are numbered from 1 to n. The i-th frog can jump over exactly ai stones in a single step, which means from stone j mod m to stone (j+ai) mod m (since all stones lie on a circle).

All frogs start their jump at stone 0
, then each of them can jump as many steps as he wants. A frog will occupy a stone when he reach it, and he will keep jumping to occupy as much stones as possible. A stone is still considered ``occupied" after a frog jumped away.
They would like to know which stones can be occupied by at least one of them. Since there may be too many stones, the frogs only want to know the sum of those stones' identifiers.
 

 

Input There are multiple test cases (no more than  20), and the first line contains an integer t,
meaning the total number of test cases.

For each test case, the first line contains two positive integer n and m - the number of frogs and stones respectively (1n104, 1m109).

The second line contains n integers a1,a2,,an, where ai denotes step length of the i-th frog (1ai109).
 

 

Output For each test case, you should print first the identifier of the test case and then the sum of all occupied stones' identifiers.  

 

Sample Input 3 2 12 9 10 3 60 22 33 66 9 96 81 40 48 32 64 16 96 42 72  

 

Sample Output Case #1: 42 Case #2: 1170 Case #3: 1872  

 

Source 2015ACM/ICPC亞洲區瀋陽站-重現賽(感謝東北大學)  

 

 

 

題意就是跳青蛙,通過分析會發現,就是步數a[i]與石頭數m,通過gcd(a[i],m)之後,gcd的倍數的和。

因為重複的數只計算一次,所以要去重。

一開始想的是容斥去重,然而還是太撈了,。。。

 

這道題和隊友討論了3天,還問了學長,發現幾個問題:

(1)如果直接列舉gcd的遍歷,應該為去重他們的最小公倍數,也就是這樣的。

for(ll j=0;j<cnt;j++)
            {
                if(i&(1<<j))
                    temp=temp*g[j]/gcd(temp,g[i]),jishu++;
            }

 

(2)直接gcd的容斥列舉去重會超時,因為極限資料可能要列舉1<<36次,for一次的極限資料個人認為可能就是1e7再帶點常數,1<<36次跑不出來,程式會崩。所以這種容斥是不可以的,雖然想法真的很好,但是真的過不去。所以,最後放棄了這種思路,其實還是可以容斥的,但是是有技巧的容斥。

 

 

直接看的題解,所以也不好說什麼,畢竟是人家的勞動成果,只是分析一下。

 

做法一:

尤拉函式的延伸用法:小於或等於n的數中,與n互質的數的總和為:φ(n) * n / 2  (n>1)。

做法二:

列舉m的因子個數,這樣就會少很多,就不存在超時的問題了。

 

以上兩種做法的具體題解傳送門:HDU 5514 Frogs(尤拉函式+數論YY)

 

 

直接貼程式碼吧。

 

程式碼1(尤拉函式):

 1 //尤拉函式的公式求解
 2 #include<iostream>
 3 #include<cstdio>
 4 #include<cstring>
 5 #include<algorithm>
 6 #include<bitset>
 7 #include<cassert>
 8 #include<cctype>
 9 #include<cmath>
10 #include<cstdlib>
11 #include<ctime>
12 #include<deque>
13 #include<iomanip>
14 #include<list>
15 #include<map>
16 #include<queue>
17 #include<set>
18 #include<stack>
19 #include<vector>
20 using namespace std;
21 typedef long long ll;
22 typedef pair<int,int> pii;
23 
24 const double PI=acos(-1.0);
25 const double eps=1e-6;
26 const ll mod=1e9+7;
27 const int inf=0x3f3f3f3f;
28 const int maxn=1e5+10;
29 const int maxm=100+10;
30 #define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
31 
32 ll a[maxn],n,m;
33 
34 ll gcd(ll a,ll b)
35 {
36     return b==0?a:gcd(b,a%b);
37 }
38 
39 ll euler(ll n)
40 {
41     ll ans=n;
42     for(int i=2;i*i<=n;i++){
43         if(n%i==0){
44             ans=ans/i*(i-1);
45             while(n%i==0) n/=i;
46         }
47     }
48     if(n>1) ans=ans/n*(n-1);
49     return ans;
50 }
51 
52 bool solve(int x)
53 {
54     for(int i=0;i<n;i++){
55         if(x%a[i]==0) return true;
56     }
57     return false;
58 }
59 
60 int main()
61 {
62     int t;
63     scanf("%d",&t);
64     for(int cas=1;cas<=t;cas++){
65         memset(a,0,sizeof(a));
66         scanf("%lld%lld",&n,&m);
67         for(int i=0;i<n;i++){
68             scanf("%d",a+i);
69             a[i]=gcd(a[i],m);
70         }
71         ll ans=0;
72         for(int i=1;i*i<=m;i++){
73             if(m%i) continue;
74             if(solve(i)) ans+=(ll)euler(m/i)*m/2;
75             if(i*i==m||i==1) continue;
76             if(solve((m/i))) ans+=(ll)euler(i)*m/2;
77         }
78         printf("Case #%d: %lld\n",cas,ans);
79     }
80 }
View Code

 

程式碼2(容斥原理):

 1 //容斥定理
 2 #include<iostream>
 3 #include<cstdio>
 4 #include<cstring>
 5 #include<algorithm>
 6 #include<bitset>
 7 #include<cassert>
 8 #include<cctype>
 9 #include<cmath>
10 #include<cstdlib>
11 #include<ctime>
12 #include<deque>
13 #include<iomanip>
14 #include<list>
15 #include<map>
16 #include<queue>
17 #include<set>
18 #include<stack>
19 #include<vector>
20 using namespace std;
21 typedef long long ll;
22 typedef pair<int,int> pii;
23 
24 const double PI=acos(-1.0);
25 const double eps=1e-6;
26 const ll mod=1e9+7;
27 const int inf=0x3f3f3f3f;
28 const int maxn=1e5+10;
29 const int maxm=100+10;
30 #define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
31 
32 ll gcd(ll a,ll b)
33 {
34     return b==0?a:gcd(b,a%b);
35 }
36 
37 ll g[maxn],fac[maxn];
38 int tp[maxn],num[maxn],vis[maxn];
39 
40 int main()
41 {
42     int t;
43     scanf("%d",&t);
44     for(int cas=1;cas<=t;cas++){
45         ll n,m;
46         scanf("%lld%lld",&n,&m);
47         int ok=0;
48         for(int i=0;i<n;i++){
49             scanf("%lld",&g[i]);
50             g[i]=gcd(g[i],m);
51             if(g[i]==1) ok=1;
52         }
53         if(ok==1){
54             printf("Case #%d: %lld\n",cas,m*(m-1)/2);
55             continue;
56         }
57         sort(g,g+n);
58         n=unique(g,g+n)-g;
59         memset(vis,0,sizeof(vis));
60         memset(num,0,sizeof(num));
61         int cnt=0;
62         for(ll i=2;i*i<=m;i++){
63             if(i*i==m) fac[cnt++]=m/i;
64             else if(m%i==0) fac[cnt++]=i,fac[cnt++]=m/i;
65         }
66         sort(fac,fac+cnt);
67         int cnt1=0;
68         for(int i=0;i<n;i++){
69             if(!vis[i]){
70                 tp[cnt1++]=g[i];
71                 for(int j=0;j<n;j++)
72                     if(g[j]%g[i]==0) vis[j]=1;
73             }
74         }
75         memset(vis,0,sizeof(vis));
76         for(int i=0;i<cnt;i++){
77             for(int j=0;j<cnt1;j++){
78                 if(fac[i]%tp[j]==0){
79                     vis[i]=1;
80                     break;
81                 }
82             }
83         }
84         ll sum=0;
85         for(int i=0;i<cnt;i++){
86             if(num[i]!=vis[i]){
87                 sum+=m*(m/fac[i]-1)/2*(vis[i]-num[i]);
88                 for(int j=i+1;j<cnt;j++)
89                     if(fac[j]%fac[i]==0)
90                     num[j]=num[j]+vis[i]-num[i];
91             }
92         }
93         printf("Case #%d: %lld\n",cas,sum);
94     }
95     return 0;
96 }
View Code

 

貼一下我們想了3天的錯誤程式碼,紀念一下。

程式碼(錯誤的):

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<bitset>
 6 #include<cassert>
 7 #include<cctype>
 8 #include<cmath>
 9 #include<cstdlib>
10 #include<ctime>
11 #include<deque>
12 #include<iomanip>
13 #include<list>
14 #include<map>
15 #include<queue>
16 #include<set>
17 #include<stack>
18 #include<vector>
19 using namespace std;
20 typedef long long ll;
21 typedef pair<int,int> pii;
22 
23 const double PI=acos(-1.0);
24 const double eps=1e-6;
25 const ll mod=1e9+7;
26 const int inf=0x3f3f3f3f;
27 const int maxn=1e5+10;
28 const int maxm=100+10;
29 #define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
30 #define lson l,m,rt<<1
31 #define rson m+1,r,rt<<1|1
32 
33 ll gcd(ll a,ll b)
34 {
35     return b?gcd(b,a%b):a;
36 }
37 
38 ll sum(ll x,ll n)
39 {
40     ll temp=(n-1)/x;
41     return temp*x+(temp*(temp-1)/2)*x;
42 }
43 
44 ll a[maxn];
45 
46 int main()
47 {
48     ll t;
49     scanf("%lld",&t);
50     for(int cas=1;cas<=t;cas++)
51     {
52         ll n,m;
53         scanf("%lld%lld",&n,&m);
54         ll h=0;
55         for(int i=0;i<n;i++)
56         {
57             ll x;
58             scanf("%lld",&x);
59             a[h++]=gcd(x,m);
60         }
61         vector<ll> g;
62         sort(a,a+h);
63         for(int i=h-1;i>=0;i--){
64             int flag=0;
65             for(int j=i-1;j>=0;j--){
66                 if(a[i]%a[j]==0) flag=1;
67             }
68             if(!flag) g.push_back(a[i]);
69         }
70         int cnt=g.size();
71         ll ans=0;
72         for(ll i=0;i<(1ll<<cnt);i++)
73         {
74             ll temp=1,jishu=0;
75             for(ll j=0;j<cnt;j++)
76             {
77                 if(i&(1<<j))
78                     temp=temp*g[j]/gcd(temp,g[i]),jishu++;
79             }
80             if(jishu==0)continue;
81             if(jishu&1) ans+=sum(temp,m);
82             else ans-=sum(temp,m);
83         }
84         printf("Case #%d: %lld\n",cas,ans);
85     }
86 }
View Code

 

 

 

到此為止,拜拜,再也不看這個題了。