1. 程式人生 > >清北學堂Day1

清北學堂Day1

一點 雙向bfs 要求 col iostream .net 排除 b- 研究

Part 1:模擬考試總結

這次第一題拿了60,第二題拿了49(不知道怎麽拿的)。

第一題:

技術分享

我的想法(60分,原本是可以得70的,結果數組開小了)是,首先在輸入的時候初始化,a[i][x]指前i個裏有a[i][x]個x這個字母(類似於前綴和)。分別枚舉區間的左右端點,之後在區間內枚舉26個字母的最大值-最小值的最大值。時間復雜度是O(26*n^2)。

代碼:

技術分享
 1 #include <iostream>
 2 #include <cmath>
 3 #include <cstring>
 4 #include <cstdio>
 5 #include <cstdlib>
 6
#include <algorithm> 7 using namespace std; 8 char s[101010]; 9 int a[101010][30]; 10 int main() 11 { 12 13 freopen("a.in","r",stdin); 14 freopen("a.out","w",stdout); 15 16 int n; 17 scanf("%d",&n); 18 for(int i=1;i<=n;i++) 19 { 20 cin>>s[i]; 21
a[i][s[i]-a+1]=a[i-1][s[i]-a+1]+1; 22 for(int j=1;j<=26;j++) 23 if(j==s[i]-a+1) continue; 24 else 25 { 26 if(i==n && a[i-1][j]==0/**/) a[i][j]=-1; 27 else a[i][j]=a[i-1][j]; 28 } 29 } 30
int maxn=0,minn=9999999; 31 int ans=0; 32 for(int i=1;i<=n;i++) 33 { 34 for(int j=0;j<i;j++) 35 { 36 maxn=0;minn=9999999;/**/ 37 for(int k=1;k<=26;k++) 38 { 39 if(a[n][k]==-1) continue; 40 maxn=max(maxn,a[i][k]-a[j][k]); 41 if(a[i][k]-a[j][k]==0) continue;/**/ 42 minn=min(minn,a[i][k]-a[j][k]); 43 ans=max(ans,maxn-minn); 44 } 45 } 46 } 47 printf("%d",ans); 48 return 0; 49 }
a

其中有/**/標誌的,是最開始沒想全面的地方。

100分做法:具體見註釋。

技術分享
 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<vector>
 5 
 6 using namespace std;
 7 
 8 const int maxn=1000010;
 9 
10 int n,ans,p[26][26],minv[26][26],sum[26],last[26];
11 
12 char s[maxn];
13 
14 int main()
15 {
16     freopen("a.in","r",stdin);
17     freopen("a.out","w",stdout);
18 
19     scanf("%d",&n);
20     scanf("%s",s+1);
21     for (int a=1;a<=n;a++)
22     {
23         int c=s[a]-a;
24         sum[c]++;  //字母s[a]的前綴和 
25         last[c]=a;  //字母a的最後位置 
26         //此時我們已經找到了最大的右端點以及相對應的字母(設為x),接下來需要做的就是找到一個左端點及其對應字母(設為y),使得這個區間的最大減最小最大。 
27         //因此我們希望左端點前面的字母x盡量少,字母y盡量多。也就是兩者的數量差盡量小。(minv中存的東西) 
28         for (int b=0;b<26;b++) 
29             if (b!=a && sum[b]) ans=max(ans,max(sum[c]-sum[b]-minv[c][b]-(last[b]==p[c][b]),sum[b]-sum[c]-minv[b][c]-(last[b]==p[b][c])));
30         for (int b=0;b<26;b++)
31         {
32             if (sum[c]-sum[b]<minv[c][b]) minv[c][b]=sum[c]-sum[b],p[c][b]=a;
33             if (sum[b]-sum[c]<minv[b][c]) minv[b][c]=sum[b]-sum[c],p[b][c]=a;
34         }
35     }
36     printf("%d\n",ans);
37 
38     return 0;
39 }
a100

第二題是計算幾何,鑒於這種題不是很常考,就不深研究了。簡單來說,這道題的本質就是判斷兩條線段是否相交(可以用叉積)。把題面和我自己的代碼放出來。

技術分享

技術分享
 1 #include <iostream>
 2 #include <cmath>
 3 #include <cstring>
 4 #include <cstdio>
 5 #include <cstdlib>
 6 #include <algorithm>
 7 using namespace std;
 8 int Hx,Hy,Yx,Yy;
 9 int stx,edx,sty,edy;
