【题解 && 总结】 2019 5 31 考试题解 && 总结

我觉得我在这场考试上的时间分配十分不均匀。

首先由于是老刘放题,所以我很‘乖’的先把所有题目都读了一遍,对于前四题,我刚读完前4题时也还是有些思路的,但是当我看到5题的Mod 1e9+7时,整个人都不好了,我知道,递推来了(果不其然)。。

注:总结与题解一起


一、Classroom Watch

题目描述大致如下:
给出一个正整数 n,现在问存在多少个 x,使得 x在十进制下的每一位之和加上 x 等于 n。

分析:
看完这题,我的第一反应就是‘水’,不就是暴力嘛。但是我一看到题目范围,就知道不对了,肯定不能暴力。
这个取值范围也告诉我们这道题有着更快更简单的方法,不必强势枚举
尝试从条件入手: 合格的是的条件是由本身的数加上各个位数之和
当我看到这个条件是,我的本能反应:eng?各个位数之和?按照位数来算,这个位数最多也才81呀?

由此得到了正解:由于各个位数累加的和最多也就81,所以合格的数的范围肯定也不会超过n的81,不然毋庸置疑会比他小


Code

#include<bits/stdc++.h>
using namespace std;
int n;
int len=0;
int num[2001];

int check(int k){
    int w=k;
    while (k) w+=k%10,k/=10;
    return w;
}

