【用膝盖写代码系列】(1):NOIP2010普及组复赛详解

我知道大家都是苦逼党,照顾到大家,我特地教大家如何用膝盖写代码
这里写图片描述

第一题:数字统计
题意简述:请统计某个给定范围[L, R]的所有整数中,数字 2 出现的次数。
陷阱提示:这里的”数字2”指的是每一位上的2.如“22”要统计两次2

数据范围:1 ≤ L ≤R≤ 10000。

我对它的类型评估:暴力,模拟

思路描述:这道题直接暴力就OK:因为数据范围是1~10000,所以我们只要用一个字符数组s[5]来存储这个数字即可。
怎么存储呢?给大家一个规律(设输入数字为n):
万位: i/10000;
千位: i/1000%10;
百位: (i/100%100)%10;
十位:(((i/10%1000)%100)%10);
个位:(((((i%10000))%1000)%100)%10);
那么代码还需要怎么改动呢?
我的代码:

#include <cstdio>

int main(){
    int i,j,n,m;
    int l,r,count=0;
    int s[6];
    scanf("%d%d",&l,&r);
    for(i=l;i<=r;i++){
        s[1] = i/10000;
        s[2] = i/1000%10;
        s[3] = (i/100%100)%10;
        s[4] = (((i/10%1000)%100)%10);
        s[5] = (((((i%10000))%1000)%100)%10);
        for(j=1;j<=5;j++) {
            if(s[j] == 2) count++;
        }
    }
    printf("%d",count);
}

洛谷原题:http://www.luogu.org/problem/show?pid=1179
第一题完。

第二题:接水问题
题目简述:给定n个人的接水所需时间w以及水龙头m的数量,求所有人接完水的时间。

陷阱提示:这道题千万不能模拟每一秒,会超时!

数据范围:1≤n≤10000,1≤m≤100 且 m≤n;1≤wi≤100。

我对它的类型评估:模拟,贪心

思路描述:假设每一个水龙头的出水量为L(初始化都为0),那么每一次输入一个w,就去找出水量最小的水龙头。然后这个水龙头的出水量设置为L[i]+w。最后找到L的最大值,这个值就是答案。
为什么?这里假设每个人的接水时间全部算在水龙头上。每一次有人要接水时,必定是找目前出水量最小(即目前最快的一个水龙头),而最后那个人离开后程序才能算结束。所以要求最后那个人离开的时间,即出水量最大的水龙头。
我的代码

#include <cstdio>
#include <algorithm>
using namespace std;
#define max(a,b) a>b?a:b
int water[10001];
int main(){
    int min = 1,ans=-1;
    int i,j,n,m,w;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++){
        scanf("%d",&w);
        min = 1;
        for(j=2;j<=m;j++){
            if(water[j]<water[min]){
                min = j;
            }
        }
        water[min] +=w;
        ans = max(ans,water[min]);
    }
    printf("%d",ans);
}

在考场上怎么发挥?
先注意数据范围,千万不要看见题目就开始暴力模拟。如果有类似的模拟题,一定要注意有什么优秀方法可以解决。如这道题使用的是贪心法,有些题可以递归解决。
总结一句:暴力模拟是最后万不得已的时候才使用的下下策!

洛谷原题:http://www.luogu.org/problem/show?pid=1190

第二题完.

第三题:导弹拦截(2010版)
题意简述:给定两个大点的坐标以及n个小点的坐标,使两个大点作为圆心所构成的圆的面积可以覆盖所有点,再这个条件下使半径的平方和最小。

陷阱提示:不要天真地认为判断一个点离大点A近还是离大点B近就能AC

数据范围:
对于10%的数据,N = 1
对于20%的数据,1 ≤ N ≤ 2
对于40%的数据,1 ≤ N ≤ 100
对于70%的数据,1 ≤ N ≤ 1000
对于100%的数据,1 ≤ N ≤ 100000,且所有坐标分量的绝对值都不超过1000。

我对它的类型评估:动态规划,贪心,模拟

思路描述:本着骗分导论的精神我来说一说怎么骗到一点点分(我只会AC做法,40分做法做法抱歉了各位:-])
40分做法:就像我上面说的,判断一个点离A点近还是离B点近,离哪一个近就将这个点放到哪一个点的集合中,然后更新半径。
40分代码

#include <cstdio>
#include <algorithm>
#include <cmath>

#define power(a) a*a
#define max(a,b) a>b?a:b

struct point {
    int x,y;
}a[100001];

int GetDiv(int x1,int y1,int x2,int y2){
    //printf("(%d,%d) (%d,%d)\n",x1,y1,x2,y2); 
    return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}

int main(){
    int i,j,n,m;
    int l,r;
    int x1,y1,x2,y2;
    int r1=0,r2=0;
    scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&n);
    for(i=0;i<n;i++){
        scanf("%d%d",&a[i].x,&a[i].y);
        //printf("x=%d,y=%d\n",a[i].x,a[i].y); 
    }
    for(i=0;i<n;i++){
        l = GetDiv(x1,y1,a[i].x,a[i].y);
        r = GetDiv(x2,y2,a[i].x,a[i].y);
        //printf("l=%d,r=%d\n",l,r);
        if(l < r) {
            r1 = max(r1,l);
        }
        else{
            r2 = max(r2,r); 
        }
    }
    printf("%d",r1+r2);
}

