天天看點

第六屆藍橋杯省賽C/C++B組(倒序)第六屆藍橋杯省賽C/C++B組(倒序)

第六屆藍橋杯省賽C/C++B組(倒序)

10.生命之樹

在X森林裡,上帝建立了生命之樹。

他給每棵樹的每個節點(葉子也稱為一個節點)上,都标了一個整數,代表這個點的和諧值。

上帝要在這棵樹内選出一個非空節點集 S,使得對于 S 中的任意兩個點 a,b,都存在一個點列 {a,v1,v2,…,vk,b} 使得這個點列中的每個點都是 S 裡面的元素,且序列中相鄰兩個點間有一條邊相連。

在這個前提下,上帝要使得 S 中的點所對應的整數的和盡量大。

這個最大的和就是上帝給生命之樹的評分。

經過 atm 的努力,他已經知道了上帝給每棵樹上每個節點上的整數。

但是由于 atm 不擅長計算,他不知道怎樣有效的求評分。

他需要你為他寫一個程式來計算一棵樹的分數。

輸入格式
第一行一個整數 n 表示這棵樹有 n 個節點。

第二行 n 個整數,依次表示每個節點的評分。

接下來 n−1 行,每行 2 個整數 u,v,表示存在一條 u 到 v 的邊。

由于這是一棵樹,是以是不存在環的。

樹的節點編号從 1 到 n。

輸出格式
輸出一行一個數,表示上帝給這棵樹的分數。

資料範圍
1≤n≤105,
每個節點的評分的絕對值均不超過 106。

輸入樣例:
5
1 -2 -3 4 5
4 2
3 1
1 2
2 5
輸出樣例:
8
           

思路

利用樹形dp,應該是樹形dp吧hh。

f[u]表示的是以u為根節點的子樹的最大分數。

f[u]=w[u]+f[j] j表示子結點 f[j]需要大于0,否則就不要

對樹進行一遍dfs,dfs的過程中求解即可。

代碼

#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 100010,M = 2*N;
int h[N],e[M],ne[M],w[N],idx;
int n;
LL f[N];
void add(int a,int b)
{
	e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void dfs(int u,int father)
{
	f[u]=w[u];
	for(int i=h[u];~i;i=ne[i])
	{
		int j=e[i];
		if(j!=father)
		{
			dfs(j,u);
			f[u]+=max(0ll,f[j]);//加上子樹的大于0的值
		}
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;++i)cin>>w[i];
	memset(h,-1,sizeof h);
	for(int i=0;i<n-1;++i)
	{
		int a,b;
		cin>>a>>b;
		add(a,b);
		add(b,a);
	}
	dfs(1,-1);
	LL ans = f[1];
	for(int i=1;i<=n;++i)
		ans = max(f[i],ans);
	cout<<ans<<endl;
	return 0;	
}
           

9.壘骰子

賭聖atm晚年迷戀上了壘骰子,就是把骰子一個壘在另一個上邊,不能歪歪扭扭,要壘成方柱體。

經過長期觀察,atm 發現了穩定骰子的奧秘:有些數字的面貼着會互相排斥!

我們先來規範一下骰子:1 的對面是 4,2 的對面是 5,3 的對面是 6。

假設有 m 組互斥現象,每組中的那兩個數字的面緊貼在一起,骰子就不能穩定的壘起來。

atm想計算一下有多少種不同的可能的壘骰子方式。

兩種壘骰子方式相同,當且僅當這兩種方式中對應高度的骰子的對應數字的朝向都相同。

由于方案數可能過多,請輸出模 109+7 的結果。

輸入格式
第一行包含兩個整數 n,m,分别表示骰子的數目和排斥的組數。

接下來 m 行,每行兩個整數 a,b,表示 a 和 b 數字不能緊貼在一起。

輸出格式
共一個數,表示答案模 109+7 的結果。

資料範圍
1≤n≤109,
1≤m≤36,
1≤a,b≤6
輸入樣例:
2 1
1 2
輸出樣例:
544
           

思路

這個題目比較難,我也講不清楚。簡單說一下知識點。

首先我們考慮dp f[i][j]為壘i個骰子,第i個骰子j點朝上的情況數。如果一層一層求,鐵定逾時。

那麼這個時候考慮矩陣乘法,尋求f[n][i]和f[n+1][j]的關系(這樣表示似乎不對,就是第n層的六面和第n+1層的六面的關系),這個時候就可以找到關系矩陣A。然後由于排斥的組數,把某些位置改為0即可。

快速矩陣乘。

代碼

#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 6,mod = 1e9+7;
int f[N][N];
int a[N][N];
int get_op(int x)
{
	if(x<3)return x+3;
	return x-3;
}
void mul(int c[][N],int a[][N],int b[][N])
{
	static int t[N][N]={0};
	memset(t,0,sizeof t);
	for(int i=0;i<N;++i)
	{
		for(int j=0;j<N;++j)
		{
			for(int k=0;k<N;++k)
			{
				t[i][j]=(t[i][j]+1ll*a[i][k]*b[k][j])%mod;//不能+= 因為加上6個1e9會爆int
			}
		}
	}
	memcpy(c,t,sizeof t);	
} 
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=0;i<N;++i)
	{
		for(int j=0;j<N;++j)
		{
			a[i][j]=4;
		}
	}
	for(int i=0;i<m;++i)
	{
		int x,y;
		cin>>x>>y;
		x--;y--;
		a[x][get_op(y)]=0;
		a[y][get_op(x)]=0;
	}
	for(int i=0;i<N;++i)
	{
		f[0][i]=4;
	}
	n--;
	while(n)
	{
		if(n&1)mul(f,f,a);
		n>>=1;
		mul(a,a,a);
	}
	LL res = 0;
	for(int i=0;i<N;++i)
	{
		res = (res+f[0][i])%mod;
	}
	cout<<res<<endl;
	return 0;
} 
           

