NOIP2014普及组解题报告 ----王煜伟

本人是潍坊一中的王煜伟,69级,今年高一,
现在马上就要NOIP了, 打算把历年的NOIP普及、提高组题目都做一下, 然后写写解题报告∵这个报告主要是给初中同学看的,所以我会写的详细一点

NOIP2014普及组解题报告

Prolem 1 珠心算测试(count)

这道题其实很简单, 意思就是说给你一些数 a1,a2,a3,a4...an
然后让你回答有多少个A+B=C(A ≠ B ≠ C)满足(回答C的数量,而不是等式的数量)

方法一

那么有一种很明显的做法就是三层循环枚举C、A、B,
注意:C是在最外层,若找到了一个A和一个B,满足上述等式,则C是一个符合要求的解,这时ans++,并且退出当前枚举,枚举下一个C,这种算法的时间复杂度是 O(N3)

而我当时没想到这个算法,因为有更好用而且简单更不容易出错的解法,

方法二

i=1...n,j=i+1...nai+ajyou[ai+aj]truea1anyou[ai]ans++

这个算法的好处在于它很好写,不用退出什么的,也不用注意循环的顺序,而且时间复杂度是 O(N2)

代码(方法2):

#include<cstdio>

using namespace std;

int n, a[101], i, j, count;
bool you[20001]={false};

int main()
{
    freopen("count.in","r",stdin);
    freopen("count.out","w",stdout);

    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%d",&a[i]);

    for(i=1;i<n;i++)
        for(j=i+1;j<=n;j++)
            you[ a[i]+a[j] ]=true;
    count=0;
    for(i=1;i<=n;i++)
        count += you[ a[i] ];

    printf("%d\n",count);

    return 0;
}

在此征求一下大神的意见,如有更快的做法,敬请奉上

小结:

这道题很简单,但很多人没有做对的原因就是没有好好理解题意,但是根本原因其实还在于心态太骄傲了,认为是第一题就可以轻视,这样是不好的,水题我们更要做好啊,你想想同样是100分,这100分多么好拿,所以是水题、越该放平心态,细心地做。当时我正是由于重视(2013年第一题爆零的教训),用了整整15分钟才做好,最后得了100分

Problem 2 比例简化

这道题目是说,给定A和B,求解一组A’和B’,满足以下条件:

ABAB0
0<A,BL
AB 互质

首先,想一个总体的框架:
我们发现 L100 ,因此可以枚举 AB ,然后判断是否A’B’满足上述条件,并且打擂台求比值最小的一组就行了,打擂台的复杂度是 O(1) 。设验证的复杂度为 O(k) ,则总的算法的复杂度为 O(kL2) ,其中 L2104 ,所以我们只要保证k的大小在100以内就一定没有问题。
现在要求两个分数的差值,该怎么办呢?高精除!很多人一下就想到了,当时我在赛场上就是这么想的,但是又仔细一考虑。。。。。首先,高精除有风险,而且如果我是出题者的话,我一定会卡高精除,第二,高精除的编程复杂度很高,很容易出错而且耗时间
于是我重新读题,找寻一些特殊的切入点,终于看到了这个东西: 1A,B106 ,我的脑袋里瞬间就萌生出一种想法:模拟手动比较分数——就是说如果你要比较两个分数,就先把他们通分,然后比较分子的大小,如 abcd 比较,先把它们化成 adbdbcbd 的形式,然后比较ad和bc的大小,而在整个枚举的过程中,你最大的情况只需要比较 ABAB ,而且他们分母的乘积最大是 108 ,到此,问题就完美地解决了!

贴上代码:

#include<cstdio>
#include<iostream>

using namespace std;

long A,B,a,b,L,besta,bestb;
long long cha_zi, cha_mu, best_cha_zi, best_cha_mu=-1, t1, t2;

void Minus(long long z1, long long m1, long long z2, long long m2, long long &cz, long long &cm)
{   
    z1=z1*m2;
    z2=z2*m1;
    m1=m2=m1*m2;
    cm=m1;
    cz=z1-z2;
}

bool huzhi(int x, int y)
{
    int max, i;
    if(x>y)max=x;
    else max=y;

    for(i=2; i*i<=max; i++)
    {
        if(x%i==0 && y%i==0)
            return false;
    }
    return true;
}

int main()
{
    freopen("ratio.in","r",stdin);
    freopen("ratio.out","w",stdout);

    cin >> A >> B >> L;

    for(a=1;a<=L;a++)
    {
        for(b=1;b<=L;b++)
        {
            if( huzhi(a,b)==true)
            {
                Minus(a,b,A,B,cha_zi,cha_mu);
                if(cha_zi>=0)
                {
                    Minus(best_cha_zi,best_cha_mu,cha_zi,cha_mu,t1,t2);
                    if(t1>0 || best_cha_mu==-1)
                    {
                        best_cha_zi=cha_zi;
                        best_cha_mu=cha_mu;
                        besta=a;
                        bestb=b;
                    }
                }
            }
        }
    }

    cout << besta << " " << bestb << endl;
    return 0;
}

