2015 苏州大学程序设计校赛题解 #1
【传送门】
1001 签到题:
答案为12。
思路:考虑到 n 的范围比较小,因此只要开一个 > 100000 的数组来记录在 [1,n] 范围内的数是否出现即可。
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; int vis[100005]; int main() { int n,m,i,x,ans; //freopen("2.in","r",stdin); //freopen("2.out","w",stdout); while (~scanf("%d%d",&n,&m)) { memset(vis,0,sizeof(vis)); for (i=1;i<=m;i++) { scanf("%d",&x); if (x>=1&&x<=n) vis[x]=1; } ans=0; for (i=1;i<=n;i++) if (vis[i]==0) ans++; printf("%d\n",ans); } }
1003 投放炸弹:
思路:考虑枚举投放炸弹的位置,发现距离某个位置曼哈顿距离 <= k 的格子形成一个菱形,那么能炸到的居民区数就是该菱形内的 '*' 数量。
做法1:开一个二维 pre 数组来纪录每行 '*' 个数的前缀和,pre[i][j] 表示第 i 行前 j 个位置里 '*' 的个数。那么在枚举投弹位置后,再枚举其能炸到的行,对于第 i 行,计算出能炸到的列的范围 [L,R] 那么该行能炸到的 '*' 个数就是:pre[i][R] - pre[i][L - 1],时间复杂度:O(n^2 * m)
做法2:上文说到求菱形内 '*' 数量和,我们可以将 n*m 的矩形整个旋转45度,然后菱形区域就变成了正方形,问题就变成了经典的二维前缀和。这里需要比较好的编号方式,对于二维前缀和,用 S[i][j] 表示前 i 行前 j 列这个子矩形内的 '*' 数量,然后用对于左上角 (x1,y1),右下 (x2,y2) 的子矩型,S[x2][y2] - S[x2][y1-1] - S[x1-1][y2] + S[x1-1][y1-1] 就表示其子矩形内的 '*' 数量。
做法1:
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; char s[205][205]; int pre[205][205]; int main() { int n,m,x,i,j,k,ans,tmp,y1,y2; //freopen("3.in","r",stdin); //freopen("3.out","w",stdout); while (~scanf("%d%d%d",&n,&m,&x)) { for (i=1;i<=n;i++) scanf("%s",s[i]+1); memset(pre,0,sizeof(pre)); for (i=1;i<=n;i++) for (j=1;j<=m;j++) pre[i][j]=pre[i][j-1]+(s[i][j]=='*'); ans=0; for (i=1;i<=n;i++) for (j=1;j<=m;j++) if (s[i][j]=='.') { tmp=0; for (k=1;k<=n;k++) { if (abs(i-k)>x) continue; y1=j-(x-abs(i-k)); y2=j+(x-abs(i-k)); y1=max(1,y1); y2=min(m,y2); tmp+=pre[k][y2]-pre[k][y1-1]; } ans=max(ans,tmp); } printf("%d\n",ans); } return 0; }
做法2:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int n,m,X; char g[210][210]; int sum[600][600]; int main(){ while(scanf("%d%d%d",&n,&m,&X) != EOF){ for(int i = 1; i <= n; ++i) scanf("%s",g[i] + 1); memset(sum,0,sizeof(sum)); int x,y; for(int j = 1; j <= m; ++j){ x = j; y = j + n - 1; for(int i = 1; i <= n; ++i){ if(g[i][j] == '*') sum[x][y] = 1; x++; y--; } } int top = n + m; for(int i = 1; i < top; ++i){ for(int j = 1; j < top; ++j){ sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1]; } } int ans = 0; for(int j = 1; j <= m; ++j){ x = j; y = j + n - 1; for(int i = 1; i <= n; ++i){ if(g[i][j] == '.'){ int tx1 = min(top - 1,x + X); int tx2 = max(1,x - X); int ty1 = min(top - 1,y + X); int ty2 = max(1,y - X); ans = max(ans,sum[tx1][ty1] - sum[tx2 - 1][ty1] - sum[tx1][ty2 - 1] + sum[tx2 - 1][ty2 - 1]); } x++; y--; } } printf("%d\n",ans); } return 0; }
1004 防AK的数字:
思路:本题为数位DP题(不懂的可以百度一下这个算法),可以用 dp[i][j][f] 表示前 i 位,最后一个数字为 j,前缀是否等于上界,那么更新的过程为:如果 f=1,那么 i+1 位可以放的数为 pre_num ~ str[i+1],如果 f=0,那么当前数已经小于上界,那么 i+1 位可以放的数为 pre_num ~ 9。转移过程见代码。想攻克这道题,建议先了解一下DP以及数位DP,再做一下数位DP的例题:HDU 2089
#include<stdio.h> #include<string.h> #include<algorithm> #define mod 1000000007 using namespace std; void add(int &x,int y) { x+=y; if (x>=mod) x-=mod; } int a[105],dp[105][15][2],flag; int solve(char s[]) { int n=strlen(s); for (int i=1;i<=n;i++) a[i]=s[i-1]-'0'; memset(dp,0,sizeof(dp)); dp[0][0][1]=1; for (int i=0;i<n;i++) for (int j=0;j<=9;j++) for (int k=0;k<=1;k++) for (int x=j;x<=9;x++) if (k==1&&x>a[i+1]) continue; else if (k==1&&x==a[i+1]) add(dp[i+1][x][1],dp[i][j][k]); else add(dp[i+1][x][0],dp[i][j][k]); int ans=0; flag=0; for (int i=0;i<=9;i++) { add(ans,dp[n][i][0]); add(ans,dp[n][i][1]); add(flag,dp[n][i][1]); } return ans; } char s1[105],s2[105]; int main() { //freopen("4.in","r",stdin); //freopen("4.out","w",stdout); while (~scanf("%s%s",s1,s2)) printf("%d\n",(solve(s2)-solve(s1)+flag+mod)%mod); return 0; }
1005 发现:
思路:萌萌哒炉石题 233!先考虑三个全为0,三个全为1的发现,统计其次数为:ta,tb,再统计有0也有1的发现次数:tc,然后判断 tc - max(0,a - ta) - max(0,b - tb) 是否大于等于0即可。
#include <stdio.h> int n,a,b; int main(){ while(scanf("%d%d%d",&n,&a,&b) != EOF){ int v1,v2,v3,ta = 0,tb = 0,tc = 0; for(int i = 1; i <= n; ++i){ scanf("%d%d%d",&v1,&v2,&v3); if(v1 + v2 + v3 == 0) ta++; else if(v1 + v2 + v3 == 3) tb++; else tc++; } if(ta < a) tc -= a - ta; if(tb < b) tc -= b - tb; if(tc >= 0) printf("YES\n"); else printf("NO\n"); } return 0; }
1006 取金币:
思路:先特判一下 a=0 或者 b=0 时肯定是 First 的情况。考虑 a=1,b=1 的情况,是 Second,然后倒着考虑,每次加两个金币,这样无论加多少次,两边的金币数要么全是奇数,要么全是偶数,所以 Second 的情况是两边奇偶性相同,First 的情况是两边奇偶性不同。
#include <stdio.h> int main(){ int n; scanf("%d",&n); while(n--){ int a,b; scanf("%d%d",&a,&b); if(a==0||b==0){ printf("First\n"); continue; } if((a&1)+(b&1)==1) printf("First\n"); else printf("Second\n"); } return 0; }
1007 连通图:
思路:注意 1 <= n <= 4 这个数据范围,所以只要手画一下图就行,具体见代码。
#include <stdio.h> int main(){ int n,k; while(scanf("%d%d",&n,&k) != EOF){ if(n <= 2){ printf("%.2f\n",(n == 1 || k) ? 1.0 : 0.0); } else if(n == 3){ printf("%.2f\n",k > 1 ? 1.0 : 0.0); } else{ if(k == 3) printf("%.2f\n",0.8); else printf("%.2f\n",k > 3 ? 1.0 : 0.0); } } return 0; }
1008 猪猪过河:
思路:注意一个点:如果猪猪跳完最后一步到达对岸后桥沉了(h<0)是允许的。首先算出需要跳的步数 t,然后判断在桥上的步数 t-1 中会不会有 h < 0 的情况。
#include <iostream> int T,x,y,L,H; int main(){ scanf("%d",&T); while(T--){ scanf("%d%d%d%d",&x,&y,&L,&H); int t = L / x; if(L % x != 0) t++; if(t - 1 <= H / y) printf("%d\n",t); else printf("-1\n"); } return 0; }
1009 死胡同:
思路:【首先特判掉 n =1 的情况,此时必定是YES,接下来考虑 n > 1 的情况。】
考虑猪猪的行走路线,如果 n=4,那么遍历到的格子:12343212343212...,以 123432 为一个周期,其长度为 2n-2 。
做法1:由于 n <= 100000,这道题可以暴力模拟来做,不断地走直到某个位置某一朝向的情况出现两次,再推出循环。但是要考虑到 k 远大于 n 的情况下性能较差的情况,所以先将 k %= (2n-2),这样就可以通过。
做法2:由于 2n-2 是一个周期,那么可以把问题看成猪猪在一个长为 2n-2 的环里面无限地走,每次走 k 步,可以证明如果 GCD(2n-2,k)=1,可以走到所有格子。
简证:【用 GCD 代表 2n-2 和 k 的最大公约数,用 LCM 代表两者的最小公倍数】从 1 出发,不断地每次走 k 步,直到再次走到 1 形成一个周期,此时走的步数必定为 p*(2n-2),p为一个>=1的常数,易得 p*(2n-2)= LCM 那么在第二次走到1之前,总共标记了m = LCM / k 个格子,由于 LCM=(2n-2)* k / GCD,那么 m = (2n-2)/ GCD,如果 GCD>=2,那么 m < n。由上述,每个周期起点都为1,且标记的点相同,点数都为 m < n,所以 GCD >=2 时不能标记所有点,答案为NO。
接下来证明 GCD = 1 时能标记所有点,考虑另外一个问题:一个有 L 个格子的环,从 1 出发,每次跳 k 个格子能否跳到所有格子。显然重复跳到某个格子至少需要跳 LCM(L,k) 个格子(设为结论1),跳了 LCM / k 次,如果 GCD(L,K)=1,那么跳的次数为 L 次,这期间必定跳到 1~L 所有点,即证明:期间跳到的格子不重复。如果跳到的点重复,则说明重复跳到某个格子至少需要跳的格子数 < LCM,与结论1矛盾,得证。
做法1:
#include <stdio.h> #include <string.h> const int MAX = 102400; const int LEFT = 0; const int RIGHT = 1; int N, K; int pVisited[2][MAX]; int main(){ while(scanf("%d%d",&N,&K) != EOF){ if(N == 1){ printf("YES\n"); continue; } K %= 2 * (N - 1); memset(pVisited, false, sizeof(pVisited)); int nDir = RIGHT, nCur = 1; while(!pVisited[nDir][nCur]){ pVisited[nDir][nCur] = true; if(nDir == RIGHT){ nCur += K; if(nCur > N){ nCur = 2 * N - nCur; nDir = LEFT; } if(nCur < 1){ nCur = 2 - nCur; nDir = RIGHT; } } else{ nCur -= K; if(nCur < 1){ nCur = 2 - nCur; nDir = RIGHT; } if(nCur > N){ nCur = 2 * N - nCur; nDir = LEFT; } } } bool bFlag = true; for(int i = 1; i <= N; i++){ if(!pVisited[LEFT][i] && !pVisited[RIGHT][i]) { bFlag = false; break; } } if(bFlag) printf("YES\n"); else printf("NO\n"); } return 0; }
做法2:
#include <stdio.h> int n,k; int Gcd(int a,int b){ return b == 0 ? a : Gcd(b,a % b); } int main(){ while(scanf("%d%d",&n,&k) != EOF){ if(n == 1){ printf("YES\n"); continue; } if(Gcd(2 * n - 2,k) == 1) printf("YES\n"); else printf("NO\n"); } return 0; }
1010 投票:
思路:经典的投票问题,先用二维字符串数组存下名字,然后开两层循环来判断(strcmp(s1,s2)==0 说明s1,s2相同),复杂度O(n*n*length_of_name)。当然有更加优美的做法:只用一层循环来做复杂度:O(n * length_of_name),具体见代码并思考。
下面给出优美的做法:
#include <stdio.h> #include <string.h> #include <stdlib.h> char name[1010][15]; int main() { int T; scanf("%d", &T); while (T -- > 0) { int m; scanf("%d", &m); int i, answerId = 0, votes = 1; for (i = 0; i < m; ++ i) { scanf("%s", name[i]); } for (i = 1; i < m; ++ i) { if (strcmp(name[i], name[answerId]) == 0) ++ votes; else { -- votes; if (votes <= 0) { answerId = i; votes = 1; } } } printf("%s\n", name[answerId]); } return 0; }