T3:
题目描述
【背景】
在双人对决的竞技性比赛,如乒乓球、羽毛球、国际象棋中,最常见的赛制是淘汰赛和循环赛。前者的特点是比赛场数少,每场都紧张刺激,但偶然性较高。后者的特点是较为公平,偶然性较低,但比赛过程往往十分冗长。 本题中介绍的瑞士轮赛制,因最早使用于 1895 年在瑞士举办的国际象棋比赛而得名。它可以看作是淘汰赛与循环赛的折衷,既保证了比赛的稳定性,又能使赛程不至于过长。
【问题描述】
2*N 名编号为1~2N 的选手共进行R 轮比赛。每轮比赛开始前,以及所有比赛结束后,都会按照总分从高到低对选手进行一次排名。选手的总分为第一轮开始前的初始分数加上已参加过的所有比赛的得分和。总分相同的,约定编号较小的选手排名靠前。
每轮比赛的对阵安排与该轮比赛开始前的排名有关:第 1 名和第2 名、第3 名和第4名、……、第2K – 1 名和第2K 名、…… 、第 2N – 1 名和第2N 名,各进行一场比赛。每场比赛胜者得1 分,负者得0 分。也就是说除了首轮以外,其它轮比赛的安排均不能事先确定,而是要取决于选手在之前比赛中的表现。
现给定每个选手的初始分数及其实力值,试计算在 R 轮比赛过后,排名第Q 的选手编号是多少。我们假设选手的实力值两两不同,且每场比赛中实力值较高的总能获胜。
输入
输入的第一行是三个正整数 N、R、Q,每两个数之间用一个空格隔开,表示有2*N 名选手、R 轮比赛,以及我们关心的名次Q。
第二行是 2*N 个非负整数s1, s2, …, s2N,每两个数之间用一个空格隔开,其中si 表示编号为i 的选手的初始分数。
第三行是 2*N 个正整数w1, w2, …, w2N,每两个数之间用一个空格隔开,其中wi 表示编号为i 的选手的实力值。
输出
输出只有一行,包含一个整数,即 R 轮比赛结束后,排名第Q 的选手的编号。
样例输入
2 4 2
7 6 6 7
10 5 20 15
样例输出
1
【数据范围】
对于 30%的数据,1 ≤ N≤ 100;
对于 50%的数据,1 ≤ N≤ 10,000;
对于 100%的数据,1 ≤ N≤ 100,000,1 ≤ R≤ 50,1 ≤ Q≤ 2N,0 ≤ s1, s2, …, s2N ≤ 108,1 ≤ w1,w2, …, w2N ≤ 108。
这道题目可以用死做的方法,每经过一轮之后就排个序,但是这样子的效率实在太低了,而且其实根本不用快排。
思路:
先把一开始的人按照分数排好序,然后每次用a数组储存这一轮获胜的人的位置,b数组储存这一轮败的人的位置,然后我们可以知道a,b数组一定是有序的,那这样子的话我们就可以利用归并排序的思想,O(n)排序法,大大的提高效率——从而AC这道题目。
type rec=record num,tot,pos:Longint end; var n,r,q:Longint; i,j,len,l1,l2,w1,w2:Longint; d,qq:array[0..200000] of rec; p1,p2:array[0..100010] of Longint; procedure sort(l,r:longint); var re:rec; i,j,mid,mi:Longint; begin i:=l; j:=r; mid:=d[(l+r) div 2].num; mi:=d[(l+r) div 2].pos; while i<j do begin while (d[i].num>mid) or ((d[i].num=mid) and (d[i].pos<mi)) do inc(i); while (d[j].num<mid) or ((d[j].num=mid) and (d[j].pos>mi)) do dec(j); if i<=j then begin re:=d[i]; d[i]:=d[j]; d[j]:=re; inc(i); dec(j); end; end; if l<j then sort(l,j); if i<r then sort(i,r); end; begin readln(n,r,q); for i:=1 to n*2 do read(d[i].num); for i:=1 to n*2 do read(d[i].tot); for i:=1 to n*2 do d[i].pos:=i; sort(1,2*n); for i:=1 to R do begin len:=0; for j:=1 to n do if d[j*2-1].tot>d[j*2].tot then begin inc(len); inc(d[j*2-1].num); p1[len]:=j*2-1; p2[len]:=j*2; end else begin inc(len); inc(d[j*2].num); p1[len]:=j*2; p2[len]:=j*2-1; end; len:=0; w1:=1; w2:=1; while (w1<=n) and (w2<=n) do begin l1:=p1[w1]; l2:=p2[w2]; if d[l1].num>d[l2].num then begin inc(len); qq[len]:=d[l1]; inc(w1); end; if d[l1].num<d[l2].num then begin inc(len); qq[len]:=d[l2]; inc(w2); end; if d[l1].num=d[l2].num then if (d[l1].pos<d[l2].pos) then begin inc(len); qq[len]:=d[l1]; inc(w1); end else begin inc(len); qq[len]:=d[l2]; inc(w2); end; end; for j:=w1 to n do begin inc(len); qq[len]:=d[p1[j]]; end; for j:=w2 to n do begin inc(len); qq[len]:=d[p2[j]]; end; d:=qq; end; writeln(d[q].pos); end.
T4:
对于 1 位二进制变量定义两种运算:
运算的优先级是:
1. 先计算括号内的,再计算括号外的。
2. “×”运算优先于“⊕”运算,即计算表达式时,先计算×运算,再计算⊕运算。
例如:计算表达式A⊕B × C 时,先计算B × C,其结果再与A 做⊕运算。
现给定一个未完成的表达式,例如_+(_*_),请你在横线处填入数字0 或者1,请问有多少种填法可以使得表达式的值为0。
输入
输入文件名为 exp.in,共2 行。
第 1 行为一个整数L,表示给定的表达式中除去横线外的运算符和括号的个数。
第 2 行为一个字符串包含L 个字符,其中只包含’(’、’)’、’+’、’*’这4 种字符,其中’(’、’)’是左右括号,’+’、’*’分别表示前面定义的运算符“⊕”和“×”。这行字符按顺序给出了给定表达式中除去变量外的运算符和括号。
输出
输出文件 exp.out 共1 行。包含一个整数,即所有的方案数。注意:这个数可能会很大,请输出方案数对10007 取模后的结果。
样例输入
4
+(*)
样例输出
3
数据范围限制
提示
【输入输出样例说明】
给定的表达式包括横线字符之后为:_+(_*_)
在横线位置填入(0、0、0)、(0、1、0)、(0、0、1)时,表达式的值均为0,所以共有3种填法。
【数据范围】
对于 20%的数据有0 ≤L≤ 10。
对于 50%的数据有0 ≤L≤ 1,000。
对于 70%的数据有0 ≤L≤ 10,000。
对于 100%的数据有0 ≤L≤ 100,000。
对于 50%的数据输入表达式中不含括号。
这道题目其实很难,当然做对搞懂之后又是另一回事,所以先大概分析一下:
难点一:题目比较难理解。
难点二:不知往哪方面想。
难点三:不知道如何实现。
这三点使得这道题目是一道比较难的题目。
先解决第一个难点:题目的意思是:给'+','*'两种符号,两种符号都有两种符号的运算规则,其中'*'号优先,()括号则最优先。最后式子得到答案=0的有多少情况——因为每个符号可以填不同的数,得出的答案和式子都是不一样的。
第二个难点:这道题其实很容易想到用递归去做,这样子可以方便括号和优先顺序这些,但是其实用递归容易出错不好调试,还有个原因就是当到达最大的那个数据点时,惊奇的发现dg爆栈了,所以我们可以想到用数组栈代替电脑的栈,这样子方便许多,但是就是优先问题没能解决。
第三个难点:刚刚第二个点提到往栈的方面想,那如何实现呢?我们可以把'('的位置记录起来,')'的位置则不用,因为遇到')'就要计算括号的值了。然后我们可以把每个符号都加上一个[0,1]表示这个符号当前所能得到的[0..1]的方案数。为什么是当前?请继续往下看。
然后我们在一个“括号”里计算值的时候可以确定如下几点:
这个“括号”里面只有'+','*'两种符号。
这个“括号”里面一定不能有'('。
然后在计算“括号”里的值的时候,我们要优先算'*',再算'+',其实这非常好实现,我们只需把'*'得到的值存在一个数组里,然后再把数组里的数'+'运算即可(当然输入的字符串也可在其左右+'(',')',方便计算,使其本身也成一个“括号”)。这样算完之后“括号”里面得到的值要存在'('位置-1,为什么呢?因为这样子才能给下次调用这个符号的时候算到“括号”的值。
举个栗子:
11
+**(+*+)+*+
括号里的值需存在a[3]才可以在最后计算时算进去,在算乘号或加号时会算到。在最后计算时,需要计算的值是'+**+*+',然后每个符号当前所能组成[0,1]值为
[1,1],[1,1],[3,13],[1,1],[1,1],[1,1].所以为什么每个符号所组成[0,1]方案数是当前的?显而易见,这是因为这个数随时有可能变动,所以是指当前,这很重要!
再一个是算完之后栈顶要变为'('位置-1.这也很重要!
解决这三个问题后,你就能大概理解了,然后再试着看看代码是如何实现的吧:
const maxn=10007; var n,i,k,len:longint; s:string; x,y:array[0..100000] of Longint; a:Array[0..100000,0..1] of longint; ch:array[1..100000] of char; w:array[0..100000] of longint; procedure tanzhan(t:longint); var p,i:longint; begin p:=0; for i:=t+1 to k-1 do //a[i,0]表示第i个位置当前构成0的方案数 if ch[i]='*' then //a[i,1]表示第i个位置当前构成1的方案数 begin //a数组表示的都是当前的,随时有可能变化。 a[i,0]:=(a[i,0]*a[i-1,0]+a[i,1]*a[i-1,0]+a[i,0]*a[i-1,1]) mod maxn; a[i,1]:=(a[i,1]*a[i-1,1]) mod maxn; end else begin inc(p); x[p]:=a[i-1,0]; y[p]:=a[i-1,1]; //x,y数组就是存“*”运算的值。 end; inc(p); x[p]:=a[k-1,0]; y[p]:=a[k-1,1]; //因为最后一次'*'运算没有记录到。 for i:=2 to p do begin y[i]:=(y[i-1]*y[i]+x[i-1]*y[i]+y[i-1]*x[i]) mod maxn; x[i]:=(x[i-1]*x[i]) mod maxn; end; //做“+”运算。 a[t-1,0]:=x[p]; a[t-1,1]:=y[p]; //赋值以便下次计算。 end; begin readln(n); readln(s); s:='('+s+')'; len:=0; k:=0; for i:=1 to length(s) do begin inc(k); a[k][0]:=1; a[k][1]:=1; case s[i] of '(': begin inc(len); w[len]:=k; end; ')': begin tanzhan(w[len]); k:=w[len]-1; //更新栈顶。 dec(len); end; '+','*':ch[k]:=s[i]; end; end; writeln(a[0][0]); //输出[0]的方案数。 end.