2016中山市邀请赛

前言:

这次考的极差,竟然连第一题都做错,真的是脑子短路。不过失败乃成功之母,吸取教训,争取下次考好。60+20+100+100+0——呵呵

第一题:

小明今天生日,邀请了一些朋友过来开生日会。妈妈专门去买了一个大蛋糕,蛋糕为一个n*m的矩形,现在想把这个蛋糕分成1*2的小块,并且要求必须是完整的小块,不能拼接。问一共能分多少块?2016中山市邀请赛_第1张图片
Input
一行,两个正整数n,m
Output
一行,一个整数x,表示最多能分多少块

Sample Input
7 8
Sample Output
28

HINT
50% 数据 0 100% 数据 0

这道题目真的是a+b一样简单,但是我竟然能做错?想象不到吧,不是int64的问题(对于一个小牛,这个范围的问题还是知道的),那我错在什么地方?错在我看题不想题,说不能剩余,我就以为是n*(m div 2),傻叉!还可以横着啊!真傻!正解是n*m div 2.(我已无言以对)
代码:
var
    a,b:int64; 
begin
    readln(a,b); 
    writeln(a*b div 2); 
end. 

第二题:

kqp发明了一个好玩的游戏,叫czy一起玩。但czy玩了十几盘,总是输,他想知道是不是从一开始他就注定要输。这个游戏是这样的,kqp先写下一排数(既然是一排,当然有首尾咯)。kqp和czy每次只能从这排数的头或尾取一个数。最后谁取的数的和多,谁就赢了。如果两人的数的总和一样多,先取者胜。有天FW看到他们俩在玩这个游戏,很好奇。他想知道,在两人总是做出最优决策的情况下(两个人的智商都是很高的……),谁能取得最终的胜利呢?
Input
第一行为一个数k(k<=10),表示有k组测试数据;  以下k组测试数据:
每组测试数据中,第一行仅有一个偶数n(0 第二行也仅有一个数,0表示kqp先取数,1表示czy先取数 
第三行有n个数,是kqp给出的一排数。这n个数的绝对值均不超过106。
Output
对每组测试数据输出一行
表示在两人总是做出最优决策的情况下,
最终的胜利者的名字,即"kqp"或"czy"(引号不输出)。

Sample Input
2
1
1 3
2
0
1 3

Sample Output
kqp


HINT
30%,k=1,n<=10;
100%,如题所述。

这道题就是 一道“坑的不能再坑”的题目,所谓的“最优策略”应该指的是一种动态规划的决策,也就是常说的DP,但是这道题的最优策略指的是一种贪心的取法。做过usaco a game那道题的同学都知道,那道题就是用DP,而且还属于较难的动态规划。但考试时的第2题是不可能出现这种题目的,所以从这里我们应该可以推断出是用贪心。我考试时候就用了贪心,但是为什么还错?因为这是一道“坑的不能再坑的”题目,例如当前1,n两个位置选,如果先选的人取了1,则后选的人只能选n,每次只能取头和尾,不是先取的人取了1之后,后取的可以取2,这是不一样的,所以这题有多简单?就是输出先取的人的名字即可。什么都不用模拟。
代码:
var
        k,n,i,j,x,y:longint; 
begin
        readln(k); 
        for i:=1 to k do
        begin
                readln(n); 
                readln(x); 
                for j:=1 to n do
                        read(y); 
                if x=0 then writeln('kqp') else writeln('czy'); 
        end; 
end. 

第三题:

  最近备受关注的人机大战——谷歌机器人AlphaGo对战围棋大师李世石。经过五盘的对决,最终AlphaGo以4:1战胜李世石,并且使得它的排名一举上升为世界第二,仅次于中国选手柯洁。为了准备迎接柯洁的挑战,必须让AlphaGo提升自身的处理能力,但由于时间有限,仅能临时采购一些性能不一的处理器,现在知道每种处理器的处理能力和发热量,由于机器过热可能会导致AlphaGo程序崩溃,必须要控制好它的最大发热量才行,这个艰巨的任务落在你的头上,必须选出一些处理器来尽可能的提供最强的处理能力。

                         

input

第一行两个正整数n,t,表示可选择的处理器种类和最大发热量,注意,每种处理器可以采购多个,

接下来n行,每行两个正整数,分别表示每种处理器的处理能力和发热量(数值均小于100)

output

一行,一个正整数,表示AlphaGo的最大处理能力

Sample Input

3 5

2 2
4 3
1 5
Sample Output

6
HINT
50% 数据 n<=30
100% 数据 n<=300,t<=10000

这道题目其实就是一个背包问题,因为每个处理器可以采购多个,所以是完全背包问题,在这里不多讲,不会的去翻翻资料吧。

代码:

var
        f:array[0..10000] of Longint; 
        w,v:array[1..300] of Longint; 
        n,t,i,j:longint; 
begin
        readln(n,t); 
        for i:=1 to n do
                readln(w[i],v[i]); 
        for i:=1 to n do
                for j:=v[i] to t do
                        if f[j-v[i]]+w[i]>f[j] then f[j]:=f[j-v[i]]+w[i]; 
        writeln(f[t]); 
end. 

第四题:
小明的数学计算能力超强,常常在同学们面前表面得很骄傲。数学科代表实在看不下去了,决定出道很麻烦的题,好好“折磨”他一下。
数学科代表决定给他一些数,让他分组。从第一个数开始分组,且每组必须是连续的一段数,要求每组和相等,问每组和最小可以是多少。(当然这些数一定可以被分组,大不了直接分成一组。)
input
第一行为一个数N 
第二行为N个整数(每个数均小于等于1000),两个数间用空格隔开。
Output
 一行,最小的和

Sample Input
6
2 5 1 3 3 7
Sample Output
7
HINT
分成三组(2,5) (1,3,3) (7) 和为7,不存在比7更小的和。

测试点


  n


   1


  n = 10


   2


  n = 100


   3


  n = 1000


   4


  n = 200000


   5


  n = 200000


   6


n = 1000000


   7


n = 1000000


   8


n = 1000000


   9


n = 1000000


   10


n = 1000000


这道题目我在考场上想的是,先枚举分成多少组,当然是从多至少(分的组越多和就越小)所以,当我枚举完有多少组之后我就开始二分了,如何二分?先求一个前缀和,例如
a  =[2 5 1 3 3 7]
sum=[2 7 8 11 14 21]

s:=k/i
s表示的是当前分成i组,每一组的和。
然后用s看在sum数组里面的哪里,如果能找到,就继续判断下一组是否可以找到。
代码:
var
        flag:boolean; 
        f:array[0..1000000] of Longint; 
        a:array[1..1000000] of Longint; 
        n,i,j,k,sum,l,r,mid:longint; 
begin
        readln(n); 
        for i:=1 to n do
        begin
                read(a[i]); 
                inc(sum,a[i]); 
                f[i]:=f[i-1]+a[i]; 
        end; 
        for i:=n downto 1 do
        begin
                if sum mod i<>0 then continue; //这里同样也可以加一个与下面方法相同的优化。
                k:=sum div i; 
  
                flag:=true; 
                l:=1; 
                for j:=1 to i do	//依次判断i组是否可以组成.
                begin
                        r:=n; 
                        while l<=r do
                        begin
                                mid:=(l+r) div 2; 
                                if f[mid]=k then break; 
                                if f[mid]>=k then r:=mid-1 else l:=mid+1; 
                        end; //二分是找当前这一组的和是否能组成.
                        if f[mid]<>k then
                        begin
                                flag:=false; 
                                break; 
                        end; 
                        inc(k,sum div i); //能组成就下一组,同时更新要找的数以及上限l.
                        l:=mid+1; 
                end; 
  
                if flag then break; //如果这i组都可以组成则直接break.
        end; 
        if flag then writeln(sum div i) else writeln(sum); 
end.
但是这样子做,时间是很紧的,接近超时,考试能AC考的是大大的rp,但其实还有一种方法:
可以不用前缀和+二分,直接模拟出他累加的和就行了,如果累加的时候大于s则break。但是要加一个小优化,就是如果当前s都小于a数组的最大值了,就没必要模拟下去。
代码:
var
        i,j,n,tot,sum,s,max:longint; 
        a:array[0..1000000] of longint; 
begin
        readln(n); 
        for i:=1 to n do
        begin
                 read(a[i]); 
                 inc(sum,a[i]); 
                 if a[i]>max then max:=a[i]; 
        end; 
        for i:=n downto 1 do
        begin
                 if (sum mod i<>0) or (sum div ithen continue; //这里就是优化和pd
                 s:=sum div i; 
  
                 tot:=0; 
                 for j:=1 to n do
                 begin
                          inc(tot,a[j]); 
                          if tot>s then break; 
                          if tot=s then tot:=0; 
                 end; //这里直接模拟.
                 if tot=0 then break; 
        end; 
        if tot=0 then writeln(s) else writeln(sum); 
end.

第五题:
兰姐姐是来自火星的女王。相信你们一定对兰姐姐不熟悉,她统领整个火星,在各方面拥有最高权力。很久很久以前,兰爸爸是火星的国王,去世以后,两个女儿争夺王位。火星上最聪明的人是辣椒酱,他帮助兰姐姐夺得了王位,而兰姐姐的姐姐Horse没有得到王位,便离开火星前往地球修行。几年后,兰姐姐越来越思念姐姐,便决定到地球上找姐姐。今天,她找到了自己失散已久的姐姐Horse的家,但是要进门就必须答对一个大难题,作为一个大犇犇犇,她很快就解出来了,你行吗?
题目是这样的:现在有一个序列a,a的长度为n,一开始a[i]=i(1≤i≤n),现在有m个操作,每个操作的格式是这样的:x  y表示把当前的a[x]与a[y]交换。我们把这m个操作叫做一轮操作,现在问,在经过多少轮操作之后,序列a又会回到原来的样子(原来的样子就是指a[i]=i(1≤i≤n))
Input
第一行,两个整数n,m,n表示a的长度,m表示操作数
接下来m行,每行一个操作x y,表示把当前的ax与ay交换保证(1≤x,y≤n)
Output
只有一个数,表示在经过多少轮之后,序列a又会回到原来的样子

Sample Input

4 4
1 4
3 4
2 3
1 4

Sample Output

3

HINT
50%数据保证1≤n,m≤1000,答案小于等于1000
100%数据保证1≤n,m≤500000,答案小于等于2^31-1


这道题目考试时没捞到1分,就是因为读题不仔细,当然也有一部分题目描述不完整的情况,但是,为什么其他人能明白,归根到底还是自身的问题,已经说了是多少轮操作之后才会得到原来的序列,一轮操作——必定是完整的一轮,不然怎么叫一轮操作,那就叫一些操作。

所以这题用模拟的方法可以拿到50分,但是我们可以进一步优化。

我们先用数据来模拟交换的一轮:

1 2 3 4

4 2 3 1

4 2 1 3

4 1 2 3

3 1 2 4

我们可以留下一轮繁琐操作的本质,也就是1个位置换来换去,到底换到了哪儿。

f[1]=1,2

f[2]=2,3

f[3]=3,1

f[4]=4,4

f[i]表示的是第i个位置一轮操作之后换到了f[i]这个位置。

我们可以发现4他是不换的,所以4要回到原来的位置只用1次。而1,2,3可以发现他们是组成了一个循环

1-2-3-1,所以1,2,3回到原来的位置只用3次,他们的最小公倍数gbs(1,3)=3,所以答案=3


再举一个栗子:

5 3

1 2

2 3

4 5

交换一轮:

1 2 3 4 5

2 1 3 4 5

2 3 1 4 5

2 3 1 5 4

f[1]=1,3

f[2]=2,1

f[3]=3,2

f[4]=4,5

f[5]=5,4

1,2,3组成了一个循环1-3-2-1

4,5组成了一个循环4-5-4

1,2,3用3次,4,5用2次,gbs(3,2)=6

从这里我们就可以发现,其实我们只要看f数组组成了多少个循环,然后求出n个循环个数的公倍数即可。


代码:

type
        arr=array[1..500000] of int64; 
var
        a:arr; 
        f:Array[1..500000,0..1] of longint; 
        x,y:Array[1..500000] of Longint; 
        bz:array[1..500000] of boolean; 
        n,m,ans,answer,t,tot:int64; 
        i,j:Longint; 
function gcd(x,y:int64):int64; 		//求出gbs——公倍数.
var
        z,xx,yy:int64; 
begin
        xx:=x; yy:=y; 
        while x mod y<>0 do
        begin
                z:=x mod y; 
                x:=y; 
                y:=z; 
        end; 
  
        exit(xx*yy div y); 
end; 
procedure sort(l,r:Longint); 		//快排用于方便模拟.
var
        i,j,mid,p:longint; 
begin
        i:=l; j:=r; 
        mid:=f[(i+j) div 2,0]; 
        while ido
        begin
                while f[i,0]do inc(i); 
                while f[j,0]>mid do dec(j); 
                if i<=j then
                begin
                        p:=f[i,0]; f[i,0]:=f[j,0]; f[j,0]:=p; 
                        p:=f[i,1]; f[i,1]:=f[j,1]; f[j,1]:=p; 
                        inc(i); dec(j); 
                end; 
        end; 
        if lthen sort(l,j); 
        if ithen sort(i,r); 
end; 
  
begin
        readln(n,m); 
        for i:=1 to m do readln(x[i],y[i]); 
        for i:=1 to n do a[i]:=i; 
  
        for i:=1 to m do
        begin
                 t:=a[x[i]]; a[x[i]]:=a[y[i]]; a[y[i]]:=t; 		//交换一轮后的根本.
        end; 
        for i:=1 to n do
        begin
                f[i,0]:=a[i]; 
                f[i,1]:=i; 
        end; 								//判断出第i个位置到底是换到了哪里,表示为f[i,1].
        sort(1,n); 
  
        fillchar(bz,sizeof(bz),true); 
        answer:=1; 
        for i:=1 to n do
                if bz[i] then						//优化——已经有在循环里的数,无需再次判断。
                begin
                        j:=f[i,1]; 
                        ans:=1; 
                        while j<>i do					//while 循环用于找当前第i个数是在哪个循环,ans用来记录这个循环每个数要回到自己位置的个数.
                        begin
                                bz[j]:=false; 
                                j:=f[j,1]; 
                                inc(ans); 
                        end; 
                        answer:=gcd(answer,ans); 
                end; 
        writeln(answer); 
end.

小结:
这次考试考得不好,但收获才是最重要的,通过这次比赛,我明白了思维不要局限于1面,越广越好,像第1题犯得这种错误,以后不能再犯,还有从这次比赛结束后,我需要对自己进行几点要求:
1、做题时要快。根据题目的难度与实现的时间应该成正比,难的题目要能快速想到方法,简单的题目要能快速实现。
2、保证少提交次数。也就是在最少的次数AC一道题目,因为在考试时只有一次提交的机会,平时做题时如果不严谨,做的马马虎虎就提交,也不看优化数据之类的,考试时就惨了。
3、这次比赛还是没有做到心无杂念,总想着要拿第一或者怎样,但是这样子往往会影响我的正常发挥,希望下次能不要想太多,仔仔细细的做每一道题就好了, 名次无谓。

你可能感兴趣的:(比赛题解)