这段代码是我初中时写的,有不足之处还请见谅。。

小结

此道题放在第二个挺合适,特点是代码好写,但方法不是特别好想(与历年相比),只要按部就班一步一步地来,这道题还是很容易的,我的分数是100分

Problem 3 螺旋矩阵

当我看到这道题的时候,有一种很熟悉的感觉。。。。然后我就想:NOIP怎么会考这么简单的题目?然后。。。。就用了模拟的方法,调了很长时间,但是结果很惨,爆零了(因为直接开了30000*30000的数组),到现在我也不明白当时为什么那么傻。。。。
好了回归正题,这道题是有技巧的
观察这个矩阵
1 2 3 4
12 13 14 5
11 16 15 6
10 9 8 7
想想我们模拟的时候是1 2 3 4,然后转弯5 6 7,然后转弯8, 9, 10以此类推,其实我们走了很多不必要的路,比如从1到4,我们可以直接加3,然而怎样知道应该加上几呢?试一试:
1+3=4
4+3=7
7+3=10
10+2=12
12+1=13
13+1=14
14+1=15
15+1=16
16+0=0
咦?有规律!
对于每个“圈”,比如说从1到12,这里面的规律是可以找到的,从1开始,依次加3、3、3、2,而从一个圈到下一个圈的时候,横坐标要+1,然后是+1、+1、+1、+0,规律很容易看出,从这个“圈”的左上角开始,依次加k、k、k、k-1(要搞明白横纵坐标),然而k就是上一个“圈”的k-2得到的,到此问题就解决了
代码:

#include<cstdio>
#include<iostream>
using namespace std;
int n,I,J,i,j,k,t,clk;
long x;
int main()
{
    freopen("matrix.in","r",stdin);
    freopen("matrix.out","w",stdout);
    scanf("%d%d%d",&n,&I,&J);
    x=0;
    i=1;
    j=0;
    for(k=0;k<n/2+n%2;k++)
    {
        if(i!=I)
        {
            j=j+(n-2*k);
            x=x+(n-2*k);
        }
        else
        {
            x=x+(J-j);
            break;
        }
        if(j!=J)
        {
            i=i+(n-2*k-1);
            x=x+(n-2*k-1);
        } 
        else
        {
            x=x+(I-i);
            break;
        }
        if(i!=I)
        {
            j=j-(n-2*k-1);
            x=x+(n-2*k-1);
        }
        else
        {
            x=x+(j-J);
            break;
        }
        if(j!=J)
        {
            i=i-(n-2*k-2);
            x=x+(n-2*k-2);
        }
        else
        {
            x=x+(i-I);
            break;
        }
    }
    return 0;
}

小结

说白了,这道题还是在模拟,只不过换了种方式来模拟,加入了一些小小的优化,我觉得这道题的坑度等级还是很高的,它让人看了有种很想当然的感觉,会情不自禁地认为:“这道题我会”,结果很多人都爆零了,真是惨痛的教训啊!
另外,从这道题中我学会了一种方法,那就是像这种数字大规模出现的题目应该手动写写,找找规律,这样有助于解决问题

Problem 4 子矩阵

这道题要好好说说

给出如下定义:
子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵。
例如,下面左图中选取第 2、4 行和第 2、4、5 列交叉位置的元素得到一个 2*3 的子矩阵如右图所示。
相邻的元素:矩阵中的某个元素与其上下左右四个元素(如果存在的话)是相邻的。
矩阵的分值:矩阵中每一对相邻元素之差的绝对值之和。
本题任务:给定一个 n 行 m 列的正整数矩阵,请你从这个矩阵中选出一个 r 行 c 列的 子矩阵,使得这个子矩阵的分值最小,并输出这个分值。
NOIP2014普及组解题报告 ----王煜伟_第1张图片
对于 50%的数据,1 ≤ n ≤ 12, 1 ≤ m ≤ 12, 矩阵中的每个元素 1 ≤ a[i][j] ≤20;
对于 100%的数据,1 ≤ n ≤ 16, 1 ≤ m ≤ 16, 矩阵中的每个元素 1 ≤ a[i][j] ≤1000,1 ≤ r ≤ n, 1 ≤ c ≤ m

这道题乍一眼看来就是爆搜,然而爆搜要枚举行和列,枚举的次数最大约是
8!8=403202=1625702400
很明显超时,但是我们发现只枚举一层为8!=40320还是很小的,因此可以先枚举一层,另一层再考虑别的方法
想一想,如果选择哪些行已经确定了,那现在问题就变为在矩阵上选择一些列,使得分值最大
假如枚举上图所示的行,那么矩阵变成

9 4 8 7 4
6 8 5 6 9

