新生排位赛第四场 dp

第五题是一个很好的dp; 第四题据说有一点康拓展开,据说,只是据说


第一题

这题我把题目想复杂化了,而且本身题目也有bug(x的范围应该是0到2000000)所以4次才过


下面我将给出两种代码,前者是考试代码,时间上复杂度会低一些

即正好m是偶数,而又出现两个m/2时,那肯定这两个数的差值最小,其他都不用判断了),但是,

我当时就错在当已经有两个最佳解出来时,后面可能还有数字输入,但是我已经 scanf 跳出循环,后面都不读了,所以就成了答案错误了),

其实反正也相差不了多长时间,不用把情况考虑得那么细,考虑复杂了,自己倒陷进去,增加了错误的可能性


另外一个,设一个m最大值那么大的数组来储存某个数字是否出现过,bool型变量就可以了,这样大大减短了判断是否能凑成免运费,其实这题也要注意测试T不是很大,如果很大的话光初始化这个数组都可能会超时,那就要记录一下我们最大用到哪里,这样初始化范围会小一些


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

题目描述

大家都回了本部,本部生活虽然没有宏福天(mo)堂(gui)般的生活,但是却有很多外卖,今天有一家饭店新开张,凡是两个人的餐点费用之和刚好为m可以免运费,在今天一起点外卖且免运送费的两个人,各自点的外卖价格差值最小的有大惊喜。在机房的n个同学今天打算一起点外卖,但是每个人都只想要点某一个价格x的餐点,请你帮忙规划一下大家应当如何组合,并给出最有可能获得今天大惊喜的价格组合。

输入格式

第一行输入样例数 T  对于每一个样例 第一行输入整数 n(0 n500000) ,整数 m(0m2000000)  接下来的 n 行,每行输入一个整数x代表每一个人想点的餐点的价格  0x1000000

输出格式

如果能够找到最有可能获得今天大惊喜的价格组合,则输出两个人所点的餐点的价格,小的价格在前。
如果所有人都没办法免运送费,则输出“Sad”

输入样例

2
2 3
1
3
4 3
1 
2
3
4

输出样例

Sad
1 2

代码一,将m/2的情况单独拎出来讨论

#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <algorithm>
 
 
using namespace std;
int a[500005];
bool b[2000005];
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
 
        int n, m;
        scanf("%d %d", &n, &m);
        memset(b, 0, sizeof b);
        int flag = 0;
        for (int i=0; i<n; i++)
        {
            scanf("%d", &a[i]);
            if (a[i]<m)
            {
                if ((b[a[i]] == true)&&(2*a[i] == m))
                {
                    flag = 1;//这里做个标记就行,别跳出去
                }
                else
                    b[a[i]] = true;
            }
        }
        if (flag==1)
        {
            printf("%d %d\n", m/2, m/2);
        }
        else
        {
            int mina, minb, mindis;
            mindis = 100000000;
            for (int i=0; i<n; i++)
            {
                if (2*a[i] == m) continue;
                if ((a[i]<m)&&(b[m-a[i]]==true)&&(abs(m-2*a[i])<mindis))
                {
                    mindis = abs(m-2*a[i]);
                    mina = min(a[i], m-a[i]);
                    minb = max(a[i], m-a[i]);
                }
            }
            if (mindis == 100000000)
                printf("Sad\n");
            else
                printf("%d %d\n", mina, minb);
        }
    }
    return 0;
}


代码二,显然代码会更短,时间稍微长了一点点,但是这题2000毫秒的时间限制,还是没问题的


#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <algorithm>


using namespace std;
int a[500005];
bool b[2000005];
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        int n, m;
        scanf("%d %d", &n, &m);
        memset(b, 0, sizeof b);
        int flag = 0;

        int mina, minb, mindis;
        mindis = 100000000;
        for (int i=0; i<n; i++)
        {
            scanf("%d", &a[i]);
            if ((a[i]<m)&&(b[m-a[i]]==true)&&(abs(m-2*a[i])<mindis))
            {
                //b[a[i]] = true;
                mindis = abs(m-2*a[i]);
                mina = min(a[i], m-a[i]);
                minb = max(a[i], m-a[i]);
            }
            else
                b[a[i]] = true;
        }
        if (mindis == 100000000)
            printf("Sad\n");
        else
            printf("%d %d\n", mina, minb);
    }
    return 0;
}


 

 

 

 

