介紹下自己的離奇思路:
先讀清楚題意!要求是旋轉等價,即兩個以 \(c\) 個 \(1\) 開頭,總 \(1\) 個數不超過 \(k+c\) 的字串算一種。
那怎麼刻畫"只算一種"這個條件呢?一個想法可以是,對每個字串賦一個權值,一種字串的權值即旋轉出來的每個合法的,把它們加起來應該是 \(1\),再全部加出來。
一個直接的賦權方式是,對一個最小迴圈節是 \(d(d|n)\) 的字串,它的權值為 \(\dfrac{1}{\sum\limits_{i=1}^{d}ok_i}\),其中 \(ok_i\) 代表字串是否可以以位置 \(i\) 開頭。
然後做法就是列舉 \(d\),求出容斥係數,因為要考慮所有大小為 \(d\) 的 \(01\) 串,所以要 dp 三個量:長度,\(1\) 個數,以及合法開頭點數。這樣複雜度肯定沒救了。
這是同學你可能會說:沒必要讓每個"合法點"都合法作為開頭啊?我對於一個 \(len\geq c\) 的 \(1\) 段,要求上必須以這個段的段頭作為開頭才可以,這不就輕鬆了嗎?
很好!這樣就改為了段數分之一,不過還是要 dp 這個段數有點煩,怎麼辦呢?
其實這時候你可以發現,我們沒有必要非去設它的所有合法輪換的權值都相等啊!只需要加起來是 \(1\) 就可以。於是容易想到,停時定理那個設權值的方式,即 \(\dfrac{a_i}{\sum a_k}\),換在這題也就是,目前一個段到下一個合法段的距離,除掉總長,也就是 \(d\)。而目前段就是 \(1\sim c\) 開頭那個儘量延伸,且要求 \(s_d=0\),即必須要是一個段頭。
這下我們就不需要考慮這個字串整體了,只需要得到一個最前的合法段,欽定即可!
其實可以兩種情況分開討論:
第一種情況的權值是 \(\dfrac{x-1}{d}\),其中 \(x\) 表示後面那個合法段的開頭位置。第二種的權值就是 \(\dfrac{d}{d}=1\)。
顯然情況一更難,我們只考慮這個咋求。不難發現,列舉 \(x\) 的位置,和 \(x\) 前面 \(0\) 的個數,就可以透過一些預處理做到除了預處理之外單次 \(O(d^2)\) 的複雜度。
那麼預處理是什麼呢?也不難,就是 \(f_{i,j}\) 表示長度為 \(i\),以 \(0\) 結尾,不能出現連續 \(c\) 個 \(1\) 的字串數,則有轉移:
其實這個甚至只用 \(+1\) 最佳化都可以做,完全不需要字首和最佳化。不過其它一些預處理可能要字首和最佳化,不過都很簡單。這裡是 \(O(n^2)\) 的。
最後一個小問題是我們列舉的是 \(d\),也就是欽定了有大小為 \(d\) 的迴圈節。注意,最小迴圈節一定整除其它的,所以容斥係數可以直接這麼理解:
然後乘乘加加就可以了。複雜度是 \(O(n^2+\sum\limits_{d|n}d^2)=O(\sum\limits_{i\geq 1}\dfrac{n}{i}^2)=O(n^2)\)。
這是我的做法,程式碼如下,很短:
#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
int powdv(int x,int y=mod-2){
int ans=1;
while(y){
if(y&1)ans=1ll*ans*x%mod;
y>>=1,x=1ll*x*x%mod;
}
return ans;
}
int ff[3005],hh[3005];
int f[3005][3005],h[3005][3005];
int main(){
int n,c,k;
scanf("%d%d%d",&n,&c,&k);
for(int d=n;d>=1;--d)if(n%d==0){
ff[d]=powdv(d);
for(int g=2*d;g<=n;g+=d)if(n%g==0){
ff[d]=(ff[d]-ff[g]+mod)%mod;
}
}
f[1][1]=1;
for(int i=2;i<=n;++i)for(int j=2;j<=i;++j){
f[i][j]=(f[i-1][j]+f[i-1][j-1])%mod;
if(i>c+1)f[i][j]=(f[i][j]-f[i-c-1][j-1]+mod)%mod;
}
h[0][0]=1;
for(int i=1;i<=n;++i)for(int j=0;j<=i;++j){
h[i][j]=(h[i-1][j]+(j?h[i-1][j-1]:0))%mod;
}
for(int i=1;i<=n;++i)for(int j=i;j>=1;--j){
h[i][j-1]=(h[i][j-1]+h[i][j])%mod;
}
int ans=0;
for(int d=c+1;d<=n;++d)if(ff[d]){
int mx=(k+c)/(n/d),gs=max(0,d-mx),he=0;
for(int i=1;i<=d-c;++i)for(int j=gs;j<=i;++j){
he=(he+1ll*d*f[i][j])%mod;
}
for(int i=1;i<=d;++i)hh[i]=0;
for(int i=c+2;i<=d-c;++i){
int ht=0;
for(int j=1;j<=i;++j){
hh[j]=(hh[j]+f[i-c-1][j])%mod;
ht=(ht+1ll*hh[j]*h[d-c-i][max(0,gs-1-j)])%mod;
}
he=(he+1ll*(i-1)*ht)%mod;
}
ans=(ans+1ll*ff[d]*he)%mod;
}
if(k==n-c)ans=(ans+1)%mod;
printf("%d\n",ans);
return 0;
}
另外說說 Burnside 的做法,也是積累了一個套路:
------------------分割線--------------------
看了眼題解,怎麼這麼簡單???我最開始的時候想用 Burnside,可是發現不完備,遂放棄。實際你仔細想想,發現我只要不要求必須以 \(c\) 個 \(1\) 開頭就好了啊!!!!!只用這樣一件事情:存在相鄰 \(c\) 個字元全為 \(1\),之後就還是列舉迴圈節去 Burnside 就好了。思維難度少了 \(114514\) 倍,唉唉唉。