while my guitar gently weeps(while)
題目背景
(不保證題目背景與題意無關)
吉他自那以后就被擱置在小屋里。
他靜靜想着,以后要去山坡的雲霧繚繞,要去河邊的月光似水,哪怕是巷陌街頭的熙熙攘攘。最好能登上閃光燈下的舞台,讓歡呼環抱在身邊,讓掌聲縈繞在心畔。“鈿頭銀篦擊節碎,血色羅裙翻酒污”自是每個主人的夢想,自也是每把吉他輕輕許下的願望。
漸漸地,小屋里爬滿了灰塵,樹蔭鏤過的陽光有時溜進來,隱約着翻飛的雜塵。
吉他只細細數着幾次雞鳴,又幾番夕落,心也一天天老了下去。
直到某一天,第一根琴弦輕輕掙斷,那是吉他的嗚噎。
這根琴弦——猶記主人第一天把弄琴弦就是從這里開始的,好奇地不停彈響這第一種天籟。每每秋日午后,坐在窗台上,陽光像現在一樣明亮,楊樹的葉像現在一般沙響,幾曲情歌,幾支小調,落下的秋葉都記得這些事。又何堪回首幾度春秋,多少番喜怒哀樂,多少輪陰晴圓缺,不論什么都可以在吉他里消遣。
——斷了。
直到某一天,第二根琴弦輕輕掙斷,那是吉他的抽泣。
這根琴弦——猶記主人第一次撥弄心弦就是從這里開始的,坐在河畔,月影靜靜地浮在水面上,風吹過的水面潺潺作響,仿佛流進心房,就像他和她心跳的聲音一樣。只記得浪漫的曲聲悠揚,飄出厚厚的蘆葦盪,飄進甜甜的夢鄉,是夜夜囈語,是道阻且長。
——斷了。
直到某一天,第三根琴弦輕輕掙斷,那是吉他的哀鳴。
這根琴弦——猶記主人第一次投趣心閑就是從這里開始的,常是暮春雨后,亦或月圓時節,撥一曲舒緩小調,執筆填一首雅趣小詞,揮筆描摹一幅山水畫卷,或是傾心栽培一株厭陽的小花……不知什么時候眼里的柔情變淡了,就連眼淚也不似以往咸甜;不知什么時候心性變得笨拙了,連琴弦上飛翻的手指都似乎沾染了思慮與焦灼。可終究我們都在,還同以往一樣親近,一樣喜歡風吹過的聲響。
——斷了。
不知過了多久,主人的面龐才從小屋的門縫里鑽進來——那是多么疲憊的一張臉啊,可淡淡的目光里分明看得出解脫,或許還有成長里的自得,還有重逢后的欣喜。
題目描述
又是秋末冬初,主人再次撥響了吉他殘存的琴弦。主人每次從一側彈入一個音符,每次在一根琴弦處可以發生反彈,當然也可以穿過這根琴弦。當音符第一次穿過一根邊界琴弦時開始彈響,最后一次穿出邊界琴弦時為曲終。
定義函數 \(f(i)\) 表示音符在琴中反彈 \(i-2\) 次的方案數,並定義 \(f(1)=1\)。
定義第零天的 \(f\) 值為 \(n\),以后每一天都反彈上一天的 \(f\) 值的次,以此類推,持續 \(k\) 天。
問第 \(k\) 天的 \(f\) 值模 \(p\) 。
輸入格式
一行三個整數 \(n,k,p\)。
輸出格式
一行一個整數,表示答案。
樣例
樣例輸入 1
7 2 1000003
樣例輸出 1
233
樣例輸入 2
7 5 1000003
樣例輸出 2
697759
數據范圍與提示
Subtask編號 | 分值 | \(n\le\) | \(k\le\) | 特殊性質 |
---|---|---|---|---|
\(\texttt{1}\) | \(\texttt{1}\) | \(10^{10^7}\) | \(10^{18}\) | \(p=1\) |
\(\texttt{2}\) | \(\texttt{5}\) | \(10^{10^7}\) | \(0\) | \(/\) |
\(\texttt{3}\) | \(\texttt{9}\) | \(10^7\) | \(1\) | \(/\) |
\(\texttt{4}\) | \(\texttt{15}\) | \(6\) | \(5\) | \(/\) |
\(\texttt{5}\) | \(\texttt{18}\) | \(10^{10^7}\) | \(10^7\) | \(/\) |
\(\texttt{6}\) | \(\texttt{2}\) | \(1\) | \(10^{18}\) | \(/\) |
\(\texttt{7}\) | \(\texttt{20}\) | \(10^{18}\) | \(10^{18}\) | \(/\) |
\(\texttt{8}\) | \(\texttt{30}\) | \(10^{10^7}\) | \(10^{18}\) | \(/\) |
對於 \(100\%\) 的數據,滿足 \(1\le n\le 10^{10^7},0\le k\le 10^{18},1\le p\le 10^6+3\)
一句話題意
設 \(f(n)\) 表示斐波那契數列的第 \(n\) 項,給定整數 \(n,m,p\),求 \(f^{(k)}(n)\bmod p\) 的值。
其中 \(f^{(k)}(n)\) 表示函數 \(f(n)\) 的 \(k\) 次迭代,即 \(f(f(f(\cdots f(n))))\),共有 \(k\) 層。
solution
Subtask 1
\(p=1\),輸出 \(0\) 即可。
Subtask 2
\(k=0\),即沒有嵌套,字符串輸入 \(n\),輸出 \(n\bmod p\) 的值即可。
Subtask 3
\(k=1\),即只嵌套一次,用矩陣乘法(或直接遞推),直接算出 \(f(n)\bmod p\) 的值即可。
Subtask 4
出題人也不知道為什么要設這個點。
Subtask 5
為方便,設 \(t_0=p\)。
容易發現,斐波那契數列在模意義下是有周期的,並且一定存在一個整數 \(m(2\le m\le p^2)\),使得 \(f(m)\equiv f(1)\pmod p,f(m+1)\equiv f(2)\pmod p\),因此我們設 \(f(n)\bmod t_0\) 意義下的周期為 \(t_1\),\(f^{(k)}(n)\bmod t_0=f(f^{(k-1)}(n)\bmod t_1)\bmod t_0\)。
同理,一直進行下去,可以得到答案為 \(f(\cdots f(n\bmod t_k)\bmod t_{k-1}\cdots)\bmod t_0\)。
用矩陣乘法單次斐波那契復雜度為 \(\log p\),共有 \(k\) 層嵌套,每次只需改動模數即可,每次的模數可暴力預處理(此處的復雜度見下),復雜度為 \(O(k\log p)\)。
Subtask 6
\(n=1\),嵌套幾層都是 \(1\),輸出 \(1\) 即可。
Subtask 7
出題人也不知道為什么要設這個點。
Subtask 8
在上面的基礎上,我們進一步可以發現若有 \(m(m<k)\) 使得 \(t_{m}=t_{m+1}\) 則 \(t_{i}=t_{m}(m\le i\le k)\)。
因此在上面暴力算 \(t\) 數組時,算到相同時便可停止,經打表,\(m<25,t_m<10^7\)。
對於 \(k\) 范圍的擴大,我們會發現 \(t_i(m\le i\le k)\) 均相同。因此我們可以使用相同的方法,從內向外按上面的方法計算,當其嵌套 \(q\) 層與 \(r(q>r)\) 層相同時,便又產生了一個新的周期,這個周期的長度由抽屜原理可知,與模數(即 \(t_m\))同階。
所以最終復雜度為 \(O(p\log p)\)。
std
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N=1e6+3,K=25,Len=1e7+5;
long long n,k,mod,ans;
int t[K],id,id2,len;
char s[Len];
int book[Len];
const int siz=2;
struct mat
{
long long a[siz][siz];
mat(){memset(a,0,sizeof(a));}
mat operator*(mat x)
{
mat ans;
long long r;
for(int i=0;i<siz;i++)
{
for(int k=0;k<siz;k++)
{
r=a[i][k];
for(int j=0;j<siz;j++)
{
ans.a[i][j]+=x.a[k][j]*r;
ans.a[i][j]%=mod;
}
}
}
return ans;
}
mat operator^(long long x)
{
mat ans,base;
for(int i=0;i<siz;i++) ans.a[i][i]=1;
for(int i=0;i<siz;i++)
{
for(int j=0;j<siz;j++) base.a[i][j]=a[i][j]%mod;
}
while(x)
{
if(x&1) ans=ans*base;
base=base*base;
x>>=1;
}
return ans;
}
};
long long fib(long long x)
{
if(x==0) return 0;
if(x==1||x==2) return 1;
mat f,g;
f.a[0][0]=f.a[0][1]=1;
g.a[0][0]=g.a[0][1]=g.a[1][0]=1;
f=f*(g^(x-2));
return f.a[0][0];
}
void calc()
{
for(int i=0;i<=k-1;i++)
{
long long a=1%t[i],b=2%t[i],c;
t[i+1]=1;
while((a!=1%t[i])||(b!=1%t[i]))
{
c=(a+b)%t[i];
a=b;
b=c;
t[i+1]++;
}
if(t[i]==t[i+1])
{
id=i;
return;
}
}
}
inline long long read()
{
long long x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
signed main()
{
scanf("%s",s+1);
n=0;
id=id2=k=read();
mod=read();
t[0]=mod;
calc();
len=strlen(s+1);
for(int i=1;i<=len;i++)
{
if(k>=id) n=(n*10+s[i]-48)%t[id];
else n=(n*10+s[i]-48)%t[k];
}
if(k==0)
{
printf("%lld\n",n);
return 0;
}
ans=n;
book[ans]=1;
mod=t[id];
for(long long i=k-1;i>=id;i--)
{
ans=fib(ans);
if(book[ans])
{
id2=k-i-book[ans]+1;
break;
}
book[ans]=k-i+1;
}
k=(k-id-book[ans]+1)%id2+id+book[ans]-1;
for(int i=k-1;i>=0;i--)
{
if(i>=id) mod=t[id];
else mod=t[i];
ans=fib(ans);
}
printf("%lld\n",ans);
return 0;
}
后話
吐槽
第一次出有難度的題。(上回那個是 \(\texttt{ycx}\) 的 \(\texttt{idea}\))
碼量小、思維套路的簽到題。
部分分不太好設,因此是亂設的。不過這題這么水應該用不到。
如果有人被卡常,我深表遺憾。
(這話怎么這么熟悉)
本來驗題的時候 \(\texttt{ycx}\) 發現可以用 Pollard Rho 算法來優化,使得 \(p\) 的范圍擴大,並且使用高精來使得 \(k\) 與 \(n\) 同階,但在良心出題人 \(\texttt{zkx}\) 的勸阻下,棄了這個想法。(主要是沒時間)
另外你們有沒有發現,這題的英文名稱叫做 \(while\),其實也在暗示此題的雙重循環節(bushi
總結
\(32\times 1+17\times 1+1\times 1+0\times 3=50\)
平均得分:\(8.33\)
略低於預期,話說為什么連一分都不想拿啊。