【洛谷月賽】洛谷三月月賽題解報告
昨天就是洛谷三月月賽,小編考的並不好,才31分,隔壁大佬50分,於是小編決定改一改題,先看第一題:
P5238 整數校驗器
題目描述
有些時候需要解決這樣一類問題:判斷一個數 x 是否合法。
x 合法當且僅當其滿足如下條件:
- x 格式合法,一個格式合法的整數要麽是 0,要麽由一個可加可不加的負號,一個 1 到 9 之間的數字,和若幹個 0 到 9 之間的數字依次連接而成。
- x 在區間 [l,r][l,r] 範圍內(即 l≤x≤r)。
你需要實現這樣一個校驗器,對於給定的 l,r,多次判斷 x 是否合法。
輸入輸出格式
輸入格式:
第一行三個整數 T,l,r,表示校驗器的校驗區間為 [l,r][l,r],以及需要校驗的 x 的個數。
接下來 T 行,每行一個x,表示要校驗的數,保證 x 長度至少為 1 且僅由 ‘0‘~‘9‘ 及 ‘-‘ 構成,且 ‘-‘ 只會出現在第一個字符。
輸出格式:
輸出共 T 行,每行一個整數,表示每個 xx 的校驗結果。
校驗結果規定如下:0 表示 x 合法;1 表示 x 格式不合法;2 表示 x 格式合法且不在 [l,r] 區間內。
輸入輸出樣例
輸入樣例#1: 復制-3 3 4 0 00 -0 100000000000000000000輸出樣例#1: 復制
0 1 1 2
這道題一看很有思路,不就是輸入字符串,在多判斷幾次,代碼很快就出來了。
1 // luogu-judger-enable-o2 2 #include<iostream> 3 #include<cstdio> 4 using namespace std; 5 char c[1000000000];long long T,l,r,cnt,sum,k;bool flag;char x; 6 int main() 7 { 8 scanf("%lld%lld%lld",&l,&r,&T); 9 x=getchar();10 for(int i=1;i<=T;i++) 11 { 12 //x=getchar(); 13 cnt=0;sum=0;flag=false;k=0; 14 for(;;) 15 { 16 c[++cnt]=getchar();//cout<<"1:"<<cnt<<" "<<c[cnt]<<endl; 17 //cout<<c[cnt]<<" "<<sum<<endl; 18 if(c[cnt]==‘-‘) sum=0-sum; 19 else if(c[cnt]==‘\n‘) break; 20 else 21 { 22 sum*=10;sum+=(c[cnt]-48); 23 } 24 25 } 26 // for(int j=1;j<=cnt;j++) 27 // cout<<c[j]; 28 // cout<<endl; 29 //cout<<"1:"<<cnt<<" "<<sum<<endl; 30 for(int j=1;j<=cnt;j++) 31 { 32 33 34 if(c[j]==‘0‘&&cnt>2&&j==1) 35 { 36 //cout<<"c["<<j<<"]==‘0‘&&"<<cnt<<">2"<<endl; 37 printf("1 \n");k=1; 38 break; 39 } 40 else if(c[j]==‘-‘) 41 { 42 //cout<<"c["<<j<<"]==‘-‘"<<endl; 43 if(c[j+1]==‘0‘) 44 { 45 printf("1 \n");k=1; 46 break; 47 } 48 } 49 else if(l>sum||sum>r) 50 { 51 //cout<<l<<" "<<sum<<" "<<r; 52 printf("2 \n");k=1; 53 break; 54 } 55 // else if(flag==true) 56 // { 57 // if(l>(0-sum)||(0-sum)>r) 58 // { 59 // printf("2 \n");k=1; 60 // break; 61 // } 62 // } 63 } 64 if(k==0) printf("0 \n"); 65 } 66 return 0; 67 }
草草一寫,只得了5分,令小編十分傷心,於是小編就開始找自己寫的漏洞,發現當l=-100,r=100時,輸入100會輸出1,這是為什麽呢?還有輸入0102時應該輸出1,而這個程序卻輸出了2,小編才發現有些地方順序不對,還有的多加了回車。於是更改如下:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 char c[1000000000];long long T,l,r,cnt,sum,k;bool flag;char x; 5 int main() 6 { 7 scanf("%lld%lld%lld",&l,&r,&T); 8 x=getchar(); 9 for(int i=1;i<=T;i++) 10 { 11 //x=getchar(); 12 cnt=0;sum=0;flag=false;k=0; 13 for(;;) 14 { 15 c[++cnt]=getchar();//cout<<"1:"<<cnt<<" "<<c[cnt]<<endl; 16 //cout<<c[cnt]<<" "<<sum<<endl; 17 if(c[cnt]==‘-‘) sum=0-sum; 18 else if(c[cnt]==‘\n‘) break; 19 else 20 { 21 sum*=10;sum+=(c[cnt]-48); 22 } 23 24 } 25 // for(int j=1;j<=cnt;j++) 26 // cout<<c[j]; 27 // cout<<endl; 28 //cout<<"1:"<<cnt<<" "<<sum<<endl; 29 for(int j=1;j<=cnt;j++) 30 { 31 32 //cout<<c[j]<<" "<<c[j+1]<<endl; 33 if(c[j]==‘0‘&&c[j+1]!=‘\n‘&&j==1) 34 { 35 //cout<<"c["<<j<<"]==‘0‘&&"<<cnt<<">2"<<endl; 36 printf("1 \n");k=1; 37 break; 38 } 39 else if((c[j]==‘-‘&&c[j+1]==‘\n‘)||(c[j]==‘-‘&&c[j+1]==‘0‘)) 40 { 41 //cout<<"c["<<j<<"]==‘-‘"<<endl; 42 printf("1 \n");k=1; 43 break; 44 } 45 else if(l>sum||sum>r) 46 { 47 //cout<<l<<" "<<sum<<" "<<r; 48 printf("2 \n");k=1; 49 break; 50 } 51 // else if(flag==true) 52 // { 53 // if(l>(0-sum)||(0-sum)>r) 54 // { 55 // printf("2 \n");k=1; 56 // break; 57 // } 58 // } 59 } 60 if(k==0) printf("0 \n"); 61 } 62 return 0; 63 }
一個可愛的10分,沒事,不自卑,好歹多了5分,5分也是分啊。
那麽究竟什麽樣的數不符合規則呢?
- -0當然不符合,誰家0寫‘-’號呢?
- -當然也不合適,後面沒有數字了。
- 0……有前導0的當然也不行。
- 爆long long的當然不可能正常。
- 超過l,r範圍的也不行。
既然這些都不行,那麽剩下的都可以嘍,看了題解之後才發現缺了第4條的判斷。思路很簡單:先判斷格式是否正確和是否爆unsigned long long,然後判斷是否超出範圍,總之一大堆判斷模擬即可,代碼如下:
1 #include<iostream> 2 #include<sstream> 3 #include<cstring> 4 #include<cstdio> 5 using namespace std; 6 long long l,r,T,cnt=0,len=0;char c[100000]; 7 int main() 8 { 9 cin>>l>>r>>T; 10 for(int i=1;i<=T;i++) 11 { 12 len=0;cnt=0; 13 cin>>(c+1);//輸入字符串,這麽寫是為了去掉多出的回車 14 len=strlen(c+1); 15 //cout<<c[1]<<endl; 16 if(c[1]==‘-‘&&(len==1||c[2]==‘0‘))//只有一個‘-’的和‘-0’當然不行 17 { 18 cout<<"1"<<endl; 19 continue; 20 } 21 else if(c[1]==‘0‘&&len!=1)//有前導0的也不行 22 { 23 cout<<"1"<<endl; 24 continue; 25 } 26 else if(c[1]==‘-‘&&len>20)//負數太小了不可能 27 { 28 cout<<"2"<<endl; 29 continue; 30 } 31 else if(c[1]!=‘-‘&&len>19)//正數太大了不可能 32 { 33 cout<<"2"<<endl; 34 continue; 35 } 36 unsigned long long sum;//之前的處理是為了防止數字爆unsigned long long 37 long long x;//可以說x存的是sum的絕對值 38 if(c[1]==‘-‘) 39 { 40 sscanf(c+2,"%llu",&sum);//註意在#include<cstdio>和#include<sstrea 41 if(sum>(1LL<<63)) //m>兩個頭文件下才可以使用,把字符串轉換成 42 { //數字,當然,也可以手寫。 43 cout<<"2"<<endl; //1LL表示(long long)1,1<<63表示1*2^63 44 continue; 45 } 46 x=0-sum; 47 } 48 else 49 { 50 sscanf(c+1,"%llu",&sum); 51 if(sum>=(1LL<<63)) 52 { 53 cout<<"2"<<endl; 54 continue; 55 } 56 x=sum; 57 } 58 if(x>=l&&x<=r) cout<<"0"<<endl; 59 else cout<<"2"<<endl;//超出範圍當然不行 60 } 61 return 0; 62 }
接著看第二題:
P5239 回憶京都
題目背景
第十五屆東方人氣投票 音樂部門 106名
第四次國內不知道東方的人對東方原曲的投票調查 51名
回憶京都副歌我tm吹爆,東方文花帖我tm吹爆!
題目描述
輸入輸出格式
輸入格式:
第一行輸入一個q,表示有q次詢問。
第二行開始,一共q行,每行兩個數字n,m,意思如題所示。
輸出格式:
一共q行,對於每一個詢問,都輸出一個答案。
輸入輸出樣例
輸入樣例#1: 復制5 2 3 1 4 4 3 2 5 3 5輸出樣例#1: 復制
10 10 11 35 50
這道題一看就明白了,只要暴力求解不就行了嗎,但是小編表示看不懂這個是幹什麽的,看了題解才發現這個式子是讓求1~n和1~m的所有C的和,什麽意思呢?舉個例子。
就比如說樣例吧當m=2,n=3時,i的取值是1~2,j的取值是1~3,那麽C?¹+C?²+C?³+C?¹+C?²+C?¹就是這一串式子的值。
看完題解發現這道題是一道前綴和的模板題,之前學動態規劃和樹狀數組的時候有點印象,那麽什麽是前綴和呢?好說,如圖所示:
像這樣,有一個數組,裏面有若幹個數字,你的任務是輸出每次詢問的前i個數的和,但是這和我們的題有什麽關系呢?別著急把它擴展成二維前綴和。
如果每一個方格中存儲的值都為C[i][j]的值,sum數組存的是前i行前j列所有C的和,那麽就能得到動態規劃的狀態轉移方程:
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+C[i][j]。
這個狀態轉移方程應該比較好理解吧。舉個例子,假設要求i=3,j=4時的sum值,那麽sum[i][j]用方格可以表示為:
sum[i-1][j]可以表示為:
sum[i][j-1]可以表示為:
很顯然,下圖的棕色區域被算了2次,要減掉,同時加上C[i][j](深藍色格子)的值,還要記得過程中不停取模。
但是暴力去算每一個C都非常麻煩,不妨用動態規劃的思路來解題,這種組合題對於每一個數,只有兩種情況,要麽選(共有C[i-1][j-1]種情況),要麽不選(共有C[i][j-1]種情況),所以C[i][j]只和C[i-1][j-1],C[i][j-1]有關,得到以下狀態轉移方程:
C[i][j]=C[i-1][j-1]+C[i][j-1]
就這樣按順序遞推,代碼就出來了:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 long long c[1001][1001],sum[1001][1001],n,m,q; 5 int main() 6 { 7 c[1][1]=1;c[1][0]=1; 8 for(int i=2;i<=1000;i++)//打表 9 { 10 c[i][0]=1; 11 for(int j=1;j<=i;j++) 12 c[i][j]=(c[i-1][j-1]+c[i-1][j])%19260817; 13 } 14 for(int i=1;i<=1000;i++)//打表 15 for(int j=1;j<=1000;j++) 16 sum[i][j]=(sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+c[i][j]+19260817)%19260817; 17 cin>>q; 18 for(int i=1;i<=q;i++) 19 { 20 cin>>m>>n; 21 cout<<sum[n][m]<<endl;//詢問時直接輸出即可 22 } 23 return 0; 24 }
為什麽這道題可以打表呢?很簡單,這道題的數據範圍如下:
n和m最大值只有1000,就不必一次一次慢慢算了,這樣比較快。
第三題和第四題居心叵測,坑害無數人,全網僅有1人AC,小編表示微積分離我太遙遠,推薦大家看三題正解,四題正解。
【洛谷月賽】洛谷三月月賽題解報告