8VC Venture Cup 2016 - Elimination Round总结

A题:

A题题目链接

题目描述:

A. Robot Sequence
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Calvin the robot lies in an infinite rectangular grid. Calvin's source code contains a list of n commands, each either 'U', 'R', 'D', or 'L' — instructions to move a single square up, right, down, or left, respectively. How many ways can Calvin execute a non-empty contiguous substrings of commands and return to the same square he starts in? Two substrings are considered different if they have different starting or ending indices.

Input

The first line of the input contains a single positive integer, n (1 ≤ n ≤ 200) — the number of commands.

The next line contains n characters, each either 'U', 'R', 'D', or 'L' — Calvin's source code.

Output

Print a single integer — the number of contiguous substrings that Calvin can execute and return to his starting square.

Examples
input
6
URLLDR
output
2
input
4
DLUU
output
0
input
7
RLRLRLR
output
12
Note

In the first case, the entire source code works, as well as the "RL" substring in the second and third characters.

Note that, in the third case, the substring "LR" appears three times, and is therefore counted three times to the total result.

题意:

单个大写字母'U','D','L','R'分别表示机器人往上、下、左、右运动一步,给定一个数n,接下来的一行是n个大写字母组成的一个字符串,问这个字符串有多少个子串能够使得机器人移动完毕后回到初始的位置。

解析:

这道题不难,我们知道机器人往左走一步,那么它再往右走一步则回到初始位置;上下则同理,由于n的范围很小,所以我们可以双重for循环暴力遍历,只要字符串中L和R的个数相等,并且U和D的个数相等(L,R,U,D不可同时为0),那么该子串便可以使机器人回到原始位置。

完整代码实现:

#include<cstdio>
#include<algorithm>
typedef long long ll;
const int maxn = 200;
char str[maxn+10];
int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        scanf("%s",str);
        ll ans = 0;
        for(int i = 0;i < n;i++)
        {
            int sum1 = 0,sum2 = 0;
            for(int j = i;j < n;j++)
            {
                if(str[j] == 'R')
                    sum1++;
                else if(str[j] == 'L')
                    sum1--;
                else if(str[j] == 'U')
                    sum2++;
                else if(str[j] == 'D')
                    sum2--;
                if(sum1==0&&sum2==0)
                    ans++;
            }
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

D题:

D题题目链接

题目描述:

D. Jerry's Protest
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Andrew and Jerry are playing a game with Harry as the scorekeeper. The game consists of three rounds. In each round, Andrew and Jerry draw randomly without replacement from a jar containing n balls, each labeled with a distinct positive integer. Without looking, they hand their balls to Harry, who awards the point to the player with the larger number and returns the balls to the jar. The winner of the game is the one who wins at least two of the three rounds.

Andrew wins rounds 1 and 2 while Jerry wins round 3, so Andrew wins the game. However, Jerry is unhappy with this system, claiming that he will often lose the match despite having the higher overall total. What is the probability that the sum of the three balls Jerry drew is strictly higher than the sum of the three balls Andrew drew?

Input

The first line of input contains a single integer n (2 ≤ n ≤ 2000) — the number of balls in the jar.

The second line contains n integers ai (1 ≤ ai ≤ 5000) — the number written on the ith ball. It is guaranteed that no two balls have the same number.

Output

Print a single real value — the probability that Jerry has a higher total, given that Andrew wins the first two rounds and Jerry wins the third. Your answer will be considered correct if its absolute or relative error does not exceed 10 - 6.

Namely: let's assume that your answer is a, and the answer of the jury is b. The checker program will consider your answer correct, if .

Examples
input
2
1 2
output
0.0000000000
input
3
1 2 10
output
0.0740740741
Note

In the first case, there are only two balls. In the first two rounds, Andrew must have drawn the 2 and Jerry must have drawn the 1, and vice versa in the final round. Thus, Andrew's sum is 5 and Jerry's sum is 4, so Jerry never has a higher total.

In the second case, each game could've had three outcomes — 10 - 210 - 1, or 2 - 1. Jerry has a higher total if and only if Andrew won 2 - 1 in both of the first two rounds, and Jerry drew the 10 in the last round. This has probability .

题意:
有n个球,分别标号a1,a2,a3.....an(保证球的编号都不一样),然后每轮Andrew和Jerry会在这n个球中抽取一个球,记录球的编号,然后将球的编号作为抽取者本轮得分,而后将球放回(Andrew和Jerry同时抽取),已知Andrew赢取前两回合,而Jerry赢取第三回合(球的编号大者胜),问在这种情况下,Jerry的总分大于Andrew总分的概率?

解析:

首先我想到的做法是:一开始先将球的编号升序排序,这样的话方便记录获胜组合,然后通过双重for循环记录每一种获胜组合,分别用两个数组储存起来,然后再通过遍历这两个数组,计算每种情况下,Jerry和Andrew的分数,分两种情况遍历:

1.三轮得分均不相同(2-1和1-2视为相同组合)

2.两轮得分相同(与样例2一样,前两局比分是2-1 2-1,第三局是1-10或者2-10)

但是需要注意的问题是,既然两轮分数相同,那么到底是哪两轮分数相同呢?(1,2or1,3or2,3?)一开始我想到的是直接暴力遍历,不管哪种情况,全部都比较一遍,但是后来通过观察发现,其实有些遍历操作是不必要的,因此可以将其“剪掉”。

举例说明:如果第一轮和第三轮的分数相同,那么说明Jerry和Andrew这两轮的分数是相互抵消的,而第二轮是Andrew获胜,那么Andrew的总分数是永远大于Jerry的。当第二轮和第三轮的分数相同时同理。

所以两轮得分相同的情况下只需遍历第一轮和第二轮的分数相同的情况。

(三轮分数均相同,Jerry的分数是不可能大于Andrew的)

然后记录在每种获胜组合下Jerry和Andrew的总分数,如果Jerry分数更高,那么说明这种获胜组合满足条件,那么满足条件的获胜组合+1,记录完毕

后,再除以总的获胜组合,那么得到的答案便是在已知Andrew赢取前两回合,而Jerry赢取第三回合(球的编号大者胜),问在这种情况下,Jerry的总分大于Andrew总分的概率。

总的获胜组合的求法:

由于每轮抽完球后是放回的,那么每轮获胜组合的组数为C(n,2) = n*(n-1)/2     (例如球的编号是1,2,3,4(升序排序后),每一轮的获胜组合即是在这四个球中任意取两个即可)

而三轮中总的获胜组合满足乘法原理,即为C(n,2) * C(n,2) * C(n,2);

然后在修改了几次细节错误导致wa的情况后,于是我便信心满满的交上了如下的完整代码:

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e3;
int num[maxn+10],win[maxn+10],fail[maxn+10];
int main()
{
    int n;
    while(scanf("%d",&n)==1&&n)
    {
        for(int i = 1; i <= n; i++)
            scanf("%d",&num[i]);
        sort(num+1,num+n+1);
        int k = 0;               //记录有多少组获胜组合
        for(int i = 1; i <= n; i++)
            for(int j = i+1; j <= n; j++)
            {
                if(num[j]>num[i])
                {
                    win[k] = num[j];
                    fail[k] = num[i];
                    k++;
                }
            }
        //printf("%d\n",k);
        //for(int i = 0; i < k; i++)
            //printf("%d %d\n",win[i],fail[i]);
        int A_score,J_score,ans = 0;          //ans记录有效获胜组合
        for(int i = 0; i < k; i++)
        {
            for(int j = 0; j < k; j++)          //三局分数均不相同
            {
                if(i==j)   continue;
                for(int l = 0; l < k; l++)
                {
                    if(i==l||j==l)    continue;
                    A_score = win[i] + win[j] + fail[l];
                    J_score = fail[i] + fail[j] + win[l];
                    if(J_score > A_score)
                        ans++;
                }
            }
        }
        for(int i = 0; i < k; i++)
            for(int j = 0; j < k; j++)   //两局分数相同
            {
                A_score = win[i] + win[i] + fail[j];
                J_score = fail[i] + fail[i] + win[j];
                if(J_score > A_score)
                    ans++;
            }
        //printf("%d\n",ans);
        ll all = k*k*k;
        double possibility = (double)ans / all;
        printf("%.10f\n",possibility);
    }
    return 0;
}

然后得到的结果是:


其实在提交的时候我就想到会是这样的结果,三重for循环,内层循环次数可达10^9级别,所以在大数据的情况下,肯定超时。

然后,我就开始寻找是否能够在什么地方优化一下,减少遍历次数。于是我便看到每一轮两人拿的球的编号都不一样,比如说这一轮的分数是2-1,那么其实胜者这时比败者多一分,同理类推,而最后要总分数更高的话,那么胜者 - 败者的分数要大于0,而球的编号小于5000,所以,这时候我就想到了运用“桶的思想”,记录每种获胜组合的胜分即可。

而已知Andrew赢取前两回合,而Jerry赢取第三回合,要是Jerry分数高于Andrew,只要Jerry在第三回合的胜分大于Andrew前两回合的总胜分即可,那样的话Jerry的总胜分必然是大于Andrew的,所以我们一开始可以双重for循环遍历,记录每种胜分的个数。

然后再记录Andrew每种胜分的个数,最后再记录Jerry的总胜分>Andrew的情况有多少种,然后结果就是记录的情况除以获胜组合的总数即是在题设条件下Jerry分更高的概率。然后我信心满满的交上了如下的代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 2e3;
const int score_max = 5e3;
int num[maxn+10];
int count_winscore[score_max+10],A_score[score_max<<1+10];
int main()
{
    int n;
    while(scanf("%d",&n)==1&&n)
    {
        memset(count_winscore,0,sizeof(count_winscore));
        memset(A_score,0,sizeof(A_score));
        for(int i = 0; i < n; i++)
            scanf("%d",&num[i]);
        sort(num,num+n);
        ll k = 0;               //记录有多少组获胜组合
        for(int i = 0; i < n; i++)
            for(int j = i+1; j < n; j++)
            {
                if(num[j]>num[i])
                {
                    count_winscore[num[j]-num[i]]++;  //记录胜分组合
                    k++;
                }
            }
        for(int i = 1;i<=5000;i++)
        {
            if(count_winscore[i])
            {
                for(int j = 1;j <=5000;j++)
                {
                    if(count_winscore[j])
                            A_score[i+j] += count_winscore[i] * count_winscore[j];   //可以放回
                }
            }
        }
        ll ans = 0;
        for(int i = 1;i <= 5000;i++)
        {
            if(count_winscore[i])
            {
                for(int j = 1;j < i;j++)
                {
                    if(A_score[j])
                        ans += count_winscore[i] * A_score[j];
                }
            }
        }
        double possibility = (double)ans / k / k / k;
        printf("%.10f\n",possibility);
    }
    return 0;
}

本以为可以轻轻松松的AC掉,可是却得到这样的答案:


wrong answer?在看了测试数据之后,发现依旧是大数据出现的问题。然后看了一下别人AC的代码,恍然大悟

发现这个程序存在两个隐藏的错误:

1.A_score数组的最大值是可能超过int的最大值的,所以A_score数组应该声明为long long类型的

2.同样,count_winscore数组和A_score数组中数据元素相乘后的积是可能超过int的最大值的。

这两种情况都是同样的一个问题:

数据类型的最大值过小,导致结果溢出。原因:没有预判大数据时最大的数据大概是多大级别的数字。

在修改上述问题后,得出了AC的完整代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 2e3;
const int score_max = 5e3;
int num[maxn+10];
ll count_winscore[score_max+10],A_score[score_max<<1+10];
int main()
{
    int n;
    while(scanf("%d",&n)==1&&n)
    {
        memset(count_winscore,0,sizeof(count_winscore));
        memset(A_score,0,sizeof(A_score));
        for(int i = 0; i < n; i++)
            scanf("%d",&num[i]);
        sort(num,num+n);
        for(int i = 0; i < n; i++)
            for(int j = 0; j < i; j++)
                count_winscore[num[i]-num[j]]++;  //记录胜分组合
        for(int i = 1; i<=5000; i++)
            for(int j = 1; j <=5000; j++)
                    A_score[i+j] += count_winscore[i] * count_winscore[j];   //可以放回
        ll ans = 0;
        for(int i = 1; i <= 5000; i++)
            for(int j = 1; j < i; j++)
                ans += count_winscore[i] * A_score[j];
        ll k = n*(n-1) / 2;
        double possibility = (double)ans / k / k / k;
        printf("%.10f\n",possibility);
    }
    return 0;
}


可是,又出现了一个问题,为什么内存使用会如此之大?

一开始我也是摸不着头脑,后来才发现原来是声明数组时的一个隐藏错误:

A_score[score_max<<1+10]

这个是运算符运算的优先级的问题,加法运算符的优先级大于左移运算符的优先级,那么先算1+10,答案则是11,而score_max左移11位相当于是乘以2^11次方,所以使用内存才会如此大。

改进之后,我们还可以采取前缀和的预处理方式进行进一步的优化,例如如果i>j(i为Jerry的胜分),那么胜分区间在[1,j]之内的数组元素均是满足条件的获胜组合,所以我们就可以离线处理,如下所示:

 for(int j = 1; j <=10000; j++)
        A_score[j] += A_score[j-1];    //前缀和处理

这样的话,就不需要双重for循环遍历满足条件的获胜组合了,只需一重循环即可。这样的话降低了时间复杂度。完整代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 2e3;
const int score_max = 5e3;
int num[maxn+10];
ll count_winscore[score_max+10],A_score[score_max*2+10];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 0; i < n; i++)
        scanf("%d",&num[i]);
    sort(num,num+n);
    for(int i = 0; i < n; i++)
        for(int j = 0; j < i; j++)
            count_winscore[num[i]-num[j]]++;  //记录胜分组合
    for(int i = 1; i<=5000; i++)
        for(int j = 1; j <=5000; j++)
            A_score[i+j] += count_winscore[i] * count_winscore[j];   //可以放回
    for(int j = 1; j <=10000; j++)
        A_score[j] += A_score[j-1];    //前缀和处理
    ll ans = 0;
    for(int i = 1; i <= 5000; i++)
        ans += count_winscore[i] * A_score[i-1];
    ll k = n*(n-1) / 2;
    double possibility = (double)ans / k / k / k;
    printf("%.10f\n",possibility);
    return 0;
}


这样的话,才是优秀而又简洁的代码

总结:这场比赛是事后打的模拟赛,说实话,打的很纠结,本来觉得D题可做,但是最后结果还是wa了,然后第二天开始补题,当然补题也是从D题开始补,按照原来的思路,发现for循环少了一层,于是加上后,虽然能得出正确答案,但是大数据的时候却超时了。百般尝试之后,虽然往正确解答的方向走了,但是依旧wa在了细节上面。所以往后一定要打牢基础,编程的细节问题一定要牢牢记住,而且要注意各种数据类型的边界范围,防止越界。

如有错误,还请指正,O(∩_∩)O谢谢



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值