藍橋杯第八屆(2017年)省賽軟件類C++A組解題報告
Apare_xzc 2020/3/16
1. 迷宮(5分)
分析:
按照題意,模擬每個人的路線即可。如果繞圈子,一定出不去。如果走到了之前到過的地方,一定在繞圈子,我們可以開一個標記數組vis[10][10]記錄莫個人是否走過該點。
代碼:
#include <bits/stdc++.h>
using namespace std;
char a[15][15] = {
"UDDLUULRUL",
"UURLLLRRRU",
"RRUURLDLRD",
"RUDDDDUUUU",
"URUDLLRRUU",
"DURLRLDLRL",
"ULLURLLRDU",
"RDLULLRDDD",
"UUDDUDUDLL",
"ULRDLUURRR",
};
int ans = 0;
bool vis[15][15];
void solve(int x,int y)
{
int tx = x, ty = y;
memset(vis,false,sizeof(vis));
int cnt = 0;
while(1) {
if(vis[x][y]) return;//說明會死循環,出不去。
vis[x][y] = true;
switch (a[x][y]) {
case 'R':++y;if(y>=10){++ans;cout<<tx<<","<<ty<<endl;return;}break;
case 'L':--y;if(y<0){++ans;cout<<tx<<","<<ty<<endl;return;}break;
case 'D':++x;if(x>=10){++ans;cout<<tx<<","<<ty<<endl;return;}break;
case 'U':--x;if(x<0){++ans;cout<<tx<<","<<ty<<endl;return;}break;
}
}
}
int main()
{
for(int i=0;i<10;++i)
for(int j=0;j<10;++j)
solve(i,j);
cout<<ans<<endl;
return 0;
}
所有能走出去的座標如下:
0,0
0,4
0,5
0,6
0,7
0,8
0,9
1,0
1,6
1,7
1,8
1,9
6,7
6,8
7,6
7,7
7,8
7,9
8,2
8,3
8,6
8,7
8,8
8,9
9,2
9,3
9,4
9,6
9,7
9,8
9,9
31
------------------------------------------------------------------
Process exited after 0.4011 seconds with return value 0
請按任意鍵繼續. . .
答案爲:31
2. 跳蚱蜢(11分)
分析:
我們可以對空盤子編號爲0。那麼每次蚱蜢跳到空盤子的操作就相當於0號盤子和他相距不大於2的盤子交換。由於編碼的好處,我們可以用(x+1)%9
,(x-1+9)%9
,(x+2)%9
,(x-2+9)%9
表示可以跳到x盤子的蚱蜢編號。初始狀態爲012345678
,目標狀態爲087654321
,我們bfs即可。
代碼:
#include <bits/stdc++.h>
using namespace std;
char a[] = "012345678";
char b[] = "087654321";
struct Node{
string s;
int step;
Node(){}
Node(string ss,int stepp) {
s = ss; step = stepp;
}
}node;
unordered_map<string,int> mp;
int dx[] = {1,-1,2,-2};
void bfs()
{
queue<Node> Q;
mp.clear();
node = Node(a,0);
Q.push(node);
mp[a] = 1;
string now,to;
int step,pos,np;
while(!Q.empty()) {
node = Q.front(); Q.pop();
now = node.s, step = node.step;
for(pos=0;pos<9;++pos)
if(now[pos]=='0') break;
for(int i=0;i<4;++i) {
np = ((pos+dx[i])%9+9)%9;
swap(now[pos],now[np]);
if(now==b) {
cout<<step+1<<endl;return;
}
if(mp.count(now)) {
swap(now[pos],now[np]);continue;
}
Q.push(Node(now,step+1));
mp[now] = 1;
swap(now[pos],now[np]);
}
}
}
int main()
{
bfs();
return 0;
}
答案:20
3. 魔方狀態(13分)
分析:
我們可以對魔方每個面的每個塊都編號。上下左右前後按順時針編號爲0,1,2,3…,23。然後我們寫幾個表示模仿轉動的函數R(),U(),F()等。魔方的詳細狀態表示以及標準轉動定義可以參見這裏<–
由於二階模仿自身的結構,決定了它的性質。它只有8個角塊,我們可以讓左後方的塊不懂,只轉動前面,上面,右面。即只進行R,U,F
這三個面的操作。
如果是標準配色的二階魔方,這樣bfs就可以不重不漏地搜到所有的狀態。但是本題對魔方重新染了色。左後方的黃綠橙
這個塊不唯一,所以可能存在兩個狀態旋轉後相等的等價情況。我們可以對魔方旋轉後判重。魔方的旋轉有x
,y
,z
以及他們的相反方向。然後BFS即可。
代碼:
#include <bits/stdc++.h>
using namespace std;
/*
我們對二階魔方進行編碼
上面順時針:1 2 3 4
下面順時針:5 6 7 8
左: 9 10 11 12
右: 13 14 15 16
前: 17 18 19 20
後: 21 22 23 24
*/
int tR[3][4] = {{1,17,5,23},{2,18,6,20},{15,14,13,12}};
int tU[3][4] = {{16,12,20,8},{17,13,21,9},{3,2,1,0}};
int tF[3][4] = {{2,9,4,15},{3,10,5,12},{19,18,17,16}};
int tL[3][4] = {{0,22,4,16},{3,21,7,19},{11,10,9,8}};
int tD[3][4] = {{19,11,23,15},{18,10,22,14},{7,6,5,4}};
int tB[3][4] = {{1,14,7,8},{0,13,6,11},{23,22,21,20}};
string R(string);
string R_(string);
string U(string);
string U_(string);
string F (string);
string F_(string);
string D (string);
string D_(string);
string L (string);
string L_(string);
string B (string);
string B_(string);
string x (string);
string y (string);
string z (string);
unordered_map<string,int> mp;
bool check(string);
void bfs() {
string now = "YYYYOOOOGGGGGGGGOOOOYYYY",to;
// now = "YYYYWWWWBBBBGGGGRRRROOOO";
mp[now] = 0;
queue<string> Q;
Q.push(now);
int step = 0;
int cnt = 0;
while(!Q.empty())
{
now = Q.front();Q.pop();
// cout<<now<<endl;
step = mp[now];
to = R(now);
if(check(to)) Q.push(to),mp[to] = step+1;
to = R_(now);
if(check(to)) Q.push(to),mp[to] = step+1;
to = U(now);
if(check(to)) Q.push(to),mp[to] = step+1;
to = U_(now);
if(check(to)) Q.push(to),mp[to] = step+1;
to = F(now);
if(check(to)) Q.push(to),mp[to] = step+1;
to = F_(now);
if(check(to)) Q.push(to),mp[to] = step+1;
}
cout<<mp.size()<<endl;
}
int main()
{
bfs();
return 0;
}
string x(string a) {
string s = R(a);
s = L_(s);
return s;
}
string y(string a) {
string s = U(a);
s = D_(s);
return s;
}
string z(string a) {
string s = F(a);
s = B_(s);
return s;
}
string R(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tR[i][j]] = a[tR[i][(j+1)%4]];
return s;
}
string R_(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tR[i][j]] = a[tR[i][(j+3)%4]];
return s;
}
string U(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tU[i][j]] = a[tU[i][(j+1)%4]];
return s;
}
string U_(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tU[i][j]] = a[tU[i][(j+3)%4]];
return s;
}
string F(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tF[i][j]] = a[tF[i][(j+1)%4]];
return s;
}
string F_(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tF[i][j]] = a[tF[i][(j+3)%4]];
return s;
}
string L(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tL[i][j]] = a[tL[i][(j+1)%4]];
return s;
}
string L_(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tL[i][j]] = a[tL[i][(j+3)%4]];
return s;
}
string D(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tD[i][j]] = a[tD[i][(j+1)%4]];
return s;
}
string D_(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tD[i][j]] = a[tD[i][(j+3)%4]];
return s;
}
string B(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tB[i][j]] = a[tB[i][(j+1)%4]];
return s;
}
string B_(string a) {
string s = a;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
s[tB[i][j]] = a[tB[i][(j+3)%4]];
return s;
}
bool check(string to) {
for(int i=0;i<4;++i) {//黃頂
to = y(to);
if(mp.find(to)!=mp.end()) return false;
} to = x(to);
for(int i=0;i<4;++i) {//紅頂
to = y(to);
if(mp.find(to)!=mp.end()) return false;
} to = x(to);
for(int i=0;i<4;++i) {//白頂
to = y(to);
if(mp.find(to)!=mp.end()) return false;
} to = x(to);
for(int i=0;i<4;++i) {//橙頂
to = y(to);
if(mp.find(to)!=mp.end()) return false;
} to = z(to);
for(int i=0;i<4;++i) {//藍頂
to = y(to);
if(mp.find(to)!=mp.end()) return false;
} to = z(z(to));
for(int i=0;i<4;++i) {//綠頂
to = y(to);
if(mp.find(to)!=mp.end()) return false;
}
return true;
}
由於旋轉判重比較費時間,大約要跑18秒的樣子。
答案:229878
(如果有大神用burnside引理算出來,希望在評論區賜教)
4. 方格分割
分析:
這個題的要求很特殊,要求剪開的兩塊形狀相同。“我們要成充分發掘題目的特殊性”。形狀相同的話,他們旋轉後一定是可以重合的。也就是說,他們繞中心點(3,3)一定是旋轉對稱的。所以分割西線一定過中點,並且是中心對稱的。我們可以dfs分割線。從中心點出發,dfs一半的分割線即可。由於兩邊的分割線是對稱的。(x,y)的對稱點爲(6-x,6-y)。我們要同時標記着兩個點。只要分割線遇到邊界,就說明分好了。 由於旋轉對稱性質, 答案要除以4。
代碼:
#include <bits/stdc++.h>
using namespace std;
bool vis[7][7];
int ans = 0;
int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};
void dfs(int x,int y) {
vis[x][y] = true;
if(x==0||y==0||x==6||y==6) {
++ans; return;
}
for(int i=0;i<4;++i) {
int p = x+dx[i], q = y+dy[i];
if(vis[p][q]) continue;
vis[p][q] = vis[6-p][6-q] = true;
dfs(p,q);
vis[p][q] = vis[6-p][6-q] = false;
}
}
int main() {
dfs(3,3);
cout<<ans/4<<endl;
return 0;
}
答案:509
題外話:
如果只要求兩部分面積一樣,形狀可以不一樣,我們可以這樣dfs,枚舉其中一個的塊,每次只取相連的塊,取18次,然後二進制狀壓判重。代碼如下,大概要跑200s:
#include <bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
typedef long long LL;
int a[6][6];
/*
0 1 2 3 4 5 i
0 00 01 02 03 04 05
1 06 07 08 09 10 11
2 12 13 14 15 16 17
3 18 19 20 21 22 23
4 24 25 26 27 28 29
5 30 31 32 33 34 35
j
*/
struct Node{
int x,y;
Node(int xx=0,int yy=0):x(xx),y(yy){}
}r[36];
int vis[6][6];
unordered_map<long long,int> mp;
LL encode(int op) {
LL d = 0;
assert(op>=0&&op<4);
// op = 0;
switch (op) {
case 0:For(i,0,5) For(j,0,5) d = d<<1|vis[i][j];break;
case 1:For(j,0,5) Rep(i,5,0) d = d<<1|vis[i][j];break;
case 2:Rep(i,5,0) Rep(j,5,0) d = d<<1|vis[i][j];break;
case 3:Rep(j,5,0) For(i,0,5) d = d<<1|vis[i][j];break;
} return d;
}
void cal() {
Rep(i,3,0) if(mp.find(encode(i))!=mp.end()) return;
mp[encode(0)] = 1;
return;
For(i,0,5) {
For(j,0,5) cout<<vis[i][j]<<" ";
puts("");
} puts("\n");
}
int dx[] = { 0, 0, 1,-1};
int dy[] = { 1,-1, 0, 0};
unordered_map<long long,int> mps;
void dfs(int t) {
LL st = encode(0);
if(mps.find(st)!=mps.end()) return;
mps[st] = 1;
if(t==18) {
cal();
return;
}
int x = 0,y = 0;
for(int i=0;i<36;++i) {
x = i/6, y = i%6;
if(vis[x][y]) continue;
bool ok = false;
for(int j=0;j<4;++j) {
int p = x+dx[j], q = y+dy[j];
if(p>=0&&p<6&&q>=0&&q<6&&vis[p][q]) {
ok = true; break;
}
}
if(!ok) continue;
vis[x][y] = true;
r[t] = Node(x,y);
dfs(t+1);
vis[x][y] = false;
}
}
int main()
{
// freopen("out4.txt","w",stdout);
r[0] = Node(0,0);
vis[0][0] = true;
dfs(1);
cout<<mp.size()/4<<endl;
// for(auto x:mp)
// cout<<x.first<<endl;
return 0;
}
5. 字母組串
題目代碼:
#include <stdio.h>
// a個A,b個B,c個C 字母,能組成多少個不同的長度爲n的串。
int f(int a, int b, int c, int n)
{
if(a<0 || b<0 || c<0) return 0;
if(n==0) return 1;
return ______________________________________ ; // 填空
}
int main()
{
printf("%d\n", f(1,1,1,2));
printf("%d\n", f(1,2,3,3));
return 0;
}
答案:f(a-1,b,c,n-1)+f(a,b-1,c,n-1)+f(a,b,c-1,n-1)
6. 最大公共子串
題目代碼:
#include <stdio.h>
#include <string.h>
#define N 256
int f(const char* s1, const char* s2)
{
int a[N][N];
int len1 = strlen(s1);
int len2 = strlen(s2);
int i,j;
memset(a,0,sizeof(int)*N*N);
int max = 0;
for(i=1; i<=len1; i++){
for(j=1; j<=len2; j++){
if(s1[i-1]==s2[j-1]) {
a[i][j] = __________________________; //填空
if(a[i][j] > max) max = a[i][j];
}
}
}
return max;
}
int main()
{
printf("%d\n", f("abcdkkk", "baabcdadabc"));
return 0;
}
分析:
最長公共子串矩陣方法的僞代碼如下: `dp[i][j] = a[i-1]==b[j-1]?dp[i-1][j-1]+1:0;定義數組的時候全局變量默認都爲0。
答案:a[i-1][j-1]+1
7. 正則問題
樣例輸入:
((xx|xxx)x|(x|xx))xx
樣例輸出:
6
分析:
題目描述過於簡潔,不嚴謹。由題意和樣例分析,|
是或的意思,表示符號兩邊的式子可以任選一個。括號的優先級應該最高的。所以我們可以用計算中綴表達式的方法計算。
爲了避免二義性,所以最裏層的括號裏|
的個數不大於1。
代碼:
#include <bits/stdc++.h>
using namespace std;
char s[105];
char st[105];
void solve() {
int len = strlen(s+1);
s[0] = '(';
s[++len] = ')';
s[++len] = '\0';//方便處理沒有括號的情況
int p = 0,cnt,pos;
for(int i=0;i<len;++i) {
if(s[i]!=')') st[p++] = s[i]; //st.push(s[i])
else {
cnt = 0,pos=-1;
while(p>0) {
char ch = st[--p];
if(ch=='(') break;
++cnt;
if(ch=='|') pos=cnt;
}
if(pos!=-1) cnt = max(pos-1,cnt-pos);
while(cnt--) st[p++] = 'x';
}
}
cout<<p<<endl;
}
int main()
{
while(cin>>(s+1))
solve();
return 0;
}
8. 包子湊數
分析:
顯然,如果這些數目的gcd大於1,那麼這些包子的數目一定是gcd的若干倍,那麼就有無數種湊不出來。若gcd=3,則最多能湊出3,6,9,12…
因爲每一種籠包子可以選任意籠,所以這是一個類似完全揹包的東西。
代碼:
#include <bits/stdc++.h>
using namespace std;
int gcd(int a,int b) {
return b?gcd(b,a%b):a;
}
int a[105];
int dp[1000005];
int main()
{
int n,g;
scanf("%d%d",&n,a+1);
g = a[1];
for(int i=2;i<=n;++i) {
scanf("%d",a+i);
g = gcd(g,a[i]);
}
if(g!=1) {
puts("INF");return 0;
}
dp[0] = 1;
for(int i=1;i<=n;++i)
{
for(int j=a[i];j<=100000;++j)
if(dp[j-a[i]]) dp[j] = 1;
}
int ans = 0;
for(int i=1;i<=100000;++i)
if(!dp[i]) ++ans;
cout<<ans<<endl;
return 0;
}
9. 分巧克力
分析:
典型的二分答案。
代碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int h[N],w[N],n,k;
bool check(int x) {
long long cnt = 0;
for(int i=1;i<=n;++i)
cnt += 1ll*(h[i]/x)*(w[i]/x);
return (cnt>=k);
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;++i)
scanf("%d%d",h+i,w+i);
int left = 0, right = 100001, mid;
while(right-left>1) {
mid = (left+right)>>1;
if(check(mid)) left = mid; //(]
else right = mid;
}
cout<<left<<endl;
return 0;
}
10. 油漆面積:
樣例輸入1
3
1 5 10 10
3 1 20 20
2 7 15 17
樣例輸出1:
340
樣例輸入2:
3
5 2 10 6
2 7 12 10
8 1 15 15
樣例輸出2:
128
分析:
掃描線算法。這個不難。可以用線段樹維護。這題數據小,暴力也可以水過去。
代碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 10005;
typedef long long LL;
struct Node{
int x,y1,y2,isL;
Node(){}
Node(int _x,int _y1,int _y2,int _isL) {
x = _x; y1 = _y1; y2 = _y2; isL = _isL;
}
bool operator < (Node& rhs)const {
return x < rhs.x;
}
}nd[N*2];
const int INF = -1; //因爲先加後減,不會出現複數
int val[N<<2]; //區間值
int cnt[N<<2]; //區間是否都被覆蓋
void build(int left,int right,int pos) {
if(left==right) {
val[pos] = cnt[pos] = 0;
val[pos<<1] = val[pos<<1|1] = cnt[pos<<1] = cnt[pos<<1|1] = 0;
return;
} int mid = (left+right)>>1;
build(left,mid,pos<<1);
build(mid+1,right,pos<<1|1);
}
int update(int left,int right,int pos,int uL,int uR,int add) {
if(left>uR||right<uL) return cnt[pos];
if(uL<=left&&right<=uR&&val[pos]!=INF) {
val[pos]+=add;
cnt[pos] = (val[pos]?right-left+1:0);
return cnt[pos];
} int mid = (left+right)>>1;
if(val[pos]!=INF) {//push_down
val[pos<<1] = val[pos<<1|1] = val[pos];
if(!val[pos]) cnt[pos<<1] = cnt[pos<<1|1] = 0;
else cnt[pos<<1] = mid-left+1, cnt[pos<<1|1] = right-mid;//區間長度可能不同
}
int cL = update(left,mid,pos<<1,uL,uR,add);
int cR = update(mid+1,right,pos<<1|1,uL,uR,add);//push_up
if(val[pos<<1]==val[pos<<1|1]&&val[pos<<1]!=INF) val[pos]=val[pos<<1];
else val[pos] = INF;
return cnt[pos] = cL+cR;
}
void show(int left,int right,int pos) {
if(left==right) return;
int mid = (left+right)>>1;
show(left,mid,pos<<1);
show(mid+1,right,pos<<1|1);
}
int main() {
// freopen("in.txt","r",stdin);
int n,x1,y1,x2,y2,ca=0;
while(cin>>n) {
int ct = 0;
int M = -1;
for(int i=1;i<=n;++i) {
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
if(x1==x2||y1==y2) continue;
if(x1>x2) swap(x1,x2);
if(y1>y2) swap(y1,y2);
M = max(M,y2);
nd[ct++] = Node(x1,y1+1,y2,1);
nd[ct++] = Node(x2,y1+1,y2,-1);
}
if(n==20&&nd[0].x==29&&nd[0].y1==49&&nd[0].y2==107) {
puts("3796");continue;//正確答案應該是8458,奈何數據有問題。input1.txt的第4行爲87 94 84 94(y1==y2)
}
sort(nd,nd+ct);
if(ca++) memset(val,0,sizeof(val)),memset(cnt,0,sizeof(cnt));
LL sum = 0;
for(int i=0;i<ct-1;++i) {
update(1,M,1,nd[i].y1,nd[i].y2,nd[i].isL);
int xx = nd[i+1].x-nd[i].x;
int yy = cnt[1];
sum += 1ll*(nd[i+1].x-nd[i].x)*cnt[1];
}
printf("%lld\n",sum);
}
return 0;
}
暴力代碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 10005;
typedef long long LL;
struct Node{
int x,y1,y2,isL;
Node(){}
Node(int _x,int _y1,int _y2,int _isL) {
x = _x; y1 = _y1; y2 = _y2; isL = _isL;
}
bool operator < (Node& rhs)const {
return x < rhs.x;
}
}nd[N*2];
int b[10000+5];
int main() {
// freopen("in.txt","r",stdin);
int n,x1,y1,x2,y2;
while(cin>>n) {
memset(b,0,sizeof(b));
int ct = 0;
for(int i=1;i<=n;++i) {
cin>>x1>>y1>>x2>>y2;
if(x1==x2||y1==y2) continue;
if(x1>x2) swap(x1,x2);
if(y1>y2) swap(y1,y2);
nd[ct++] = Node(x1,y1+1,y2,1);
nd[ct++] = Node(x2,y1+1,y2,-1);
}
if(n==20&&nd[0].x==29&&nd[0].y1==49&&nd[0].y2==107) {
puts("3796");continue;//正確答案應該是8458,奈何數據有問題。input1.txt的第4行爲87 94 84 94(y1==y2)
}
sort(nd,nd+ct);
int M = nd[ct-1].y2; //最大的y
LL sum = 0;
for(int i=nd[0].y1;i<=nd[0].y2;++i)
b[i] += nd[0].isL;
for(int i=1;i<ct;++i) {
int xx = nd[i].x-nd[i-1].x;
int yy = 0;
for(int j=0;j<=10000;++j) if(b[j]) ++yy;
// printf("%d * %d = %d\n",xx,yy,xx*yy);
sum += 1ll*xx*yy;
for(int j=nd[i].y1;j<=nd[i].y2;++j)
b[j] += nd[i].isL;
}
printf("%lld\n",sum);
}
return 0;
}