8.移動距離

X星球居民小區的樓房全是一樣的,并且按矩陣樣式排列。其樓房的編号為1,2,3...
當排滿一行時,從下一行相鄰的樓往反方向排号。
比如:當小區排号寬度為6時,開始情形如下:
1   3  4  5  6
12 11 10 9  8  7
13 14 15 .....
我們的問題是:已知了兩個樓号m和n,需要求出它們之間的最短移動距離(不能斜線方向移動)
輸入為3個整數w m n,空格分開,都在1到10000範圍内
w為排号寬度,m,n為待計算的樓号。
要求輸出一個整數,表示m n 兩樓間最短移動距離。
例如:
使用者輸入:
6 8 2
則,程式應該輸出:
4
再例如:
使用者輸入:
4 7 20
則,程式應該輸出:
5
資源約定:
峰值記憶體消耗 < 256M
CPU消耗  < 1000ms
           

思路

把編号變為0 1 2 3…

然後處理x,y坐标

代碼

#include<iostream>
#include<cmath>
using namespace std;
int w,m,n;
int main()
{
	cin>>w>>m>>n;
	m--;n--;
	int x1,y1,x2,y2;
	x1=m/w;
	x2=n/w;
	y1=m%w;
	y2=n%w;
	if(x1%2)
		y1=w-1-y1;
	if(x2%2)
		y2=w-1-y2;
	cout<<abs(x1-x2)+abs(y1-y2)<<endl;
	return 0;
}
           

7.牌型種類

小明被劫持到X賭城,被迫與其他3人玩牌。
一副撲克牌(去掉大小王牌,共52張),均勻發給4個人,每個人13張。
這時,小明腦子裡突然冒出一個問題:
如果不考慮花色,隻考慮點數,也不考慮自己得到的牌的先後順序,自己手裡能拿到的初始牌型組合一共有多少種呢?
請填寫該整數,不要填寫任何多餘的内容或說明文字。
           

思路

dfs即可 每種牌可選0-4張,然後從第一種搜到第13種就行了

代碼

#include<iostream>
#include<cmath>
using namespace std;
int ans;
void dfs(int n,int m)
{
	if(m==0)
	{
		ans++;
		return;
	}
	if(n==13||m<0)
	{
		return;
	}
	for(int i=0;i<=4;++i)
		dfs(n+1,m-i);
}
int main()
{
	dfs(0,13); 
	cout<<ans<<endl;
	return 0;
}
           

6.加法變乘法

我們都知道:1+2+3+ ... + 49 = 1225
現在要求你把其中兩個不相鄰的加号變成乘号,使得結果為2015
比如:
1+2+3+...+10*11+12+...+27*28+29+...+49 = 2015
就是符合要求的答案。
請你尋找另外一個可能的答案,并把位置靠前的那個乘号左邊的數字送出(對于示例,就是送出10)。
注意:需要你送出的是一個整數,不要填寫任何多餘的内容。
           

思路

這個思路看代碼吧

代碼

#include<iostream>
#include<cmath>
using namespace std;

int main()
{
	for(int i=2;i<=49;++i)
	{
		for(int j=i+2;j<=49;++j)
		{
			if((1225+i*(i-1)+j*(j-1))-i-(i-1)-j-(j-1)==2015)
				cout<<i-1<<" "<<j-1<<endl;
		}
	}
	return 0;
}
           

答案

16

5.九數組分數

1,2,3...9 這九個數字組成一個分數,其值恰好為1/3,如何組法?
下面的程式實作了該功能,請填寫劃線部分缺失的代碼。
           

思路

複制粘貼;讀代碼;填空,運作正确;完成。

代碼

#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);
		t=x[k]; x[k]=x[i]; x[i]=t;// 填空處
	}
}
 
int main()
{
	int x[] = {1,2,3,4,5,6,7,8,9};
	f(x,0);
	return 0;
}
           

答案

t=x[k]; x[k]=x[i]; x[i]=t;

4.格子中輸出

StringInGrid函數會在一個指定大小的格子中列印指定的字元串。
要求字元串在水準、垂直兩個方向上都居中。
如果字元串太長,就截斷。
如果不能恰好居中,可以稍稍偏左或者偏上一點。
下面的程式實作這個邏輯,請填寫劃線部分缺少的代碼。
           

