本人是潍坊一中的王煜伟,69级,今年高一,
现在马上就要NOIP了, 打算把历年的NOIP普及、提高组题目都做一下, 然后写写解题报告∵这个报告主要是给初中同学看的,所以我会写的详细一点
这道题其实很简单, 意思就是说给你一些数 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...n,如果ai+aj这个数在集合中存在,那么you[ai+aj]←true,然后再从a1到an做一次扫描,只要you[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分
这道题目是说,给定A和B,求解一组A’和B’,满足以下条件:
A′B′−AB≥0
0<A′,B′≤L
A′和B′ 互质
首先,想一个总体的框架:
我们发现 L≤100 ,因此可以枚举 A′和B′ ,然后判断是否A’B’满足上述条件,并且打擂台求比值最小的一组就行了,打擂台的复杂度是 O(1) 。设验证的复杂度为 O(k) ,则总的算法的复杂度为 O(kL2) ,其中 L2是104 ,所以我们只要保证k的大小在100以内就一定没有问题。
现在要求两个分数的差值,该怎么办呢?高精除!很多人一下就想到了,当时我在赛场上就是这么想的,但是又仔细一考虑。。。。。首先,高精除有风险,而且如果我是出题者的话,我一定会卡高精除,第二,高精除的编程复杂度很高,很容易出错而且耗时间
于是我重新读题,找寻一些特殊的切入点,终于看到了这个东西: 1≤A,B≤106 ,我的脑袋里瞬间就萌生出一种想法:模拟手动比较分数——就是说如果你要比较两个分数,就先把他们通分,然后比较分子的大小,如 ab和cd 比较,先把它们化成 adbd和bcbd 的形式,然后比较ad和bc的大小,而在整个枚举的过程中,你最大的情况只需要比较 A′B′和AB的大小 ,而且他们分母的乘积最大是 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分
当我看到这道题的时候,有一种很熟悉的感觉。。。。然后我就想: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;
}
说白了,这道题还是在模拟,只不过换了种方式来模拟,加入了一些小小的优化,我觉得这道题的坑度等级还是很高的,它让人看了有种很想当然的感觉,会情不自禁地认为:“这道题我会”,结果很多人都爆零了,真是惨痛的教训啊!
另外,从这道题中我学会了一种方法,那就是像这种数字大规模出现的题目应该手动写写,找找规律,这样有助于解决问题
这道题要好好说说
给出如下定义:
子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵。
例如,下面左图中选取第 2、4 行和第 2、4、5 列交叉位置的元素得到一个 2*3 的子矩阵如右图所示。
相邻的元素:矩阵中的某个元素与其上下左右四个元素(如果存在的话)是相邻的。
矩阵的分值:矩阵中每一对相邻元素之差的绝对值之和。
本题任务:给定一个 n 行 m 列的正整数矩阵,请你从这个矩阵中选出一个 r 行 c 列的 子矩阵,使得这个子矩阵的分值最小,并输出这个分值。
对于 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]表示已经选择了i个数,并且最后一个的编号为j 所付出的最小代价,那么有状态转移方程:
f[i][j]=min(f[i−1][k]+hc[k][j])+zc[j])
(1≤i≤N,1≤j≤j,1≤k<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所带给人们的吧。。。。。。