新生训练个人排位赛第二场

想法是一方面,细节也不可忽略,会做但不一定会过

 

比赛链接

第一题

 本题题意比较清楚,不过有几个小坑:

1.题目复杂化 :直接一次比较保存最大值即可,不用拿sort进行比较,会超时的啦

2.两个数相除作商的比较:可以用b*maxa>a*maxb;当然也可以用转成两个double型的绝对值小于某个很小的数来判断

int dlcmp(double x)

{

    return x<-eps?-1:x>eps;

}

if (dlcmp(a-b) > 0).....

 

当然这题需要强制类型转换

if (dlcmp(double(b)/a-double(maxb)/maxa)>0)

 

3.鉴于a,b都能达到10的9次方,所以用maxb*a这种方法时int会爆,必须要用long long,应当注意的是int 和 long是一样的,(所以%ld %d也是一样的,因此long long 要用%lld)

 

A. 丁*去谷歌 2014新生暑假个人排位赛02

时间限制 1000 ms  内存限制 65536 KB

题目描述

丁*要去Google上班了,去之前丁*想再做一道水题,但时间不多了,所以他希望题目做起来既水又快。现在一共有n道题,编号从1到n,每道题有两个值a和b,a为做这道题需要的时间,b为题目的“水值”,丁*希望做b/a最大的那题。

输入格式

输入第一行为数据组数T(T≤10),接下来T组数据,每组数据中第一行为一个数n,n为题目的数量,接下来n行,每行两个正整数a和b。如果两道题b/a的值是一样的就输出a比较小的,如果还一样就输出编号比较靠前的。 1≤a,b≤109,1≤n≤100000)

输出格式

对于每组数据,输出对应的题目编号,每个输出占一行。

输入样例

1
2
3 5
4 8

输出样例

2
/*
USER_ID: test#zsp
PROBLEM: 416
SUBMISSION_TIME: 2014-07-16 14:34:24
*/
#include <iostream>
#include <cstdio>
#include <string.h>
#include <vector>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
long long a, b, maxa, maxb;
int maxt;
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        int qnum;
        scanf("%d", &qnum);
        maxt = 1;
        for (int i=1; i<=qnum; i++)
        {
            scanf("%ld %ld", &a, &b);
            if (i==1)
            {
                maxa = a;
                maxb = b;
                maxt = i;
            }
            else if ((b*maxa>a*maxb)||((b*maxa==a*maxb)&&(a<maxa)))
            {
                maxa = a;
                maxb = b;
                maxt = i;
            }
        }
 
        printf("%d\n", maxt);
    }
    return 0;
}


 

第二题

这道题虽然是一道简单背包,但是要注意两个问题

1.当题目中出现10的9次方时就应当注意int是否会爆,这题的DP由于涉及累加是可能会爆的

2.当然这题改成long long后还应注意输出也要改成%lld

3.自然10的9次方本身是不会爆的,所以关于b,即每个的D[i],可以用long long ,也可用int,都是对的

 

417. 丁*又去谷歌

时间限制 1000 ms  内存限制 65536 KB

题目描述

丁*又要去Google上班了,这一次丁*想多做几道水题,并使题目的总水量最大.丁*同一时刻只能在水一道题,只有做完这道题才能得到它的水值,丁*的总时间为t,现在一共有n道题,编号从1到n,每道题有两个值a和b,a为做这道题需要的时间,b为题目的水值。

输入格式

输入第一行为数据组数T(T≤10),接下来T组数据,每组数据中第一行为两个数t和n,n为题目的数量,t为总时间,接下来n行,每行两个正整数a和b。(1≤a,t≤1000,1≤n≤100,1≤b≤1000000000)

输出格式

对于每组数据,输出对应的最大总水量,每个输出占一行。

输入样例

1
10 2
8 16
6 12

输出样例

16
/*
USER_ID: test#zsp
PROBLEM: 417
SUBMISSION_TIME: 2014-07-16 18:12:58
*/
#include <iostream>
#include <cstdio>
#include <string.h>
#include <vector>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        int  N, W[1005];
        long long D[1005];
        int M;
        long long dp[1005];
        memset(dp, 0, sizeof (dp));
        memset(W, 0, sizeof (W));
        memset(D, 0, sizeof (D));
        scanf("%d%d", &M, &N);
        for (int i=1; i<=N; i++)
        {
            scanf("%d %lld", &W[i], &D[i]);
            //cout << D[i];
        }
        for (int i=1; i<=N; i++)
            for (int j = M; j >= W[i]; j--)
                dp[j] = (dp[j] > (dp[ j - W[i] ] + D[i])) ? dp[j] : (dp[ j - W[i] ] + D[i]);
        printf("%lld\n", dp[M]);
    }
    return 0;
}


第三题

1.这道题要求找规律,而且是以正确答案的一半来找,具体的规律函数见代码

 

2.此题注意不能先把c[i][j]算好,因为太大了,没有取模的话根本没有东西能存下

 

3.算c[i][j]要根据已经知道的n来计算,这样大大减少运算量,(注意不需要再拿4200来算)只要n那么大就行了。。。。。

 

4.但是事实证明这样还是会超过1000毫秒,但是超过1秒这题也能过

 

5.最后也是最重要的一点,(一个人在同一块石头上绊倒好几次-------)A题刚说了两个不会爆的数相乘是可能会爆的,结果下午做又忘了。特别注意,两个接近极限的int相乘,爆掉以后可能会是负数,也可能是正数,这个和计算机的储存机制有关,但可以确定的是,int 不像long long 会自动取模,强制转换成long long 以后,一般出题者考虑是不会爆的(这个从P,即要取的模的大小可以看出来),而且long long 即使爆掉也是取自己最大的可表示数的模。

这种出错

 

系统一般会返回答案错误,下次看到可以从这个角度多考虑一下。

(在学习他人代码的时候一定要注意到这一点,能用int的绝不会随便用更大的,能精简的不会随便来个强制类型转换。不然抄别人代码都会抄错,,,,,,,真是不能忍了.........)

 

6.顺便也可以记一下,这里有个组合数的模板,省得下次又想半天

 

7.memset也是需要时间的,初始化的时间复杂度也是数组大小的级别,就像下面这题4200*4200,然后还是好几组数据,差不多10的9次方就超一秒了,测试数据好像是十组所以好像多了好几秒,像下面的第四题,memset也不一定要初始化整个数组,这个可以自己估算一下

 

 

426. goblin

时间限制 1000 ms  内存限制 65536 KB

题目描述

现有一段横向长度为N的山脉,其中每段有一个独一无二的高度Hi(1到N之间的正整数)。现在你想知道对于长度为N的山脉,可能有这样的山脉多少种。这样的山脉是:某个位置要么比两边的高度都低,要么比两边的高度都高。两座山脉 A和 B 不同当且仅当存在一个 i,使得 Ai≠Bi。由于这个数目可能很大,你只对它除以 P 的余数感兴趣。

输入格式

输入以EOF为结束,每组仅含一行,两个正整数 N, P。 3≤N≤4200,P≤10^9

输出格式

对于每组数据输出仅含一行,一个非负整数,表示你所求的答案对 P 取余之后的结果。

输入样例

4 7

输出样例

3
说明:共有 10 种可能的山脉,它们是:
1324    1423    2143    2314    2413
3142    3241    3412    4132    4231 
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string.h>
 
using namespace std;
 
int a[4205], c[4205][4205];
int main()
{
    int n, p;
    while (~scanf("%d %d", &n, &p))
    {
        memset(a, 0, sizeof a);
        //memset(c, 0, sizeof c);//注意此处加上会导致超过储存限制
        c[0][0] = 1;
        for (int i=1; i<=n; i++)
        {
            c[i][0] = 1;
            c[i][i] = 1;
            for (int j=1; j<=i/2; j++)
            {
                c[i][j] = (c[i-1][j-1] + c[i-1][j])%p;
                c[i][i-j] = c[i][j];
            }
        }
        a[0] = a[1] = 1;
        for (int i=2; i<=n; i++)
        {
            for (int j=0; i-1-j>=0; j+=2)
                a[i] = (((long long)c[i-1][j]*(long long)a[i-1-j]%p)*(long long)a[j]%p + a[i])%p;
        }
 
        printf("%d\n", 2*a[n]%p);
    }
    return 0;
}

第四题

本题总体思路是个博弈,这个博弈和一般nim的特殊处在于比赛是同步进行的,所以不是每个游戏的sg值异或和,而是每个游戏的步数进行比较,取最大的那个,那么最大那个是谁赢,结果就是谁;

 

至于怎么计算步数,因为每个人都走最优策略,所以每局输赢是定死的,

而赢定的人想让该局时间尽量延长;输定的人想让该局尽量变短,

具体的做法是赢点的子节点有输有赢,他要取必输的里面步数最大的,输点的子节点都是赢的,他要取所有子结点里距离结束步数最短的

但是这题的坑还比较多,哪怕知道思路一个下午T了十几次,了解到了T的十几种变

1.memset也可能初始化了太多,此处详见第三题解析

2.n,(可以推广到任何变量),没有初始化,就使用,(这有可能是粗心写反了,反正我经常这样,像我有次啥都没有就先把一个数组进行排序了,不过那次是报答案错误)那么这个数字可能是很大,所以可能导致两种结果,一种是答案错误,另一种是用了这个n再去初始化别的东西,结果就超时了,比如这里的

vector <int> a[1000];

for (int i=0; i<n; i++)

     a[i].clear();

这样就明显超n来个随机值10的9次方的,就爆了

 

3.由于很多代码粘来贴去,导致一个变量申明在了很多地方,而这些地方又有交叉,这个变量用到一半被引用到别的地方,在返回来时已经不是原来的那个值了(这个在寒假做题时就曾经碰到过,当时就因为这个原因,明明觉得自己很对,但就是过不了,找不到原因,所以放弃治疗,一个寒假没做五道题,这是很痛苦的,思路是对的,但就是过不了,,,这还学信安啊。。。。还怎么查错和找漏洞)

4.所以一定要注意一个变量从初始化,开始使用,到使用结束,中间会不会被别的地方引用篡改了。

一般会出现在

一是申明全局变量,大家都可以用,容易忽略的地方是递归调用就如下面这题help数组,必须在递归调用完以后再初始化数组,再使用

二是大家都容易忽略的i,j这种小数,在一个大的 i 循环下面用一个小的 i,所以一般定义在for循环里面

5.提到memset还要注意一下它是在<string.h>里面的,竟然<string.h>和<string>是不一样的!!!! !

 

 

 

 

427. 学姐逗学弟

时间限制3000 ms 内存限制 131072 KB

 

题目描述

学弟们来了之后,学姐每天都非常高兴的和学弟一起玩耍。这一天,学姐想出了这样一个游戏,她画了一棵树,树上共有n个节点,现在学姐把m(mn)个石子随机放在节点上,每个节点可以放多个,每一次操作是指把每一个节点上的所有石子都往下移动到他某一个子节点(一个节点有多个石子可以分别移动到不同子节点),如果没有子节点则不移动,无法移动的人输。 学姐说,学弟是绅士应该让学姐先走,其实学姐已经策划好了自己一定会赢,但是这时学弟说,学姐先下那么我来画树和放石子吧,学姐惊呆了。现在她来想知道在新的图上,两人都按最优方案走,自己还能不能赢。

输入格式

输入第一行为一个整数T表示数据组数,接下来T组数据,每组开头为两个整数n,m,表示节点个数和石子个数,1≤mn≤100000,接下来一行n−1个整数,表示2到n节点的父亲节点编号,接下来一行m个整数,表示每一个石子的位置。数据保证1为根节点。

输出格式

如果学姐能胜利,输出"MengMengDa!",否则输出"So sad..."。没有引号。

输入样例

1
3 1
1 1
1

输出样例

MengMengDa!




 
/* USER_ID: test#zsp PROBLEM: 427 SUBMISSION_TIME: 2014-07-17 17:11:23 */ #include <iostream> #include <algorithm> #include <cstdio> #include <string.h> #include <vector> #define MAXN 100005 using namespace std; vector <int> a[MAXN]; int b[MAXN]; int sg[MAXN]; int num[MAXN]; int vis[MAXN]; bool help[MAXN]; int n, m, temp; void cal(int k)//计算sg值的 { // if (num[k]) return; int len = a[k].size(); if (len == 0) { sg[k] = 0; num[k] = 1; return; } else { int maxt = 0, mint = 1000000; for (int j=0; j<len; j++) { cal(a[k][j]); mint = min(mint, num[a[k][j]]+1);//必败点步数为(它的子节点都是必胜点)子节点最小的加一 if (sg[a[k][j]]==0) maxt = max(maxt, num[a[k][j]]+1);//必胜点为必败子节点中最大的加一, } memset(help, 0, sizeof (len+10)); for (int j=0; j<len; j++) { help[sg[a[k][j]]] = true; } for (int i=0; i<=n; i++) if (!help[i]) { sg[k] = i; break; } if (sg[k]==0) { num[k] = mint; } else { num[k] = maxt; } return; } } //用来计算从这点到结束双方采用最优策略步数, //必胜点为必败子节点中最大的加一, //必败点步数为(它的子节点都是必胜点)子节点最小的加一 int main() { int T; scanf("%d", &T); while(T--) { //memset(sg, 0, sizeof sg); //memset(b, 0, sizeof b); //memset(num, 0, sizeof num); scanf("%d %d", &n, &m); for (int i=1; i<=n; i++) a[i].clear(); for (int i=2; i<=n; i++)//读取每个子节点的父亲节点,将子节点记录到父节点的子节点向量中 { scanf("%d", &temp); a[temp].push_back(i); } //memset(b, 0, sizeof b); for (int i=1; i<=m; i++)//获取当前每个石子所在的位置 { scanf("%d", &b[i]); } //memset(sg, 0, sizeof sg); //计算sg值 cal(1); //memset(num, 0, sizeof num); //计算当前各点到结束还有几步 int maxnum = 0; int ft; for (int i=1; i<=m; i++)//个人认为不可能相同的步数一个先手赢,一个后手赢 { if (num[b[i]]>maxnum) { maxnum = num[b[i]]; ft = b[i]; } } if (sg[ft] == 0) printf("So sad...\n"); else printf("MengMengDa!\n"); } return 0; } 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值