第二题

这题利用了并查集的知识,但是要注意,有可能两个公司本来已经联盟了,即本来已经归到同一个父亲结点,单独判断一下

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

题目描述

田田家开了一家大公司,面对现在的形势,很多公司选择了联盟。联盟一旦成立就意味着联盟内的每个公司互相之间都认可结盟。所以当决定以一家公司为对手的时候就一定要计算出这家公司所在联盟的总实力值来决定有没有能力击败对手。
现在有n个公司,和m条信息。每条信息可能是某两个公司宣布联盟或者查询某个公司所在联盟的实力值

输入格式

多组数据
第一行为数据量T,每个case第一输入n,m ,之后一行输入n个数ai表示第i个公司的实力值,之后m行输入信息,有两种信息

第一种为两个公司联盟,该行为1 x y ,1表示联盟操作,即x,y两个公司联盟,

第二种为查询某个公司,该行为2 x,2表示查询操作,即查询x公司所在联盟的实力值;

T<=10
n,m<=10^5
ai<=10^6

输出格式

每个查询一行输出,输出那个公司联盟的实力值

输入样例

1
5 5
1 2 3 4 5
1 1 2
2 2
2 5
1 2 3
2 1

输出样例

3
5
6
#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <algorithm>
 
 
using namespace std;
int a[100005];
long long sum[100005];
int father[100005];
void merg(int a1, int a2)
{
    while (father[a1]!=a1)
    {
        a1 = father[a1];
    }
    while (father[a2]!=a2)
        a2 = father[a2];
    if (a1==a2) return;   //-----------此处应特别注意
    father[a2] = a1;
    sum[a1] += sum[a2];
}
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        //memset(father, 0, sizeof father);
        int n, m;
        scanf("%d %d", &n, &m);
        for (int i=1; i<=n; i++)
        {
            scanf("%d", &a[i]);
            father[i] = i;
            sum[i] = a[i];
        }
 
        for (int i=0; i<m; i++)
        {
            int tag, a1, a2, b;
            scanf("%d", &tag);
            if (tag==1)
            {
                scanf("%d %d", &a1, &a2);
                merg(a1, a2);
            }
            else if (tag==2)
            {
                scanf("%d", &b);
                while (father[b]!=b)
                    b = father[b];
                printf("%lld\n", sum[b]);
            }
        }
 
    }
    return 0;
}


 

 

第三题


这题主要是表达的问题,其实算式绝大部分还是能推出来的,像我比较笨,第一遍还推错了,推了第二次,也就推出来了,注意2,3的面积分别是那四小块加起来,答案有两种:表达式写在printf里面,还有一种就是换成long double,(%Lf)(不过这种910的电脑是运行不出来的)这里分别附上两种代码

 

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

题目描述

崔逗逗放假回家后每天只能吃饭睡觉打豆豆感觉好无聊,该怎么给自己找找乐子呢?于是他主动去帮邻家初中小妹妹做作业。他看到了这样一道题:在一个边长为 a(0a10001) 的正方形ABCD中,分别以A、B、C、D为圆心做半径为a的四条弧,如图。求标号为1,2,3的图形面积。崔逗逗想了好久居然不会做,但是做不出来又会在小妹妹面前很没面子,所以他找到你来帮忙。

输入格式

多组数据。每组数据包含一个实数a,表示正方形的边长。

输出格式

对于每组数据,输出标号为1,2,3的图形面积。保留6位小数。

输入样例

0.2

输出样例

0.012606 0.020452 0.006942

代码一,表达式写在printf里

#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <algorithm>
 
using namespace std;
 
 
int main()
{
    double a;
    double pi = acos(-1.000);
    while (~scanf("%lf", &a))
    {
 
        double b, c, d;
        b = a*a*(1.0-sqrt(3.0)+pi/3.0);
        c = a*a*(sqrt(3.0)/2.0-1+pi/12)*4.0;
        d = a*a*(1.0-sqrt(3.0)/4.0-pi/6.0)*4.0;
        printf("%.6lf %.6lf %.6lf\n", a*a*(1.0-sqrt(3.0)+pi/3.0), a*a*(sqrt(3.0)/2.0-1.0+pi/12)*4.0, a*a*(1.0-sqrt(3.0)/4.0-pi/6.0)*4.0);
 
    }
    return 0;
}

 


 

代码二 学长的,因为我的电脑运行不了