10 int w[2][2],m[2][2];
11 double f(int x)
12 {
13     return ((edy-sty)/(edx-stx))*x+((edx*sty)-(sty*edy))/(edx-stx);//y=kx+b。聽說用ax+by+c更好,但還沒學過,不會用。 
14 }
15 double g(int x)
16 { 
17     return ((w[2][2]-w[1][2])/(w[2][1]-w[1][1]))*x+((w[2][1]*w[1][2])-(w[1][2]*w[2][2]))/(w[2][1]-w[1][1]);  //同上 
18 }
19 int main()
20 {
21     
22     freopen("b.in","r",stdin);
23     freopen("b.out","w",stdout);
24     
25     scanf("%d%d%d%d",&Hx,&Hy,&Yx,&Yy);
26     for(int i=1;i<=2;i++) scanf("%d%d",&w[i][1],&w[i][2]);
27     for(int i=1;i<=2;i++) scanf("%d%d",&m[i][1],&m[i][2]);
28     if(Hx<=Yx) {stx=Hx;edx=Yx;sty=Hy;edy=Yy;}
29     else {stx=Yx;edx=Hx;sty=Yy;edy=Hy;}
30     bool ok=true;
31     if(w[1][1]==w[2][1])  //特判分母為零的情況 
32     {
33         if(sty>=w[1][2] && edy<=w[2][2]) ok=false;
34         else if(sty<=w[1][2] && edy>=w[2][2]) ok=false;
35     }
36     else  //判斷兩者連線是否穿過墻 
37     {
38         for(int x=stx;x<=edx;x++)
39         {
40             if(x<w[1][1] || x>w[2][1]) continue;
41             if(f(x)==g(x)) {ok=false;break;}
42         }
43     }
44     if(ok) printf("YES");
45     else printf("NO");
46     return 0;
47 }
b

Part 2:今日專題——搜索

  • 三類基本搜索:最優解問題,可行解問題,解數量問題
  • 可以用STL中的set來判重。(依靠集合的互異性)
技術分享
 1 #include <iostream>
 2 #include <set>
 3 using namespace std;
 4 struct rec{
 5     int map[4][4];
 6 };
 7 //rec&x中的&一定要寫,否則會先拷貝這兩個數再算,非常慢。加上之後不用拷貝,快一些。
 8 //加const是為了保證在做<運算前後x、y的值不變(c++的要求,必須加)
 9 //set必須要寫operator <,因為它的本質是紅黑樹 
10 bool operator <(const rec&x,const rec&y){
11     for(int a=0;a<4;a++)
12         for(int b=0;b<4;b++)
13             if(x.map[a][b]!=y.map[a][b]) return x.map[a][b]<y.map[a][b];
14         return false;
15 }
16 ser<rec> se;
set基本用法
  • 通用剪枝:
  1. 最優解問題:超過當前解退出
  2. 解數量問題:重復性利用(記憶化搜索,即之間查到過的狀態拿來直接用)
  3. 排除不可能分支(比如上面例題2,只需考慮180以內的情況)
  4. 優先走更好的分支
  5. 隨機化(降低因搜索順序TLE的可能)

隨機化多說幾句。有個dalao第二題用隨機也過了好多點:

技術分享

當然隨機化不是指這個。平時我們搜索都習慣按照一些規律,比如從左到右,從上到下等等。出題人可能會卡這種順序,導致TLE。所以我們隨機順序,就不會被卡了:

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<time.h> 
 4 int main()
 5 {
 6         int i;
 7         srand((int)time(NULL));     //每次執行種子不同,生成不同的隨機數
 8         for(i=0; i<10; i++)
 9         {
10           printf("%d\n", rand());  //因為執行太快,不到一秒鐘,10個隨機數是相通的,但是每次執行是不同的 
11         } 
12         return 0; 
13 }
  • 卡時(這是我個人覺得今天收獲最大的一點) (P.s:只用於最優解問題)

我們知道,如果一個程序的時間限制是1s,那麽當它運行到1s的時候就已經TLE了。卡時的意思就是在它時間超限之前先輸出當前答案,這樣有一定的幾率是對的(因為可能此時最優解已經找出)。

noip2012年的mayan遊戲就是這樣,從左往右同時從上往下只能得75分;從左往右從下往上可以得95分,從右往左則可以得100分。

 1 #include<ctime>
 2 
 3 void dfs(){
 4      //if(1000*(clock()-t)>=900*CLOCKS_PER_SEC) 
 5     if(clock()-t>=999)  //假設1秒分為1000個時間單位,如果>=999則代表馬上就要超時了 (當然最好不要是999,1ms退出根本不可能,寫900左右比較好) 
 6     {
 7         output solution;  //輸出解 
 8         exit(0);  //退出整個程序 
 9     }
10 } 
11 
12 int main(){
13     t=clock();  //定義初始時間 
14 }
  • 雙向BFS
  • 叠代加深搜索:1.枚舉深度;2.二分深度。

清北學堂Day1