2015 苏州大学程序设计校赛题解 #1

传送门

1001 签到题:

  答案为12。

 

1002 丢失的数字:

  思路:考虑到 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);
    }
}
View Code

 

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;
}
View Code

做法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;
}
View Code

 

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;
} 
View Code

 

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;
}
View Code

 

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;
}
View Code

 

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;
}
View Code

 

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;
}
View Code

 

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