1. 程式人生 > >第七屆藍橋杯軟體類c/c++B組國賽

第七屆藍橋杯軟體類c/c++B組國賽


一步之遙

從昏迷中醒來,小明發現自己被關在X星球的廢礦車裡。
礦車停在平直的廢棄的軌道上。
他的面前是兩個按鈕,分別寫著“F”和“B”。

小明突然記起來,這兩個按鈕可以控制礦車在軌道上前進和後退。
按F,會前進97米。按B會後退127米。
透過昏暗的燈光,小明看到自己前方1米遠正好有個監控探頭。
他必須設法使得礦車正好停在攝像頭的下方,才有機會爭取同伴的援助。
或許,通過多次操作F和B可以辦到。

礦車上的動力已經不太足,黃色的警示燈在默默閃爍...
每次進行 F 或 B 操作都會消耗一定的能量。
小明飛快地計算,至少要多少次操作,才能把礦車準確地停在前方1米遠的地方。

請填寫為了達成目標,最少需要操作的次數。

注意,需要提交的是一個整數,不要填寫任何無關內容(比如:解釋說明等)

97

這道題一眼就可以看出需要求97x+127y=1的最小解,用拓展歐幾里得,當然這是一個天空,暴力一個一個列舉也可以

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int e_gcd(int a,int b,int &x,int &y){
    if(b == 0){
        x = 1;
        y = 0;
        return a;
    }
    int q = e_gcd(b,a%b,y,x);
    y -= a / b * x;
    return q;
}
int main(){
    int x,y;
    int g = e_gcd(97,127,x,y);
    cout << x-y << endl;
    return 0;
}

湊平方數

把0~9這10個數字,分成多個組,每個組恰好是一個平方數,這是能夠辦到的。
比如:0, 36, 5948721

再比如:
1098524736
1, 25, 6390784
0, 4, 289, 15376
等等...

注意,0可以作為獨立的數字,但不能作為多位數字的開始。
分組時,必須用完所有的數字,不能重複,不能遺漏。

如果不計較小組內資料的先後順序,請問有多少種不同的分組方案?

注意:需要提交的是一個整數,不要填寫多餘內容。


300

一開始自己寫了一個搜尋,結果跑出來900多種,一定是有重複的,當時不知道怎麼存去重了,看了別人的去重,轉換成stirng然後不同數字間用一個字元‘-’分隔,然後放入set中去重

然後看了網上的搜尋,一種思路是先打表出所有符合條件的平方數,然後在這些數中用深搜進行分組並判斷條件。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const ll INF = 9876543210;
ll pow2[1000];
int top = 0,ans = 0;
bool check(string num){
    int vis[10] = {0};
    int len = num.length();
    for(int i = 0; i < len; i++){
        int tmp = num[i] - '0';
        vis[tmp]++;
        if(vis[tmp] > 1) return false;
    }
    return true;
}
string change(ll num){
    string s = "";
    if(num == 0) s += "0";
    while(num){
        char ch[] = {num % 10 + '0','\0'};
        s.insert(0,ch);
        num /= 10;
    }
    return s;
}
void init(){
    for(ll i = 0; i * i <= INF; i++){
        ll tmp = i * i;
        if(check(change(tmp)))
            pow2[top++] = tmp;
    }
}
void dfs(int start,string num){
    int len = num.length();
    if(len > 10 || !check(num)) return;
    if(len == 10 && check(num)){
        ans++;
        return;
    }
    for(int i = start; i < top; i++){//下次遞迴是找這個i之後的數保證了不會重複,就像兩重迴圈列舉內層迴圈從外層迴圈列舉的數開始而不是重新開始
        dfs(i+1,num+change(pow2[i]));
    }
}
int main(){
    init();
    dfs(0,"");
    cout << ans << endl;
    return 0;
}



棋子換位

有n個棋子A,n個棋子B,在棋盤上排成一行。
它們中間隔著一個空位,用“.”表示,比如:

AAA.BBB

現在需要所有的A棋子和B棋子交換位置。
移動棋子的規則是:
1. A棋子只能往右邊移動,B棋子只能往左邊移動。
2. 每個棋子可以移動到相鄰的空位。
3. 每個棋子可以跳過相異的一個棋子落入空位(A跳過B或者B跳過A)。

AAA.BBB 可以走法:
移動A ==> AA.ABBB
移動B ==> AAAB.BB

跳走的例子:
AA.ABBB ==> AABA.BB

以下的程式完成了AB換位的功能,請仔細閱讀分析原始碼,填寫劃線部分缺失的內容。



#include <stdio.h>
#include <string.h>

void move(char* data, int from, int to)
{
    data[to] = data[from];
    data[from] = '.';
}

int valid(char* data, int k)
{
    if(k<0 || k>=strlen(data)) return 0;
    return 1;
}
    
void f(char* data)
{
    int i;
    int tag;
    int dd = 0; // 移動方向
    
    while(1){
        tag = 0;
        for(i=0; i<strlen(data); i++){
            if(data[i]=='.') continue;
            if(data[i]=='A') dd = 1;
            if(data[i]=='B') dd = -1;
            
            if(valid(data, i+dd) && valid(data,i+dd+dd)
            && data[i+dd]!=data[i] && data[i+dd+dd]=='.'){
            //如果能跳...
                move(data, i, i+dd+dd);
                printf("%s\n", data);
                tag = 1;
                break;
            }
        }
        
        if(tag) continue;
        
        for(i=0; i<strlen(data); i++){
            if(data[i]=='.') continue;
            if(data[i]=='A') dd = 1;
            if(data[i]=='B') dd = -1;            
                
            if(valid(data, i+dd) && data[i+dd]=='.'){
            // 如果能移動...
                if( ______________________ ) continue;  //填空位置
                move(data, i, i+dd);
                printf("%s\n", data);
                tag = 1;
                break;
            }
        }
        
        if(tag==0) break;                    
    }
}
    
int main()
{
    char data[] = "AAA.BBB";    
    f(data);
    return 0;
}


注意:只提交劃線部分缺少的程式碼,不要複製已有程式碼或填寫任何多餘內容。


data[i] == 'A' && i != 0 && i <= strlen(data)-3  && data[i-1] == 'B' && data[i+2] == 'B'

這道題我的答案程式碼和網上不一樣,但是跑出來結果是一樣的,因為這個程式碼是固定的,只要結果是對的就可以,我一開始沒有思路,因為眼看一直沒看出來到底是怎麼交換的,然後就寫了廣搜的程式,並還原了路徑,然後根據打印出的那個路徑,確定的這個程式碼,其實我這樣寫不具有普適性,對於這個樣例是可以的。

#include <stdio.h>
#include <string.h>

void move(char* data, int from, int to)
{
	data[to] = data[from];
	data[from] = '.';
}

int valid(char* data, int k)
{
	if(k<0 || k>=strlen(data)) return 0;
	return 1;
}

void f(char* data)
{
	int i;
	int tag;
	int dd = 0; // 移動方向

	while(1){
		tag = 0;
		for(i=0; i<strlen(data); i++){
			if(data[i]=='.') continue;
			if(data[i]=='A') dd = 1;
			if(data[i]=='B') dd = -1;

			if(valid(data, i+dd) && valid(data,i+dd+dd)
			&& data[i+dd]!=data[i] && data[i+dd+dd]=='.'){
			//如果能跳...
				move(data, i, i+dd+dd);
				printf("%s\n", data);
				tag = 1;
				break;
			}
		}

		if(tag) continue;

		for(i=0; i<strlen(data); i++){
			if(data[i]=='.') continue;
			if(data[i]=='A') dd = 1;
			if(data[i]=='B') dd = -1;

			if(valid(data, i+dd) && data[i+dd]=='.'){
			// 如果能移動...
				if(data[i] == 'A' && i != 0 && i <= strlen(data)-3  && data[i-1] == 'B' && data[i+2] == 'B') continue;  //填空位置
				move(data, i, i+dd);
				printf("%s\n", data);
				tag = 1;
				break;
			}
		}

		if(tag==0) break;
	}
}

int main()
{
	char data[] = "AAA.BBB";
	f(data);
	return 0;
}


//測試程式,根據以下程式得到的答案
//#include <iostream>
//#include <cstring>
//#include <cstdio>
//#include <string>
//#include <vector>
//#include <queue>
//#include <map>
//using namespace std;
//
//string result = "BBB.AAA";
//struct node{
//    string s;
//    vector<string> path;
//}a,nexta;
//map<string,int>vis;
//void exchange(string data, int from, int to)
//{
//	data[to] = data[from];
//	data[from] = '.';
//}
//
//int valid(string data, int k)
//{
//	if(k < 0 || k >= data.length()) return 0;
//	return 1;
//}
//
//void f()
//{
//   a.s = "AAA.BBB";
//   a.path.push_back(a.s);
//   vis[a.s] = 1;
//   queue<node>q;
//   q.push(a);
//   while(!q.empty()){
//      a = q.front();
//      //cout << a.s << endl;
//      q.pop();
//      if(a.s == result){
//         for(int i = 0; i < a.path.size(); i++){
//            cout << a.path[i] << endl;
//         }
//         return;
//      }
//      int dd;
//      for(int i = 0; i < a.s.length(); i++){
//			if(a.s[i]=='.') continue;
//			if(a.s[i]=='A') dd = 1;
//			if(a.s[i]=='B') dd = -1;
//
//			if(valid(a.s, i+dd) && valid(a.s,i+dd+dd)
//			&& a.s[i+dd] != a.s[i] && a.s[i+dd+dd] == '.'){
//			//如果能跳...
//			   //cout << "??" << endl;
//			    nexta = a;
//				nexta.s[i+dd+dd] = nexta.s[i];
//				nexta.s[i] = '.';
//				if(!vis.count(nexta.s)){
//                    vis[nexta.s] = 1;
//                    nexta.path.push_back(nexta.s);
//                    q.push(nexta);
//				}
//			}
//		}
//      for(int i = 0; i < a.s.length(); i++){
//			if(a.s[i]=='.') continue;
//			if(a.s[i]=='A') dd = 1;
//			if(a.s[i]=='B') dd = -1;
//
//			if(valid(a.s, i+dd) && a.s[i+dd]=='.'){
//			//如果能移動...
//			   // cout << "???" << endl;
//			    nexta = a;
//				nexta.s[i+dd] = nexta.s[i];
//				nexta.s[i] = '.';
//				if(!vis.count(nexta.s)){
//                    vis[nexta.s] = 1;
//                    nexta.path.push_back(nexta.s);
//                    q.push(nexta);
//				}
//			}
//		}
//   }
//
//}
//
//int main()
//{
//	f();
//	return 0;
//}

機器人塔

X星球的機器人表演拉拉隊有兩種服裝,A和B。
他們這次表演的是搭機器人塔。

類似:

     A
    B B
   A B A
  A A B B
 B B B A B
A B A B B A

隊內的組塔規則是:
 
  A 只能站在 AA 或 BB 的肩上。
  B 只能站在 AB 或 BA 的肩上。

你的任務是幫助拉拉隊計算一下,在給定A與B的人數時,可以組成多少種花樣的塔。

輸入一行兩個整數 M 和 N,空格分開(0<M,N<500),分別表示A、B的人數,保證人數合理性。

要求輸出一個整數,表示可以產生的花樣種數。

例如:
使用者輸入:
1 2

程式應該輸出:
3


再例如:
使用者輸入:
3 3

程式應該輸出:
4

資源約定:
峰值記憶體消耗 < 256M
CPU消耗  < 1000ms

請嚴格按要求輸出,不要畫蛇添足地列印類似:“請您輸入...” 的多餘內容。

所有程式碼放在同一個原始檔中,除錯通過後,拷貝提交該原始碼。

注意: main函式需要返回0
注意: 只使用ANSI C/ANSI C++ 標準,不要呼叫依賴於編譯環境或作業系統的特殊函式。
注意: 所有依賴的函式必須明確地在原始檔中 #include <xxx>, 不能通過工程設定而省略常用標頭檔案。

提交時,注意選擇所期望的編譯器型別。

這道題得全分是不可能的,一開始看到網上有用深搜暴力列舉底層的所有情況,然後底層確定了實際上整個塔就確定了,然後還原出整個塔統計AB個數,和所給AB個數比較看是否符合。這樣時間複雜度是比較大的,這樣寫大約是得20%的分數吧

