1. 程式人生 > >【洛谷月賽】洛谷三月月賽題解報告

【洛谷月賽】洛谷三月月賽題解報告

現在 圖片 數字 div 但是 格式 image 投票 cin

  昨天就是洛谷三月月賽,小編考的並不好,才31分,隔壁大佬50分,於是小編決定改一改題,先看第一題:

                  P5238 整數校驗器

題目描述

有些時候需要解決這樣一類問題:判斷一個數 x 是否合法。

x 合法當且僅當其滿足如下條件:

  • x 格式合法,一個格式合法的整數要麽是 0,要麽由一個可加可不加的負號,一個 19 之間的數字,和若幹個 09 之間的數字依次連接而成。
  • x 在區間 [l,r][l,r] 範圍內(即 lxr)。

你需要實現這樣一個校驗器,對於給定的 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,小編表示微積分離我太遙遠,推薦大家看三題正解,四題正解。

【洛谷月賽】洛谷三月月賽題解報告