现在选一些列,使得分值最大,如果我选择了某一列,那么代价就是这列的纵向的分值的差的绝对值之和,加上它和上一个选择的列的横向对应的权值的绝对值之和,那么现在这个问题就完全变成一个一维的问题了,我们把选择某一列付出的纵向的代价叫做纵差( zc[i] ),选择第i列和第j列所付出的横向的代价叫做 hc[i][j](i<j) ,现在把它看成一个一维的数列,选择一个数,要付出的代价就是 zc[i]+hc[last][i] ,last表示上一个选择的数,而这一个是否具有子结构最优性质呢?
设计状态 f[i][j]ij 所付出的最小代价,那么有状态转移方程:
f[i][j]=min(f[i1][k]+hc[k][j])+zc[j])
(1iN,1jj,1k<j)
枚举的复杂度是 O(N2)! ,而DP的时间复杂度是 O(N3)
总的是 O((N2)!N3)
至此,问题解决了

代码奉上

#include<cstdio>
#include<cstring>
#define oo 2000000000
using namespace std;
int N, M, R, C, zc[17], hc[17][17], a[17][17];
long ans, maxz;
int abs(int n)
{
    if(n<0)n=-n;
    return n;
}
long min(long a, long b)
{
    if(a<b)return a;
    else return b;
}
int geshu(long z)
{
    int count=0;
    while(z>0)
    {
        count+=z%2;
        z=z>>1;
    }
    return count;
}
void qiu_zchc(long z)
{
    int i,j,k,num[17],size,x;
    x=1;
    size=0;
    while(z>0)
    {
        if(z%2==1)num[++size]=x;
        x++;
        z=z>>1;
    }
    memset(zc,0,sizeof(zc));
    for(i=1;i<=M;i++)
    {
        for(j=1;j<size;j++)
            zc[i]+=abs( a[ num[j] ][i] - a[ num[j+1] ][i] );
    }
    memset(hc,0,sizeof(hc));
    for(i=1;i<=M;i++)
    {
        for(j=i;j<=M;j++)
        {
            for(k=1;k<=size;k++)
            {
                hc[j][i] += abs( a[ num[k] ][i] - a[ num[k] ][j]);
            }
        }
    }
}
long DP(long z)
{
    qiu_zchc(z);
    long f[17][17], Min=oo, min;
    int i,j,k;
    for(i=1;i<=M;i++)
        f[1][i]=zc[i];
    for(i=2;i<=C;i++)
    {
        for(j=1;j<=M;j++)
        {
            min=oo;
            for(k=1;k<j;k++)
                if( f[i-1][k]+hc[j][k] < min)min=f[i-1][k]+hc[j][k];
            f[i][j] = min + zc[j];
        }
    }
    for(j=1;j<=M;j++)
        if(f[C][j]<Min)Min=f[C][j];

    return Min;
}
int main()
{
    freopen("submatrix.in","r",stdin);
    freopen("submatrix.out","w",stdout);
    int i,j;
    long t, z;
    scanf("%d%d%d%d",&N,&M,&R,&C);
    for(i=1;i<=N;i++)
        for(j=1;j<=M;j++)
            scanf("%d",&a[i][j]);
    maxz=1;
    for(i=1;i<=N;i++)maxz=maxz*2;
    maxz=maxz-1;
    ans=oo;
    for(z=1;z<=maxz;z++)
    {
        if(geshu(z)==R)
        {
            t=DP(z);
            if(t<ans)ans=t;
        }
    }
    printf("%ld\n",ans);
    return 0;
}

是初中时候写的。。。。当时只会用拼音,数学函数也不会用,还请见谅

小结

这道题的思维量已经上升了一个台阶,它对初中选手的分析能力提出了很高的要求,比起历年的第四题,是一道很有水平的题目,当时我们大山东只有一个人得了100分,(然而第一名这个才得了5分),我表示如果把这个放在提高组day1的最后一题的话也毫不过分,所以说是道好题
从这个题中,我积累了一个经验,其实有时候拿一下部分分还是可以的,当时我在赛场上犹豫不决,最后还剩下30分钟的时候我做完了前三道,在犹豫到底做不做这一道,结果犹豫着30分钟就过去了,不仅前面写的代码一点没动,这道题也没敲出来,本来或许能拿50分的梦想也破灭了,所以说,以后比赛时该那部分分就那部分分,做不出满分也没什么,总比爆零要好

总结

2014年的比赛,我上初三,其中第一题100分,靠的是稳中求胜,第二题仍然是100分,考的也是稳中求胜,然而由于比赛的前一晚心理压力太大,几乎是彻夜未眠,做到第三题时,就感觉精力耗尽。。。疲惫不堪,糊糊涂涂地就爆零了,第四题更是因为犹豫不决,连部分分都没拿到。纵观我初中的NOIP生涯,是110、30、200,经历了大起大落,我感觉我学到了很多、也成熟了很多,最后的一次,我在我们大山东省排到了12名,虽然抱有无尽的遗憾,但人生也就是这样,没有什么事情会向我们想象的那么圆满,只要平常不断训练自己的心理素质、提高自己的能力,然后比赛时正常发挥就行了,超高的受挫能力、强大的分析能力、还有丰富的人生经历。。。。这也许就是OI所带给人们的吧。。。。。。

你可能感兴趣的:(NOIP2014普及组解题报告 ----王煜伟)