隨(rand):原根,循環矩陣,dp


20分特判,一個puts("1")一個快速冪,不講。

50%算法:

上次就講了,可是應該還是有像 xuefen某 或 Dybal某 一樣沒聽的。

用a×inv(b)%mod來表示分數的時候,這個分數值可加可乘(有空證明)

像是一個dp題啊。

初狀態是1方案數為1,然后做乘法轉移不就好了嘛?

設dp[i][j]表示進行了i次操作后所得的值為j

dp[i][j*a[k]%mod]+=dp[i-1][j];

復雜度O(mod2×m)

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<algorithm>
 5 #include<queue>
 6 #include<vector>
 7 #include<string>
 8 #include<cstring>
 9 #define int long long
10 #define m(a) memset(a,0,sizeof(a))
11 #define AA cout<<"Alita"<<endl
12 using namespace std;
13 const int mod=1e9+7;
14 int ans,S,n,m,p,v[1050],a[100500],f[1050][1050],g[1050][1050],tmp[1050][1050];
15 int poww(int x,int y,int z)
16 {
17         int sum=1;
18         while(y)
19         {
20                 if(y&1) sum=sum*x%z;
21                 y>>=1;
22                 x=x*x%z;
23         }
24         return sum;
25 }
26 void X_g()
27 {
28         memset(tmp,0,sizeof(tmp));
29         for(int i=1;i<p;i++)
30         {
31                 for(int j=1;j<p;j++)
32                 {
33                         (tmp[1][i]+=g[1][j]*f[j][i]%mod)%=mod;
34                 }
35         }
36         for(int i=1;i<p;i++) 
37         {
38                 g[1][i]=tmp[1][i];
39         }
40 }
41 void X_f()
42 {
43         memset(tmp,0,sizeof(tmp));
44         for(int i=1;i<p;i++)
45         {
46                 for(int j=1;j<p;j++)
47                 {
48                         for(int k=1;k<p;k++)
49                         {
50                                 (tmp[i][j]+=f[i][k]*f[k][j]%mod)%=mod;
51                         }
52                 }
53         }
54         for(int i=1;i<p;i++)
55         {
56                 for(int j=1;j<p;j++)
57                 {
58                         f[i][j]=tmp[i][j];
59                 }
60         }
61 }
62 void work(int y)
63 {
64         while(y)
65         {
66                 if(y&1) X_g();
67                 y>>=1;
68                 X_f();
69         }
70 }
71 signed main()
72 {
73         //freopen("1.in","r",stdin);
74         //freopen("1.out","w",stdout);
75         scanf("%lld%lld%lld",&n,&m,&p);
76         if(p==2){puts("1");return 0;}
77         for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
78         if(n==1){printf("%lld",poww(a[1],m,p));return 0;}
79         S=poww(n,mod-2,mod);
80         for(int i=1;i<=n;i++)
81         {
82                 (v[a[i]%p]+=S)%=mod;
83         }
84         g[1][1]=1;
85         for(int j=1;j<p;j++)
86         {
87                 for(int k=1;k<p;k++)
88                 {
89                         f[j][j*k%p]+=v[k];
90                 }
91         }
92         work(m);
93         for(int i=1;i<p;i++)
94         {
95                 (ans+=g[1][i]*i%mod)%=mod;
96         }
97         printf("%lld",ans);
98         return 0;
99 }
Dybala的50分代碼(已征得同意后轉載)

因為我直接沒想這個這么暴力的dp,看到題目的m已經破1e8了那么要么復雜度與之無關要么把它log掉

 

理論80%算法:

這題m又不是一個計算參數,復雜度不太可能與之無關,嘗試log?

帶log的dp。。。矩陣快速冪啊!

可以發現dp的第二維並不大,是mod級別,矩陣乘mod3貌似勉強可以接受

O(mod3×log2m)的復雜度。我也不知道題解為什么說能得80分。

略微一算,知道復雜度已經略微超過極限,卡常?沒有用,還是50分。

個人認為把矩陣快速冪和暴力dp的得分設置成一樣不太道德。

到現在還有人不會用矩陣快速冪優化dp啊。。。這。。。我該怎么講?

ans矩陣用來更新答案,base矩陣是快速冪的基底。