#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
const long double pi=acos(-1);
//const long double ans3=4-2*pi/3-sqrt(3);
//const long double ans1=1+pi/3-sqrt(3);
//const long double ans2=-4+pi/3+2*sqrt(3);
int main()
{
    long double n;
    while(cin>>n)
    {
        long double ans3=4*n*n-2*pi*n*n/3-n*n*sqrt(3);
        long double ans1=n*n+pi*n*n/3-n*n*sqrt(3);
        long double ans2=-4*n*n+pi*n*n/3+2*n*n*sqrt(3);
 
        printf("%.6Lf %.6Lf %.6Lf\n",ans1,ans2,ans3);
    }
 
    return 0;
}



 

 

 

 

第四题

这题其实我还是想到解题方法的,只是%lld写成了 %l64d, 结果主机评判还告诉我是超时了,就没有想到会是这个错,有一点可惜,不过也当是个教训吧,记得上次一个同学还

if (a < n); 

    printf(" ");

多了一个分号,结果格式错误找得两眼昏花啊


其实题意转化过来就是x^2x == x+2x,什么样的x才会满足这种条件呢?

那么就是转化成的二进制没有两个相邻的1,因为2x其实就是x的二进制往前移一位,而异或运算和加法运算最大的不同就在于,异或运算碰到两个1为0,加法运算碰到两个1,除了变0还要进一位,所以我们只要没有两个1相加,异或 和 直接相加就是一样的了,而当x的二进制有两个相邻的1时,它错一位产生的2x,就必然会出现两个1的情况

所以本题转化成给你一个数,判断小于等于它的数里面有几个是二进制没有相邻的1


首先预初始化一个数组, a[n]的含义是自由的n位,可能有几个没有相邻一的数,例如a[1]为2,分别是0,1

a[2]为1,分别是00,01,10(00 和 0 是一样的,这里为了表示得整齐一点);同理可得到递推式a[n] = a[n-1] +a[n-2];(是一个斐波那契数列)

预处理完以后,对于一个给定的十进制数p,先把它转成二进制,如果一共有n位,那么首先最高位取0,至少有a[n-1]个,

然后最高位取1,次高位必须取0,那么看次高位真实值是多少,如果为1,那么很好,n位,n-1位确定了,n-2及以下可以自由取值,所以有a[n-2]种,加起来就是a[n-1]+a[n-2];   如果是次高位真实值不是1,那么可以把原来的数拆成10000。。。(n个0)+另外一个数,如果这个数是0,返回1;如果非零,递归,把它看成一个新的p,重新按刚才的方法再次判断。

总体思路是这样,详细请看代码


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

题目描述

崔逗逗是一个善良的学长,最喜欢为学弟学妹出水题。他给大家一个整数n,求0<=x<=n的范围内,有多少个数满足(x)^(2x)^(3x)==0 (^是异或符号)。你只要告诉他对1000000009取余的答案就好啦。

输入格式

输入有多组数据,数量在100以内,每行一个整数n(0<=n<=10^18)。

输出格式

每组数据输出一行答案。

输入样例

1
2

输出样例

2
3

 

#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define maxn 1000000009
using namespace std;
long long ln;
//long long ait[65];
long long a[65];
int b[65];
int num;
long long sum;
long long cal(int n)
{
    if (n<0) return 0;
    if (n==0) return 1;
    if (!b[n])
        return cal(n-1);
    if (n==1) return 2;
  
    if (b[n]&&b[n-1])
        return sum = (a[n-1]+a[n-2])%maxn;
    else if (b[n]&&(!b[n-1]))
        return sum = (a[n-1] + cal(n-2))%maxn;
  
}
int main()
{
    a[0] = 1;
    a[1] = 2;
    for (int i=2; i<64; i++)
    {
        a[i] = (a[i-1]+a[i-2])%maxn;
    }
    while (~scanf("%lld", &ln))
    {
        memset(b, 0, sizeof b);
        num = 1;
        while (ln>0)
        {
            if (ln&1)
                b[num++] = 1;
            else
                b[num++] = 0;
            ln = ln>>1;
        }
        sum = 0;
        printf("%lld\n", cal(num-1));
    }
    return 0;
}

 

 

 

第五题  今天很晚了,其实昨天就该贴出来,但是太忙了,忘了,有空的时候还应该把注释加上,今天太累了,先把代码直接粘过来,今天才发现原来我一直怪csdn不好用,莫名其妙就会没有选择功能,原来是我自己的浏览器问题,现在下了一个谷歌浏览器专门写博客,感觉好多了,希望审核的机器大哥大姐手下留情,能让我的博客早点审核过


这题的关键是dp式(虽然我很喜欢用a数组表示)

a[i][j][k] = a[i-k][j-1][k+1]+a[i-k][j-1][k-1];

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

题目描述

焦级长特别喜欢搭积木,一天他创造了一种新的玩法。焦级长一共有N个积木,从下往上一共搭了H层,其中最底层有M个积木,除最底层,每一层的积木数是它下一层积木数+1或-1且每层积木不超过10个。


输入格式

input
输入含多组数据。每组第一行为三个整数N,H,M,第二行后每行一个整数K,以-1结束(1<=N<=540,H<=60,M<=10,K<=10^10)。

输出格式

output
第一行是满足N、H、M的积木搭建方案总数,以后每一行对于对应的K,给出顺序排列的第K种方案(最小的排列为第一种)。
如样例中,2 1 2 3 2 3是一种方案,代表一层的积木数从下往上分别为212323,232321也是一种方案,212323比232321要小,即第一个数小的排前面,第一个数相等的就看第二个数,以此类推。
这里的K就是求第K个按顺序排列的方案。

输入样例

13 6 2
1
3
-1

输出样例

3
2 1 2 3 2 3
2 3 2 3 2 1

 有递推和递归两个版本


递归版本,我比较容易想到递归,虽然很多时候并不是最优的



#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
 
using namespace std;
 
long long a[545][65][13];
bool b[545][65][13];
long long req;
long long cal(int n, int h, int m)
{
    if (n<0) return 0;
    if ((n==0)||(m==0)||(m>10)||(n<m))
        return 0;
    else if (h==1)
    {
        if (n==m)
            a[n][h][m] = 1;
        else
            return 0;
    }
    else if (b[n][h][m]==true) return a[n][h][m];
    else
        a[n][h][m] = cal(n-m, h-1, m-1) + cal(n-m, h-1, m+1);
    b[n][h][m] = true;
    return a[n][h][m];
}
int main()
{
    int n, h, m;
    while(~scanf("%d %d %d",&n, &h, &m))
    {
        memset(b, 0, sizeof b);
        cal(n, h, m);
        printf("%lld\n", a[n][h][m]);
        int n1, h1, m1;
        scanf("%lld", &req);
        while (req!=-1)
        {
            n1 = n;
            h1 = h;
            m1 = m;
            printf("%d", m1);
            for (int i=1; i<h; i++)
            {
                if (req > a[n1-m1][h1-1][m1-1])
                {
                    req -= a[n1-m1][h1-1][m1-1];
                    n1 = n1-m1;
                    m1++;
                }
                else
                {
                    n1 = n1-m1;
                    m1--;
                }
                h1--;
                printf(" %d", m1);
 
            }
            printf("\n");
            scanf("%lld", &req);
        }
    }
    return 0;
}

递推 预处理所有情况


#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
 
using namespace std;
int n, h, m;
long long a[560][65][15];
long long req, t;
void cal()
{
    for (int i=1; i<=10; i++)
        a[i][1][i] = 1;
    for (int i=1; i<=540; i++)
        for (int j=2; j<=65; j++)
            for (int k=1; k<min(i, 11); k++)
                a[i][j][k] = a[i-k][j-1][k+1]+a[i-k][j-1][k-1];
}
int main()
{
    memset(a, 0, sizeof a);
    cal();
    while(~scanf("%d %d %d",&n, &h, &m))
    {
        printf("%lld\n", a[n][h][m]);
        int n1, h1, m1;
 
        scanf("%lld", &req);
        while (req!=-1)
        {
            n1 = n;
            h1 = h;
            m1 = m;
            printf("%d", m1);
            for (int i=1; i<h; i++)
            {
                t = a[n1-m1][h1-1][m1-1];
                if (req > a[n1-m1][h1-1][m1-1])
                {
                    req -= a[n1-m1][h1-1][m1-1];
                    n1 = n1-m1;
                    m1++;
                }
                else
                {
                    n1 = n1-m1;
                    m1--;
                }
                h1--;
                printf(" %d", m1);
 
            }
            printf("\n");
            scanf("%lld", &req);
        }
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值