思路

複制粘貼;讀代碼;填空,運作正确;完成。

printf("%*s",10,“abc”) 表示以10寬度來顯示abc

我們可能知道scanf裡用*修飾符,是起到過濾讀入的作用。比如一個有三列數值的資料,我隻想得到第2列數值,可以在循環裡用scanf(“%*d%d%*d”, a[i])來讀入第i行的第2個數值到a[i]。 
  但是* 修飾符在printf中的含義完全不同。如果寫成printf(“%6d”, 123),很多童鞋應該就不會陌生了,這是設定域寬的意思。同理,%6s也是域寬。* 修飾符正是用來更靈活的控制域寬。使用%*s,表示這裡的具體域寬值由後面的實參決定,如printf(“%*s”, 6, “abc”)就是把”abc”放到在域寬為6的空間中右對齊
  來源:https://www.cnblogs.com/darklights/p/5296377.html
           

代碼

#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",(width-2-strlen(buf))/2,"",buf,(width-2-strlen(buf))/2,"");  //填空
 
	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,"abcd123gjdjgdddddddddddddd4");
	return 0;
}
           

答案

(width-2-strlen(buf))/2,"",buf,(width-2-strlen(buf))/2,""

3.三羊獻瑞

觀察下面的加法算式:

      祥 瑞 生 輝   // 0 1 2 3
  +   三 羊 獻 瑞   // 4 5 6 1
-------------------
   三 羊 生 瑞 氣   // 4 5 2 1 7

(如果有對齊問題,可以參看【圖1.jpg】)

其中,相同的漢字代表相同的數字,不同的漢字代表不同的數字。

請你填寫“三羊獻瑞”所代表的4位數字(答案唯一),不要填寫任何多餘内容。
           

思路

全排列+簡單的判斷

代碼

#include<iostream>
#include<vector>
using namespace std;
//8
int ans;
vector<int> vec;
bool st[10];
int get(int a[],int n)
{
	int res = 0;
	for(int i=0;i<n;++i)
	{
		res = res*10+vec[a[i]];
	}
	return res;
}
bool check()
{
	int a[] = {0,1,2,3};
	int b[] = {4,5,6,1};
	int c[] = {4,5,2,1,7};
	if(vec[0]==0||vec[4]==0)
		return false;
	int A = get(a,4);
	int B = get(b,4);
	int C = get(c,5);
	if(A+B==C)
	{
		//cout<<ans<<endl;
		cout<<A<<" "<<B<<" "<<C<<endl;
		for(int i=0;i<4;++i)
		{
			cout<<vec[b[i]]<<" ";
		}
		cout<<endl; 
		return true;
	}
	return false;//必須傳回 
}
void dfs(int n)
{
	if(n==8)
	{
		if(check())
			ans++;
		return;
	}
	for(int i=0;i<=9;++i)
	{
		if(!st[i])
		{
			st[i]=true;
			vec.push_back(i);
			dfs(n+1);
			st[i]=false;
			vec.pop_back();
		}
	}
}
int main()
{
	dfs(0);
	cout<<ans<<endl;
	return 0;
}
           

答案

1085

2.星系炸彈

思路

日期加法

代碼

#include<iostream>
using namespace std;
int days[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
bool isleap(int x)
{
	if((x%4==0&&x%100!=0)||(x%400==0))
		return true;
	return false;
}
int modify(int x)
{
	int year = x/10000;
	int month = x%10000/100;
	int day = x%100;
	day++;
	int t = days[month];
	if(month==2&&isleap(year))
		t++;
	if(day>t)
	{
		month++;
		day=1;
	}
	if(month>12)
	{
		year++;
		month=1;
	}
	return year*10000+month*100+day;
}
int main()
{
	//2000 08 04
	int year,month,day,t;
	cin>>year>>month>>day>>t;
	int ans = year*10000+month*100+day;
	while(t--)
	{
		ans = modify(ans);
	}
	printf("%04d-%02d-%02d",ans/10000,ans%10000/100,ans%100);
	return 0;
}
           

答案

2017-08-05

1.獎券數目

有些人很迷信數字,比如帶“4”的數字,認為和“死”諧音,就覺得不吉利。
雖然這些說法純屬無稽之談,但有時還要迎合大衆的需求。某抽獎活動的獎券号碼是5位數(10000-99999),要求其中不要出現帶“4”的号碼,

主辦機關請你計算一下,如果任何兩張獎券不重号,最多可發出獎券多少張。


請送出該數字(一個整數),不要寫任何多餘的内容或說明性文字。
           

思路

代碼

#include<iostream>
using namespace std;
bool check(int x)
{
	while(x)
	{
		int t=x%10;
		x/=10;
		if(t==4)return false;
	}
	return true;
}
int main()
{
	int ans = 0;
	for(int i=10000;i<=99999;++i)
	{
		if(check(i))
			ans++;
	}
	cout<<ans<<endl;
	return 0;
}
           

答案

52488

繼續閱讀