普通快速冪是這個樣子的:for(;t;t>>=1,base=base*base){if(t&1)ans*=base;base*=base;}

換成矩陣也是一樣的啊:for(;t;t>>=1){if(t&1)mult_ans();mult_base();}

只不過是兩個矩陣乘法函數而已。

提醒:包括用重載運算符的矩陣乘,傳參時要帶上‘&’符號,不然有時會TLE/RE

base矩陣的構造也很簡單,對於每一個可能被選到的數a[i],枚舉乘之前的數j。

base[j][j*a[i]%p]+=1;(這里是求解總方案書數,如果是概率打法要把1改成概率)

就是能從a轉移到b的話,base[a][b]就有值,是方案的話就是1,算概率的話就是概率。

ans矩陣可以弄成一維數組,能略快一些。這題初始為1,那么ans[1]=1就好了。

注意:代碼中的p是原題中的mod,而代碼中的mod是1e9+7。我是概率打法

 1 #include<cstdio>
 2 #define int long long
 3 #define mod 1000000007ll
 4 int n,m,p,base[1003][1003],ans[1003],res[1003][1003],a[100005],ANS;
 5 int pow(int bbase,int t,int modd,int aans=1){
 6     for(;t;t>>=1,bbase=bbase*bbase%modd)if(t&1)aans=aans*bbase%modd;
 7     return aans;
 8 }
 9 void mult_base(){
10     for(int i=0;i<p;++i)for(int j=0;j<p;++j)for(int k=0;k<p;++k)(res[i][j]+=base[i][k]*base[k][j])%=mod;
11     for(int i=0;i<p;++i)for(int j=0;j<p;++j)base[i][j]=res[i][j],res[i][j]=0;
12 }
13 void mult_ans(){
14     for(int i=0;i<p;++i)for(int j=0;j<p;++j)(res[0][j]+=ans[i]*base[i][j])%=mod;
15     for(int i=0;i<p;++i)ans[i]=res[0][i],res[0][i]=0;
16 }
17 signed main(){
18     scanf("%lld%lld%lld",&n,&m,&p);
19     if(n==1){
20         scanf("%lld",&a[1]);
21         printf("%lld\n",pow(a[1],m,p));return 0;
22     }
23     ans[1]=1;const int P=pow(n,mod-2,mod);//概率,即1/n
24     for(int i=1;i<=n;++i)scanf("%lld",&a[i]),a[i]%=p;
25     for(int i=1;i<=n;++i)for(int j=0;j<p;++j)(base[j][j*a[i]%p]+=P)%=mod;
26     for(;m;m>>=1){if(m&1)mult_ans();mult_base();}
27     for(int i=0;i<p;++i)(ANS+=ans[i]*i)%=mod;
28     printf("%lld\n",ANS);
29 }
卡死常數還是50分

 

100%算法:

想象一下如果題目改一下,你是隨機選擇數加上它而不是乘,該怎么做?

原始dp方程:dp[i][(j+a[k])%mod]+=dp[i-1][j];

同理,構造矩陣。

base[i][(i+a[j])%mod]=1;//或者概率

寫幾個不太大的樣例,把矩陣寫出來,你會發現,它是一個循環矩陣。

這個加法類型的轉移矩陣基本上都是循環矩陣(對於不同的被轉移數,加數都一樣)

回到這道題。

首先從復雜度上考慮,也許可以猜出來這是個循環矩陣。

可是那個詭異的乘法形式並不是循環的,雖然它對於不同的被轉移數,乘數都一樣。

如果它也是一個加法,該多好啊。

沒辦法,它是個乘法,我們要接受這個現實。

但是,這並不能阻礙我們把它強制弄成加法。

看看孫金寧老師的叮囑:啊,什么原根,啊,幾次方,啊什么取到所有值。。。(困)

次方?乘法?加法?歐拉定理?

xa × xb=xa+b

誒,這里有加法了!這樣可以把乘法換成加法。

動用一下歐拉定理:

xa × xb=x(a+b)%(mod-1)

如果題目的所有輸入都是x的幾次方,就很好做了。

考慮:剛開始一個數是a0,你可以從a4,a7,a23里面隨機選數把它乘起來,值對mod取模,得到ans,求最后ans的期望

