藍橋杯第六屆(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