AC做法:我们先按每一颗导弹到点A的距离排一个序,然后枚举某个点P,将这个点看做断点。即点P前的点归入点A中,点P后的点归入点B中。我们枚举一下点,然后求出最小的平方和即可。
(距离公式: (x21x22)+(y21y22) ,这道题比较特殊,不需要开根号!(调了我一个多小时=.=))
AC代码

#include <cstdio>
#include <algorithm>
#include <cmath>

#define min(a,b) a<b?a:b
#define max(a,b) a>b?a:b

struct point {
    int div1,div2;
    bool operator < (const point &a)const{
        return div1<a.div1;
    }
}a[100001];

int GetDiv(int x1,int y1,int x2,int y2){
    //printf("(%d,%d) (%d,%d)\n",x1,y1,x2,y2); 
    return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}

int main(){
    int i,j,n,m;
    int x1,y1,x2,y2;
    int r1=0,r2=0;
    int x,y;
    scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&n);
    for(i=1;i<=n;i++){
        scanf("%d%d",&x,&y);
        a[i].div1 = GetDiv(x,y,x1,y1);
        a[i].div2 = GetDiv(x,y,x2,y2);
    }
    std::sort(a+1,a+1+n);
    int ans = a[n].div1,r=0;
    for(i=n;i>=1;i--){
        ans = min(ans,a[i].div1+r);
        r = max(r,a[i].div2);
    }
    printf("%d",ans);
}

在考场上怎么发挥?
首先,这一道题一看就是一道动态规划题目(实际上是贪心),所以要冷静推出转移方程或者做题方法。考场上如果时间来不及,可以写一个40分的代码提交。而且要熟记公式,如果这时候距离公式忘掉了就悲哀咯。
总结一句话:熟记公式,细推正解

洛谷原题:http://www.luogu.org/problem/show?pid=1158

第三题完。

第四题:三国游戏
题目(原题):
小涵很喜欢电脑游戏,这些天他正在玩一个叫做《三国》的游戏。

在游戏中,小涵和计算机各执一方,组建各自的军队进行对战。游戏中共有 N 位武将(N为偶数且不小于 4),任意两个武将之间有一个“默契值”,表示若此两位武将作为一对组合作战时,该组合的威力有多大。游戏开始前,所有武将都是自由的(称为自由武将,一旦某个自由武将被选中作为某方军队的一员,那么他就不再是自由武将了),换句话说,所谓的自由武将不属于任何一方。

游戏开始,小涵和计算机要从自由武将中挑选武将组成自己的军队,规则如下:小涵先从自由武将中选出一个加入自己的军队,然后计算机也从自由武将中选出一个加入计算机方的军队。接下来一直按照“小涵→计算机→小涵→……”的顺序选择武将,直到所有的武将被双方均分完。然后,程序自动从双方军队中各挑出一对默契值最高

的武将组合代表自己的军队进行二对二比武,拥有更高默契值的一对武将组合获胜,表示两军交战,拥有获胜武将组合的一方获胜。

已知计算机一方选择武将的原则是尽量破坏对手下一步将形成的最强组合,它采取的具体策略如下:任何时刻,轮到计算机挑选时,它会尝试将对手军队中的每个武将与当前每个自由武将进行一一配对,找出所有配对中默契值最高的那对武将组合,并将该组合中的自由武将选入自己的军队。 下面举例说明计算机的选将策略,例如,游戏中一共有 6 个武将,他们相互之间的默契值如下表所示:
武将默契值
双方选将过程如下所示:

小涵想知道,如果计算机在一局游戏中始终坚持上面这个策略,那么自己有没有可能必胜?如果有,在所有可能的胜利结局中,自己那对用于比武的武将组合的默契值最大是多少? 假设整个游戏过程中,对战双方任何时候均能看到自由武将队中的武将和对方军队的武将。为了简化问题,保证对于不同的武将组合,其默契值均不相同。

陷阱提示:输入时只是一个三角形,要把它补成一个矩形,像题面中的图一样。

我对它的类型评估:博弈论,贪心

思路描述:这一道题是要重点讲的(毕竟是最后一题),这道题看似题面很复杂,但其实可以进行题面简化:
给出n个人的关系值,求你在不能完成最大搭配的情况下所能做到的最大搭配
然后再简化一遍就是正解写法:
给出n个人的关系值,求每个人的次大关系值
题面一简化完,整个代码就好写了。
然后是小源的取胜问题。
这里有一个规律:计算机只是尝试打破小源的阵形,并没有要获胜,所以小源是必胜的。
那么这道题的整个代码就出来了。
我的代码:

#include <cstdio>
#include <cstring>

#define cmax(a,b) a>b?a:b

int main(){
    int i,j,n,m;
    scanf("%d",&n);
    int node[501][501];
    int ans = 0;
    for(i=1;i<=n-1;i++){
        for(j=i+1;j<=n;j++){
            scanf("%d",&node[i][j]);
            node[j][i] = node[i][j];
        }
    }
    int max,min;
    for(i=1;i<=n;i++){
        max = min = 0;
        for(j=1;j<=n;j++){
           if(node[i][j] > max){
               min = max;
               max = node[i][j];
           }
           else 
            if(node[i][j] >min) 
                min = node[i][j]; 
           ans = cmax(min,ans);
        }
    }
    printf("1\n%d",ans);
}

在考场上怎么发挥?
最重要的就是尽可能地简化题面,找出题目中的规律。如果你把一道题的题面简化到不能再简化,恭喜你,你想出正解了√
一句话总结:缩!缩!缩题面!

洛谷原题:http://www.luogu.org/problem/show?pid=1199

本次的用膝盖写代码系列到此完结~
期待下一期吧~

这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值