這個問題就相當於:考慮:剛開始一個數是0,你可以從4,7,23里面隨機選數把它加起來,值對mod-1取模,求最后aans%mod的期望

好做好做!就是普通的加法dp,循環矩陣肝它!

但是。。。這個a是多少呢?

繼續聽孫金寧的數學課。原根啥啥啥的。。。

原根?啥?原根?哦。好吧。原根就原根吧。

如果你比我聰明,你可能會直接發現,既然原根的次方值能夠取遍1~mod-1的所有數,那么這些數就都可以用原根唯一確定的表示出來。那么就可以把題目的所有數都用原根表示,然后當成加法dp來做。你就A了。

如果你沒我聰明,那么就相當與你和我一樣聰明。

那么如果咱們一樣聰明,誒,那好啊,咱們都想不到。既然它一直在念叨原根,那就試試拿原根表示唄。

那么首先我們需要求出原根。用題目給出的那個充要條件很好求。

for(int i=1;i<p;i++){
    int now=1,j;
    for(j=0;j<p-1;++j)
        if(al[now]==-1)//像它描述里說的一樣,不能出現重復的,因為它要在mod-1次里取到mod-1個不同值,故不能重復
            al[now]=j,//記錄下now這個值唯一確定的對應着i的幾次冪
            qpow[j]=now,//記錄下來i的j次冪是幾,以后會用到
            now=now*i%p;//now是i的j次冪,j++,更新
        else break;
    if(j==p-1){g=i;break;}//如果j成功的走完了,沒有被中途break掉,那么i就是原根。
    else for(int i=1;i<p;++i)al[i]=-1;//清空
}

然而我這么求是用了它說的第一條,這樣的話可以順便求出qpow和al數組里面的值。

al數組不僅是用來標記是否出現過的。如果它出現過,還記錄了它對應的是原根g的幾次冪。

接下來,我們就擁有了1~mod-1里面每個值和g的幾次冪間的雙向映射。

那么就把原問題映射成加法dp,循環矩陣干他,再映射回來統計答案即可。

 1 #include<cstdio>
 2 #define int long long
 3 #define mod 1000000007ll
 4 int n,m,p,base[1003],ans[1003],res[1003],a[100005],ANS;
 5 int al[1003],g,qpow[1003];
 6 int pow(int bbase,int t,int modd,int aans=1){
 7     for(;t;t>>=1,bbase=bbase*bbase%modd)if(t&1)aans=aans*bbase%modd;
 8     return aans;
 9 }
10 void mult_base(){
11     for(int i=0;i<p;++i)for(int j=0;j<p;++j)(res[j]+=base[i]*base[(j-i+p)%p])%=mod;
12     for(int i=0;i<p;++i)base[i]=res[i],res[i]=0;
13 }
14 void mult_ans(){
15     for(int i=0;i<p;++i)for(int j=0;j<p;++j)(res[j]+=ans[i]*base[(j-i+p)%p])%=mod;
16     for(int i=0;i<p;++i)ans[i]=res[i],res[i]=0;
17 }
18 signed main(){
19     scanf("%lld%lld%lld",&n,&m,&p);
20     ans[0]=1;const int P=pow(n,mod-2,mod);
21     for(int i=1;i<=1000;++i)al[i]=-1;
22     for(int i=1;i<p;i++){
23         int now=1,j;
24         for(j=0;j<p-1;++j)
25             if(al[now]==-1)al[now]=j,qpow[j]=now,now=now*i%p;
26             else break;
27         if(j==p-1){g=i;break;}
28         else for(int i=1;i<p;++i)al[i]=-1;
29     }p--;
30     for(int i=1;i<=n;++i)scanf("%lld",&a[i]),a[i]=al[a[i]];
31     for(int i=1;i<=n;++i)(base[a[i]]+=P)%=mod;
32     for(;m;m>>=1){if(m&1)mult_ans();mult_base();}
33     for(int i=0;i<p;++i)(ANS+=ans[i]*qpow[i])%=mod;
34     printf("%lld\n",ANS);
35 }
於倬浩:然后你就愉快的拿到了100分的好成績


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM