[藍橋杯解題報告]第六屆藍橋杯大賽省賽2015(軟件類)真題C++A組 Apare_xzc

藍橋杯第六屆(2015年)省賽軟件類C++A組解題報告

Apare_xzc 2020/3/12


在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述


1. 方程整數解

在這裏插入圖片描述

分析:

        dfs即可。0^2 + 10^2 +30^2 = 1000

代碼:

#include <bits/stdc++.h>
using namespace std;
int main() {
	for(int i=0; i<=40; ++i) 
		for(int j=i; j<=40; ++j) 
			for(int k=j; k<=40; ++k) 
				if(i*i+j*j+k*k==1000) {
					cout<<i<<" "<<j<<" "<<k<<endl;
					return 0; 
				}
	return 0;
}

這題我找到的解是0*0 + 10*10 + 30*30 = 1000,所以我的答案是0

在這裏插入圖片描述


2. 星系炸彈

在這裏插入圖片描述

分析:

        這個題可以直接數出來。到2014年11月30日,過了30 - 9 = 21(天),到2014年12月31日,過了21 + 31 = 52(天),那麼到2016年12月31日,過了52 + 365 + 366 = 783(天),還有1000 - 783 = 217(天)。大概217天是7個月,2017年前7個月的天數之和爲:31 + 28 + 31 + 30 + 31 + 30 + 31 = 212(天), 那麼還有217 - 212 = 5(天),答案就是2017-08-05 。
        當然我們也可以寫程序。寫一個函數,給定當前日期和增加的天數,返回若干天以後的日期。代碼如下。

代碼:

#include <bits/stdc++.h>
using namespace std;
int md[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
bool Leap(int y) {
	if(y%400==0||y%4==0&&y%100) return 1;
	return 0;
}
int getMonthDay(int y,int m) {
	if(m!=2) return md[m];
	return md[m]+Leap(y);
}
void add(int &y,int &m,int &d,int add) {
	int monthday = getMonthDay(y,m),yday;
	if(d+add<=monthday) {
		d += add; return;
	}
	add -= monthday-d;
	d = monthday;
	for(int i=m+1; i<=12; ++i) {
		monthday = getMonthDay(y,i);
		if(add>=monthday) m = i, d = monthday, add -= monthday;
		else break;
	}
	if(add==0) return;
	if(m<12) {
		++m; d = add; return;
	}
	for(int i=y+1;; ++i) {
		yday = 365+Leap(i);
		if(add>=yday) add -= yday,++y;
		else break;
	}
	if(add==0) return;
	++y;
	if(add<=31) {
		m = 1; d = add; return;
	}
	for(int i=1; i<=12; ++i) {
		monthday = getMonthDay(y,i);
		if(add>=monthday) m = i, d = monthday, add-=monthday;
		else break;
	}
	if(add==0) return;
	if(m==12) m = 0;
	++m; d = add;
	return;
}
int main() {
	int y = 2014, m = 11, d = 9, i=1000;
	add(y,m,d,i);
	printf("%04d-%02d-%02d\n",y,m,d);
	return 0;
}

答案爲:2017-08-05

在這裏插入圖片描述


3. 奇妙的數字

在這裏插入圖片描述

分析:

        因爲立方在十位數之內,所以這個數並不大,暴力找就好了。

代碼:

#include <bits/stdc++.h>
using namespace std;
bool f(int x) {
	int cnt[10] = {0};
	long long y = x*x;
	long long z = y*x;
	map<int,int> mp;
	while(y) cnt[y%10]++,y/=10;
	while(z) cnt[z%10]++,z/=10;
	bool ok = true;
	for(int i=0; i<10; ++i) {
		if(cnt[i]!=1) return false;
	}
	cout<<x<<endl;
	return true;
}
int main(void) {
	for(int i=1;; ++i) 
		if(f(i)) break;
	return 0;
}

答案爲:69

在這裏插入圖片描述


4. 格子中輸出

在這裏插入圖片描述

題目代碼如下:

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

void StringInGrid(int width, int height, const char* s)
{
	int i,k;
	char buf[1000];
	strcpy(buf, s);
	if(strlen(s)>width-2) buf[width-2]=0;
	
	printf("+");
	for(i=0;i<width-2;i++) printf("-");
	printf("+\n");
	
	for(k=1; k<(height-1)/2;k++){
		printf("|");
		for(i=0;i<width-2;i++) printf(" ");
		printf("|\n");
	}
	
	printf("|");
	
	printf("%*s%s%*s",_____________________________________________);  //填空
	          
	printf("|\n");
	
	for(k=(height-1)/2+1; k<height-1; k++){
		printf("|");
		for(i=0;i<width-2;i++) printf(" ");
		printf("|\n");
	}	
	
	printf("+");
	for(i=0;i<width-2;i++) printf("-");
	printf("+\n");	
}

int main()
{
	StringInGrid(20,6,"abcd1234");
	return 0;
}

分析:

        題目要求就是居中顯示一行字符串。%*s這個佔位符需要兩個參數,第一個參數是(unsigned) int類型的x,代表輸出字符串應該站x個字符位置,第二個參數是字符串的頭指針。
        我們知道,寬度爲width,前後要有豎線,那麼剩下width-2個位置,字符串的長度爲strlen(s),所以左邊和右邊空格數爲:(width-2-strlen(s))/2, 化簡後爲:(width - strlen(s)) / 2 - 1

答案爲:(width-strlen(s))/2-1," ",buf,(width-strlen(s))/2-1," "

在這裏插入圖片描述


5. 9數組分組

在這裏插入圖片描述

題目代碼如下:

#include <stdio.h>

void test(int x[])
{
	int a = x[0]*1000 + x[1]*100 + x[2]*10 + x[3];
	int b = x[4]*10000 + x[5]*1000 + x[6]*100 + x[7]*10 + x[8];
	
	if(a*3==b) printf("%d / %d\n", a, b);
}

void f(int x[], int k)
{
	int i,t;
	if(k>=9){
		test(x);
		return;
	}
	
	for(i=k; i<9; i++){
		{t=x[k]; x[k]=x[i]; x[i]=t;}
		f(x,k+1);
		_____________________________________________ // 填空處
	}
}
	
int main()
{
	int x[] = {1,2,3,4,5,6,7,8,9};
	f(x,0);	
	return 0;
}

分析:

        這是一個很好的通過交換生成全排列的做法,這樣解決了遞歸可能的爆棧問題。
        從代碼可以看出,每次將第x個數和後面的分別角換,生成新的排列。

答案:{t=x[k]; x[k]=x[i]; x[i]=t;}

在這裏插入圖片描述


6. 牌型種數

在這裏插入圖片描述

分析:

        52張沒有大小王的牌中選13張,問有多少種不同的結果。每個點數的牌有4張。我們也不用容斥了,直接爆搜就好了。每個點數可以取0-4張,最後每個點數選的牌個數之和爲13。

代碼:

#include <bits/stdc++.h>
using namespace std;
long long ans = 0;
int r[13];
void dfs(int x,int sum) {
	if(x==13) {
		if(sum==13) ++ans; 
		return;
	}
	for(int i=0;i<=4;++i) {
		if(i+sum<=13) dfs(x+1,sum+i);
	}
}
int main(void) {
	dfs(0,0);
	cout<<ans<<endl;
	return 0;
} 

答案:3598180

在這裏插入圖片描述


7. 手鍊樣式

在這裏插入圖片描述

分析:

        一看就是一個Polya定理。數字這麼小,直接搜吧。dfs一個長度爲12,由RWY中的字符組成的字符串,判斷之前是否有等價的狀態。我們知道,手鍊轉動相當於字符串循環移位,手鍊翻轉相當於字符串翻轉。所以,沒得到一個字符串,將它循環移位12次,加上翻轉,24個狀態都判斷一遍,如果以前沒有出現過這個狀態,就計數。可以用map,也可以字符串哈希更快。

代碼:

#include <bits/stdc++.h>
using namespace std;
char r[15] = "ABCDEFGHIJKL";
int ans = 0;
map<string,int> mp;
string toLeft(int d) {
	string s;
	for(int i=d;i<=d+11;++i)
	{
		s += r[i%12];
	}
	//cout<<s<<endl;
	return s;
}
void cal()
{
	r[12] = '\0';
	string str; 
	bool ok = true;
	for(int i=1;i<=12;++i)
	{
		str = toLeft(i);
		if(mp.count(str)) {
			ok = false;break;
		} 
		reverse(str.begin(),str.end());
		if(mp.count(str)) {
			ok = false;break;
		}
	}
	if(ok) {
		mp[r] = 1;++ans;
	}
}
void dfs(int x,int cr,int cw,int cy)
{
	if(x==12){
		cal();
		return;
	}
	if(cr>0) {
		r[x] = 'R';
		dfs(x+1,cr-1,cw,cy);
	} if(cw>0) {
		r[x] = 'W';
		dfs(x+1,cr,cw-1,cy);
	} if(cy>0) {
		r[x] = 'Y';
		dfs(x+1,cr,cw,cy-1);
	}
}
int main()
{
	dfs(0,3,4,5);
	cout<<ans<<endl;
	return 0;
} 

答案:1170

在這裏插入圖片描述


8. 飲料換購

在這裏插入圖片描述

分析:

         這個不能賒賬,也不能借,和我們做的小學奧數題不同,但貼近實際規則。直接除以3,計數,直到小於3。

代碼:

#include <bits/stdc++.h>
using namespace std;
int main() {
	int n;
	while(cin>>n) {
		int ans = n;
		int add = n;
		while(add>=3) {
			ans += add/3;
			add = add/3+add%3;
		}
		cout<<ans<<endl;
	}
	return 0;
}

在這裏插入圖片描述


9. 壘骰子

在這裏插入圖片描述
在這裏插入圖片描述

分析:

      每個骰子向上有6中選擇,確定了向上的數字後,可以水平旋轉,有4中選擇。我們設dp[x][y]爲壘x個骰子,且第x個骰子朝上的數字爲y的個數。那麼,我們就可以由dp[x-1][t]來向dp[x][y]遞推。只要滿足y對面的數字y‘和t不排斥即可。所以,遞推的僞代碼可以寫出來:

for y in range(1,6+1):
	dp[1][y] = 1 #第一個骰子朝上的數字爲y的情況都爲1種
for x in range(2,n+1): #防止第x個骰子
	for y in range(1,6+1):
		for t in range(1,6+1):
				dp[x][y] += can[op(y)][t] * dp[x-1][t]
sum = 0
for y in range(1,6+1):
	sum += dp[n][y]
sum = sum * pow(4,n)

我們可以用係數矩陣來描述這個關係:

| dp[x][1] |           |1  1  1  1  1  1|     |dp[x-1][1]|
| dp[x][2] |           |1  1  1  1  1  1|     |dp[x-1][2]|
| dp[x][3] |     =     |1  1  1  1  1  1|  *  |dp[x-1][3]|
| dp[x][4] |           |1  0  1  1  1  1|     |dp[x-1][4]|
| dp[x][5] |           |0  1  1  1  1  1|     |dp[x-1][5]|
| dp[x][6] |           |1  1  1  1  1  1|     |dp[x-1][6]|

於是乎,我們求出了係數矩陣,就可以矩陣快速冪加速dp了。
對於一對排斥的數字x,y,不能貼着。就是說,如果下層的x朝上,那麼上層的op(y)就不餓能朝上,所以can[op(y)][x] = 0, 同理,can[op(x)][y] = 0
我們知道,dp[1][1],dp[1][2], … dp[1][6]均爲1,那麼用係數矩陣(66)的n-1次方左乘這個dp[1]列向量(61) 的到的新的列向量(6*1)即爲dp[n],我們將dp[n]的六個值相加,然後乘以4^n即可。注意答案對1E9+7取模。

代碼:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int mod = 1e9+7;
LL a[6][6];
LL fast_pow(LL a,LL b)
{
	if(a==0) return 0;
	LL ans = 1;
	while(b) {
		if(b&1) ans = ans*a%mod;
		a = a*a%mod;
		b>>=1;
	}
	return ans;
}
void Mul(LL a[6][6],LL b[6][6]) {
	LL c[6][6] = {0};
	for(int i=0;i<6;++i)
		for(int j=0;j<6;++j)
			for(int k=0;k<6;++k)
				c[i][j] = (c[i][j]+a[i][k]*b[k][j]%mod)%mod;
	for(int i=0;i<6;++i)
		for(int j=0;j<6;++j)
			a[i][j] = c[i][j]; 
}
void Mat_fast_pow(LL a[6][6],int n) {
	LL p = fast_pow(4ll,n); --n;
	LL ans[6][6] = {0};
	for(int i=0;i<6;++i) ans[i][i] = 1;
	while(n) {
		if(n&1) Mul(ans,a);
		Mul(a,a);
		n>>=1;
	}
	LL res = 0;
	for(int i=0;i<6;++i)
		for(int j=0;j<6;++j)
			res = (res+ans[i][j])%mod;
	res = res * p % mod;
	cout<<res<<endl;
}
int op(int x) {
	return (x+3)%6;
} 
int main()
{
	int n,m,x,y;
	while(cin>>n>>m)
	{
		for(int i=0;i<6;++i)
			for(int j=0;j<6;++j)
				a[i][j] = 1;
		while(m--){
			cin>>x>>y; 
			--x,--y;
			a[op(x)][y] = a[op(y)][x] = 0;
		}
		Mat_fast_pow(a,n);
	}
	return 0;
}

在這裏插入圖片描述


10. 災後重建

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

樣例輸入:

7 10 4
1 3 10
2 6 9
4 1 5
3 7 4
3 6 9
1 5 8
2 7 4
3 2 10
1 7 6
7 6 9
1 7 1 0
1 7 3 1
2 5 1 0
3 7 2 1

樣例輸出:

9
6
8
8

分析:

        就是說,一個圖,所有的邊都被破壞,現在要讓其中的某些節點相互連通,讓我們連一些邊,要求最長的邊最小化。求最長的邊的長度。
        我們可以貪心地想,將邊按長度(維修時間)從小大排序。然後從小到大連邊,知道選出的所有點都互相連通就停止,那麼最後連的那一條邊即爲答案。判斷圖的連通性可以用並查集。這也就是最小生成樹的Kruskal算法的思想。
        由於並查集check每次初始化都要O(n), 我們不如二分答案。二分加幾條邊可以滿足連通。
        注意有重邊和自環,我們可以用map<pair<int,int>,int>處理輸入的邊

代碼:

#include <bits/stdc++.h>
#define MP make_pair
#define pb push_back
using namespace std;
const int maxn = 5E4+10;
const int maxm = 2E5+10;
struct Node{
	int to,Next,d;
}node[maxm*2];
int head[maxn],tot;
void init(int n) {
	memset(head,-1,sizeof(head));
	tot = 0;
}
void addedge(int u,int v,int d) {
	node[tot].to = v;
	node[tot].d = d;
	node[tot].Next = head[u];
	head[u] = tot++;
}
struct E{
	int u,v,d;
	E(int _u=0,int _v=0,int _d=0):u(_u),v(_v),d(_d){} 
	bool operator < (const E& rhs)const {
		return d < rhs.d;
	}
}edge[maxm];
int pre[maxn];
int Find(int x) {
	return x==pre[x]?x:pre[x]=Find(pre[x]);
}
void join(int x,int y) {
	int fx = Find(x), fy = Find(y);
	if(fx==fy) return;
	pre[fy] = fx; 
}
int main()
{
	int m,n,q,x,y,p,L,R,k,c;
	scanf("%d%d%d",&n,&m,&q);
	init(n);
	map<pair<int,int>,int> mpe;
	map<pair<int,int>,int>::iterator it;
	pair<int,int> pr;
	for(int i=0;i<m;++i) {
		scanf("%d%d%d",&x,&y,&p);
		if(x==y) continue;
		if(x>y) swap(x,y);
		pr = make_pair(x,y);
		if(!mpe.count(pr)||mpe[pr]>p) mpe[pr] = p;
	} 
	m = 0;
	for(it=mpe.begin();it!=mpe.end();++it){
		pr = it->first; x = pr.first; y = pr.second;
		p = it->second;
		addedge(x,y,p);
		addedge(y,x,p);
		edge[++m] = E(x,y,p);
	} 
	sort(edge+1,edge+m+1);
	for(int ca=1;ca<=q;++ca) {
		scanf("%d%d%d%d",&L,&R,&k,&c);
		int fir = L;
		for(;fir<=R;++fir)
			if(fir%k==c) break;
		map<int,int> mp;
		vector<int> v;
		int sz = 0,fa;
		while(fir<=R)
			mp[fir] = 1,v.pb(fir),fir+=k, ++sz;
		int left = 0, right = m, mid;
		while(right-left>1) {
			mid = (left+right)>>1;
			for(int i=1;i<=n;++i) pre[i] = i;
			for(int i=1;i<=mid;++i) {
				join(edge[i].u,edge[i].v); 
			}
			fa = Find(v[0]);
			bool ok = true;
			for(int i=1;i<sz;++i) {
				if(Find(v[i])!=fa) {
					ok = false; break;
				}
			}
			if(ok) right = mid;
			else left = mid;
		}
		printf("%d\n",edge[right].d);
	}
	return 0;
}

2020.3.12
23:17
xzc


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章