然後觀察這個遞推過程發現:

每層第i個和上層第i個有異或運算A^B=B ,A^A=A,B^B=A,B^A=B。   好了,本題基本結束。

              map[line][i]=map[line][i-1]^map[line-1][i-1];

也就是要確定一個位置的時候是根據它的左邊和左上是什麼異或得到的,讓A=0,B=1就行了。這樣就能優化很多,大約可以過一半多的資料吧

code:

//優化
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int ans;
int a[1100];
int mp[50][50];
void init(){
    int sum = 0;
    for(int i = 1; i <= 50; i++){
        sum += i;
        a[i] = sum;
    }
}
void DFS(int line,int reA,int reB,int n){
    //line表示當前行,reA表示可用A個數,reB可用B個數,n是總的層數
    int A = reA;
    int B = reB;
    if(reA < 0 || reB < 0)
        return;
    if(line == n && A == 0 && B == 0){
        ans++;
        return;
    }
    reA = A;
    reB = B;
    mp[line][0] = 0;//第一個只能列舉,因為左邊沒有
    reA--;
    for(int i = 1; i <= line; i++){//因為遞迴函式中line是從0開始的所有實際line行應有line+1個,
                                   //第一個枚舉了,剩下個line個根據遞推式確定
        mp[line][i] = mp[line][i-1] ^ mp[line-1][i-1];
        if(mp[line][i] == 0)
            reA--;
        else
            reB--;
    }
    DFS(line+1,reA,reB,n);

    reA = A;
    reB = B;
    mp[line][0] = 1;
    reB--;
    for(int i = 1; i <= line; i++){
        mp[line][i] = mp[line][i-1] ^ mp[line-1][i-1];
        if(mp[line][i] == 0)
            reA--;
        else
            reB--;
    }
    DFS(line+1,reA,reB,n);
}
int main(){
    init();
    int x,y,n;
    while(scanf("%d%d",&x,&y) != EOF){
        ans = 0;
        for(int i = 0; i <= 50; i++){
            if(a[i] == x+y){
                n = i;
                break;
            }
        }
        DFS(0,x,y,n);
        printf("%d\n",ans);
    }
    return 0;
}

//一般搜尋法,先深搜列舉確定最底層,只要最底層確定整個塔就確定了,然後統計ab個數比較即可但是速度慢
//#include <iostream>
//#include <cstring>
//#include <cstdio>
//using namespace std;
//int a[1100];
//char s1[1000],s2[1000];
//int vis[1000];
//int n,m,k,ans;
//void init(){
//    int sum = 0;
//    for(int i = 1; i <= 50; i++){
//        sum += i;
//        a[i] = sum;
//    }
//}
//int judge(int t){//由最底層向上推出整個塔,統計出ab個數,看是否相同
//    strcpy(s2+1,s1+1);
//    int suma = 0,sumb = 0;
//    while(t){
//        for(int i = 1; i <= t; i++){
//            if(s2[i] == 'A') suma++;
//            if(s2[i] == 'B') sumb++;
//        }
//        for(int i = 1; i <= t-1; i++){
//            if(s2[i] == s2[i+1])
//                s2[i] = 'A';
//            else
//                s2[i] = 'B';
//        }
//        t--;
//    }
//    if(suma == m && sumb == n)
//        return 1;
//    return 0;
//}
//void dfs(int cnt){//dfs列舉最底層
//    if(cnt == k+1){
//        if(judge(k))
//            ans++;
//        return;
//    }
//    if(!vis[cnt]){
//        vis[cnt] = 1;
//        s1[cnt] = 'A';
//        dfs(cnt+1);
//        s1[cnt] = 'B';
//        dfs(cnt+1);
//        vis[cnt] = 0;
//    }
//}
//int main(){
//    init();
//    scanf("%d%d",&m,&n);
//    ans = 0;
//    for(int i = 0; i <= 50; i++){
//        if(a[i] == n+m){
//            k = i;
//            break;
//        }
//    }
//    dfs(1);
//    printf("%d\n",ans);
//    return 0;
//}

廣場舞

LQ市的市民廣場是一個多邊形,廣場上鋪滿了大理石的地板磚。