int main(){
	freopen("num.in","r",stdin);
	freopen("num.out","w",stdout);
    scanf("%d",&n);
    int x=n,ans=0;
    while (x) len++,x/=10;//取出位数
    for (int i=n-9*len/*至少需要的数值*/;i<=n;i++)
      if (check(i)==n) num[++ans]=i;//符合条件
    printf("%d\n",ans);
    for (int i=1;i<=ans;i++) printf("%d\n",num[i]);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

后记:
当时一看到题目,就去上个厕所冷静了一下。
上厕所5分钟,就想到了这个方法,赶快从厕所跑回来
第一题用了15分钟
/厕所是个好东西/

这道题给我们启示;当我们一道题目不能按常规的思路去解时,可以根据题目条件之间的关系来进行优化


二、组合技能

蓝月商城出新技能书了!!
如果古天乐想购买“旋风斩”,则他需要花费A元;如果古天乐想买“半月弯刀”,则需要B元;如果古天乐两个一起买,则需要C元。
蓝月的设计师非常有头脑,每样商品的利润都是相同的。即假设旋风斩和半月弯刀的成本为a,b元,则A-a=B-b=C-a-b。
给出A,B,C求出利润,数据保证为正数。
(这题目。。。。)

这道题我只想无语。


前言:
拿到题目:暴力啊!
果断用10分钟打了个暴力。
看了一下数据范围:一惊!!怎么可能??!!

竟然就是暴力!!

开心得过了,结果人家五行的代码。,


分析:
暴力想必大家都会,那么接下来我给大家推一下五行的代码:

  • 设利润为x
  • A-a=B-b=C-a-b
  • A-a+B-b=2x,C-a-b=x
  • A-a+B-b-x=C-a-b
  • x=A+B-C

确实五行。


那么接下来给暴力的代码:

#include<bits/stdc++.h>
using namespace std;
int t;
int a,b,c;

bool check(int w,int a,int b,int c){
    int A=a-w,B=b-w;
    if (c-A-B==w) return 1;
    return 0;
}

int main(){
	freopen("combo.in","r",stdin);
	freopen("combo.out","w",stdout);
    scanf("%d",&t);
    for (int i=1;i<=t;i++){
	    scanf("%d %d %d",&a,&b,&c);
	    for (int i=min(a,b);i>=0;i--)
	      if (check(i,a,b,c)) {printf("%d\n",i);break;}
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}

后记:
这道题很快,大概用了10分钟。
前两道用了半小时,似乎很顺利。

这道题给我们启示:能打暴力尽量打暴力。不必去想花里胡哨的东西 有的时候我们不能老是往暴力的方面去想,有的时候还要尝试去想一下更简单的正解


三、表面积

古天乐在搭积木,积木图可以抽象为一个n*m的网格图,其中第(i,j)的位置有A[i][j]个积木。求表面积。
(简洁明了)


前言:
看这道题时,我是有些迟疑的——底面积算不算呢?
当时我有充足的时间,就把第二个样例模拟了一遍,十分钟之后,我模拟出了样例,发现是要算底面积的,于是开开心心的继续看第一个样例:
cin:
1 1
1
cout:
6
我:&%!&@*!(!


分析:
我们尝试想常规思路:表面积?公式?似乎不行,如此不规则的图中想找几个规则的图形真的很难

尝试换思路:
表面积?就是露在外面的面积,那么我们是否可以认为是总的面积减去不露在外面的面积呢?
事实就是如此。
我们假设一共有 n u m num 块积木。
那么总的面积 s u m = n u m 6 sum=num*6
尝试遮住的面积怎么求。
遮住的面一共有三种情况:

  • 上下遮,也就是说同一层积木叠上去会遮住面积,这个好求,对于一层积木而言,遮住面积就是 ( a [ i ] [ j ] 1 ) 2 (a[i][j]-1)*2
  • 左右遮,就是说积木左右拼接起来时会有遮住的面,这个我们尝试用循环去求出
  • 前后遮,与左右遮思路相同

那么总方案就是 s u m sum-遮住的面的面积


Code:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int sum=0,tot=0;
int a[201][201];

int main(){
    freopen("surface.in","r",stdin);
	freopen("surface.out","w",stdout); 
    scanf("%d %d",&n,&m);
    for (int i=1;i<=n;i++)
      for (int j=1;j<=m;j++)
        scanf("%d",&a[i][j]),sum+=a[i][j]*6,tot+=max(0,(a[i][j]-1)*2);
    for (int i=1;i<=n;i++)
      for (int j=2;j<=m;j++)
        tot+=min(a[i][j],a[i][j-1])*2;
    for (int j=1;j<=m;j++)
      for (int i=2;i<=n;i++)
        tot+=min(a[i][j],a[i-1][j])*2;//分别前后左右求出遮住的面积
   tot=max(tot,0);
    printf("%d",sum-tot);//总面积-重叠面积 
    fclose(stdin);
    fclose(stdout);
    return 0;
}


后记:
这道题由于进行了一次模拟,所以耗时多了一点,用了半个小时。
前三题用了1个小时,似乎挺顺利。。

这道题给我们的启发是:不能老是禁锢于传统化思想,我们要考虑所求的答案可以由哪些其他能够求得数值转移出来,问题就转化成求相应的数值


四、红皇后的旅行

给定一个n*n的棋盘,行和列标号为0,1,2,….,n-1。在棋盘的(i_start,j_start)位置上有一位红皇后,每次红皇后可以往六个方向走,如图所示:
在这里插入图片描述
现在红皇后想去(i_end,j_end)点,求最短距离,并且输出一条路径。
显然最短路径有无穷条,请按照以下顺序来搜索:UL, UR, R, LR, LL, L。
如果无解,输出Impossible


前言:
刚开始看这道题时,我的第一反应就是广搜。
但是我一想到这是老刘放的题,就不对了,想了一会儿会不会是dp。
十分钟过去没想出来
算了就bfs吧…………
A了。


分析:
这道题不用怎么说吧,就是裸的一道bfs。
只是多了一个输出方案,只需要在转移的时候多加一个数组来记录当前的最优值是由哪个状态转移过来的,在记录一下转移的方向即可。


Code

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
int dx[6]={-2,-2,0,+2,+2,0};
int dy[6]={-1,1,+2,+1,-1,-2};
int n;
int st_i,st_j,en_i,en_j;
int pr1[201][201],pr2[201][201];
int z[201][201];
int sum[201][201];

void redu(int x,int y){
    if (!x) return;
    redu(pr1[x][y],pr2[x][y]);
    if (z[x][y]==0) printf("UL ");
    else if (z[x][y]==1) printf("UR ");
    else if (z[x][y]==2) printf("R ");
    else if (z[x][y]==3) printf("LR ");
    else if (z[x][y]==4) printf("LL ");
    else if (z[x][y]==5) printf("L ");//输出
    return;
}

int main(){
	freopen("redqueen.in","r",stdin);
	freopen("redqueen.out","w",stdout);
    scanf("%d",&n);
    scanf("%d %d %d %d",&st_i,&st_j,&en_i,&en_j);
    st_i++;st_j++;en_i++;en_j++;
    queue < pair < int , int > > q;
    memset(sum,20,sizeof(sum));
    sum[st_i][st_j]=0;
    pr1[st_i][st_j]=pr2[st_i][st_j]=0;
    z[st_i][st_j]=z[en_i][en_j]=6;
    q.push(mp(st_i,st_j));
    while (!q.empty()){
	    int x=q.front().first,y=q.front().second;
	    q.pop();
	    for (int i=0;i<6;i++){
		    int xx=x+dx[i],yy=y+dy[i];
		    if (xx<0 || xx>n || yy<0 || yy>n) continue;
		    if (sum[x][y]+1>=sum[xx][yy]) continue;
		    sum[xx][yy]=sum[x][y]+1;
		    pr1[xx][yy]=x,pr2[xx][yy]=y;//记录转移对象
		    z[xx][yy]=i;//记录方向
		    if (xx==en_i && yy==en_j) break;
		    q.push(mp(xx,yy));
		}
	}
	if (sum[en_i][en_j]==336860180) {printf("Impossible");return 0;}
	else printf("%d\n",sum[en_i][en_j]);
	redu(en_i,en_j);//输出方案
	fclose(stdin);
	fclose(stdout);
	return 0;
}

后记:
这道题比较码,写了30分钟。
前四题一共用了90分钟,似乎,似乎挺顺利?

这道题给我们的启发是:不必纠结!!按自己的第一印象来写题!!!


五、构造序列

有一个长度为n的序列A,其中A[1]=1,A[n]=x,A[2…n-1]可以是1至k间任意一个正整数。求有多少个不同的序列,使得相邻两个数不同。
答案对10^9+7取模。

前言:
怀揣着一颗扑通扑通跳的心,看向了递推题。
我自信的告诉自己心虚的心:
“不能害怕呀,还有2个小时呢”
确实,还有两个小时。


分析:
当我们看到这道题时,便会想到:这道题是递推!

然后就是思考如何才能推出答案。
题目有约束条件:

  • a[n]的数值
  • 不能与前面的数相同

我们尝试着对约束条件来设置状态。

f [ i ] [ 0 ] i x f[i][0]表示前i个数,当前位置的数等于x的方案书

f [ i ] [ 1 ] i x f[i][1]表示前i个数,当前位置的数不等于x的方案书

对着方程,我们很同意想到f[i][1]的方程:
f [ i ] [ 1 ] = f [ i 1 ] [ 0 ] / / f[i][1]=f[i-1][0] // 由于当前是选的,所以只能由前面不选的推出来

我们尝试想f[i][0]怎么推出来:

  • 由f[i-1][1]推出

    f[i-1][1]表示前一个位置选x,f[i][0]表示当前这个位置不选x。那么很容易想,前一个位置选了x,x排除掉,当前位置不能选择x,x排除掉。我们发现k个数中就排除了一个x,那么其余k-1个数当前位置都可以填,那么可以由 f [ i 1 ] [ 1 ] ( k 1 ) f[i-1][1]*(k-1) 推出

  • 由f[i-1][0]推出
    f[i-1][0]表示前一个位置不选x,f[i][0]表示当前这个位置也不选x。那么很容易想,前一个选的数不能选且前一个数不是x,前一个数排除掉,当前位置不能选择x,x排除掉。我们发现k个数中排除了两个数,那么其余k-2个数当前位置都可以填,那么可以由 f [ i 1 ] [ 0 ] ( k 2 ) f[i-1][0]*(k-2) 推出

所以得到以下方程:
f [ i ] [ 0 ] = f [ i 1 ] [ 1 ] ( k 1 ) + f [ i 1 ] [ 0 ] ( k 2 ) f[i][0]=f[i-1][1]*(k-1)+f[i-1][0]*(k-2)


Code

#include<bits/stdc++.h>
using namespace std;
int P=1e9+7;
int n,k,x;
long long f[100010][2];

int main(){
	freopen("construct.in","r",stdin);
	freopen("construct.out","w",stdout);
    scanf("%d %d %d",&n,&k,&x);
    if (x==1) f[2][0]=k-1,f[2][1]=0;
    else f[2][0]=k-2,f[2][1]=1;
    for (int i=3;i<=n;i++){
	    f[i][1]=(f[i-1][0])%P;
	    f[i][0]=(f[i-1][1]*(k-1)+f[i-1][0]*(k-2))%P;
	}
	printf("%lld",(f[n-1][0])%P);
	return 0;
}

后记:
呵呵了,时间确实是很够,这道题我硬着头皮推了两个小时。
推翻了n个假设。
然后就凉了、。
递推大毒瘤啊!!!


这道题给我们的启发:不能写递推 当写到递推题目时,可以根据题目的一些约束条件来设置方程,在根据状态之间的关系来更新新的状态


补:

这套题确实毒瘤,第一题我用了15分钟,第二题10分钟,第三、四题60分钟,最后一题推到考试结束。。。

猜你喜欢

转载自blog.csdn.net/huang_ke_hai/article/details/90718499