地板磚鋪得方方正正,就像座標軸紙一樣。
以某四塊磚相接的點為原點,地板磚的兩條邊為兩個正方向,一塊磚的邊長為橫縱座標的單位長度,則所有橫縱座標都為整數的點都是四塊磚的交點(如果在廣場內)。

廣場的磚單調無趣,卻給跳廣場舞的市民們提供了絕佳的參照物。每天傍晚,都會有大批市民前來跳舞。
舞者每次都會選一塊完整的磚來跳舞,兩個人不會選擇同一塊磚,如果一塊磚在廣場邊上導致缺角或者邊不完整,則沒人會選這塊磚。
(廣場形狀的例子參考【圖1.png】)

現在,告訴你廣場的形狀,請幫LQ市的市長計算一下,同一時刻最多有多少市民可以在廣場跳舞。



【輸入格式】
輸入的第一行包含一個整數n,表示廣場是n邊形的(因此有n個頂點)。
接下來n行,每行兩個整數,依次表示n邊形每個頂點的座標(也就是說廣場邊緣拐彎的地方都在磚的頂角上。資料保證廣場是一個簡單多邊形。

【輸出格式】
輸出一個整數,表示最多有多少市民可以在廣場跳舞。

【樣例輸入】
5
3 3
6 4
4 1
1 -1
0 4

【樣例輸出】
7

【樣例說明】
廣場如圖1.png所示,一共有7塊完整的地板磚,因此最多能有7位市民一起跳舞。

【資料規模與約定】
對於30%的資料,n不超過100,橫縱座標的絕對值均不超過100。
對於50%的資料,n不超過1000,橫縱座標的絕對值均不超過1000。
對於100%的資料,n不超過1000,橫縱座標的絕對值均不超過100000000(一億)。


資源約定:
峰值記憶體消耗 < 256M
CPU消耗  < 1000ms

請嚴格按要求輸出,不要畫蛇添足地列印類似:“請您輸入...” 的多餘內容。

所有程式碼放在同一個原始檔中,除錯通過後,拷貝提交該原始碼。

注意: main函式需要返回0
注意: 只使用ANSI C/ANSI C++ 標準,不要呼叫依賴於編譯環境或作業系統的特殊函式。
注意: 所有依賴的函式必須明確地在原始檔中 #include <xxx>, 不能通過工程設定而省略常用標頭檔案。

提交時,注意選擇所期望的編譯器型別。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
//待輸入點的座標
double vx[100],vy[100];
int num;//頂點個數

//關鍵函式,判斷點是否在凸多邊形內
int isIn(double testx,double testy){
    for(int k = 0; k < num; k++){
        if(testx == vx[k] && testy == vy[k])//這個點恰好是頂點,當然算在裡面
            return 1;
    }

    for(int i = 0; i < num; i++){
        double dy = vy[(i+1)%num] - vy[i];
        double dx = vx[(i+1)%num] - vx[i];
        if(dx == 0.0) continue;
        double k = dy / dx;//算出多邊形某條面的斜率
        if((vy[i] - testy) / (vx[i]-testx) == k && (testx > vx[i]) != (testx > vx[(i+1)%num]))
            return 1;
        //上面的判斷什麼意思呢?我們來分析一下,首先發現這個測試點和邊上一點的斜率和這條邊斜率相同說明什麼
        //這保證了這個點必定在這條線段上或者線段的延長線上,那麼只有當線上段上才是在圖形內部
        //所以只需要讓測試點的x橫座標線上段連端點的x橫座標之間即可,上面寫法的意思就是不能在同側
        //測試點x值不能同時在兩端點左側,也不能同時在兩端右側,而在某一個點上的情況之前就已經判斷了
    }

    //上面for迴圈判斷了在邊上的情況,我們用到了斜率,但是有一種斜率是求不出來的,就是和y軸平行的時候下面就是對這種情況的判斷
    for(int i = 0; i < num; i++){
        if(testx == vx[i] && vx[i] == vx[(i+1)%num] && (testy > vy[i]) != (testy > vy[(i+1)%num]))
            return 1;
    //三點x橫座標相等,測試點的y值在兩端點之間
    }

    //以上點在邊上的情況就判斷完了,下面不在邊上的情況我們就利用水平射線相交點判別法判斷
    int flag = 0;//記錄交點個數的奇偶性
    for(int i = 0,j = num-1; i < num; j = i++){
    if((vy[i] > testy) != (vy[j] > testy) && (testx < (vx[j] - vx[i]) * (testy - vy[i]) / (vy[j] - vy[i]) + vx[i]))
            flag = !flag;
    }
    return flag;
    //具體解釋見上面部落格的解釋
}

bool IsSquare(double x,double y){
    //判斷是否可以構成小正方形及判斷它本身和它的左,下,左下點是否在凸多邊形內
    if(isIn(x,y)){
        if(!isIn(x+1.0,y)) return false;
        if(!isIn(x,y-1.0)) return false;
        if(!isIn(x+1.0,y-1.0)) return false;
        return true;
    }
    else
        return false;
}

int main(){
    int ans = 0;
    scanf("%d",&num);
    for(int i = 0; i < num; i++){
        scanf("%lf%lf",&vx[i],&vy[i]);
    }
    //獲得圖形的邊界
    double xmax = vx[0],xmin = vx[0];
    double ymax = vy[0],ymin = vy[0];
    for(int i = 0; i < num; i++){
        xmax = max(xmax,vx[i]);
        xmin = min(xmin,vx[i]);
        ymax = max(ymax,vy[i]);
        ymin = min(ymin,vy[i]);

    }

    //掃描範圍內的每個點
    for(double x = xmin; x <= xmax; x++){
        for(double y = ymax; y >= ymin; y--){
            //判斷是否可以構成一個完整的小正方
            if(IsSquare(x,y)) ans++;
        }
    }
    printf("%d\n",ans);
    return 0;
}
/*
7
1 2
1 4
3 7
4 4
6 6
6 2
2 1
ans:12

6
1 3
2 4
3 3
4 4
5 3
3 -1
ans:4
*/ 


生成樹計數

給定一個 n*m 的格點圖,包含 n 行 m 列共 n*m 個頂點,相鄰的頂點之間有一條邊。
【圖1.png】給出了一個3*4的格點圖的例子。


如果在圖中刪除部分頂點和其相鄰的邊,如上圖刪除第2行第3列和第3行第1列的頂點後,如【圖2.png】所示。

圖的生成樹指包含圖中的所有頂點和其中的一部分邊,使得任意兩個頂點之間都有由邊構成的唯一路徑。如果兩個生成樹包含有不同的邊即被認為不同,則上圖中共有31種不同的生成樹,其中a邊不選有10種,a邊選有21種。
給出格點圖中保留的頂點的資訊,請計算該圖一共有多少種不同的生成樹。

【輸入格式】
輸入的第一行包含兩個整數n, m,用空格分隔,表示格點圖的行數和列數。
接下來n行,每行m個字母(中間沒有分隔字元),每個字母必然是大寫E或大寫N,E表示對應的頂點存在,N表示對應的頂點不存在。保證存在至少一個頂點。

【輸出格式】
輸出一行,包含一個整數,表示生成樹的個數。答案可能很大,你只需要計算答案除以1000000007的餘數即可。

【樣例輸入】
3 4
EEEE
EENE
NEEE

【樣例輸出】
31

【資料規模與約定】
對於10%的資料,1<=n<=2。
對於30%的資料,1<=n<=3。
對於40%的資料,1<=n<=4。
對於50%的資料,1<=n<=5。
另有20%的資料,1<=n*m<=12。
另有10%的資料,1<=m<=15。
對於100%的資料,1<=n<=6,1<=m<=100000。

資源約定:
峰值記憶體消耗 < 256M
CPU消耗  < 4500ms

請嚴格按要求輸出,不要畫蛇添足地列印類似:“請您輸入...” 的多餘內容。

所有程式碼放在同一個原始檔中,除錯通過後,拷貝提交該原始碼。

注意: main函式需要返回0
注意: 只使用ANSI C/ANSI C++ 標準,不要呼叫依賴於編譯環境或作業系統的特殊函式。
注意: 所有依賴的函式必須明確地在原始檔中 #include <xxx>, 不能通過工程設定而省略常用標頭檔案。

提交時,注意選擇所期望的編譯器型別。

不會。。不打算研究了,比賽肯定也不會