第五届山东省省赛题解

A - angry_birds_again_and_again

题意:

就是给你一个经过(0,0)点的一个二次曲线 在给你一个tx,px,角A,问你在tx点二次曲线和切线还有x轴正半轴围成的曲线。

思路:

求出二次曲线的表达式然后定积分求面积即可。

设二次曲线的表达式为 y=a*x*x+b*x+c;

因为其过(0,0)点所以 c=0;

方程两边求导 y’=2*a*x+b 在(0,0)点的倒数为 b 用因为角AOX=角A 所以 b=tan(角A);

接下来仅需要求a即可:利用RT三角形TPD(设从TX点做垂直与x轴正半轴的直线与二次曲线的交点为D)

绝对值 tan(角DPT)=AT/PT;(注意这里的斜率是负的所以加绝对值) 推出来 -2*a*t-b=(a*t*t+b*t)/(p-t);

令c=p-t; 偷懒方便书写

a=(-b*c-b*t)/(t*t+2*t*c);

最后直接求面积即可

总面积=定积分求二次曲线面积+三角形DPT面积 S=(a/3)*t*t*t+(b/t)*t*t+0.5*(a*t*t+b*t)*c。

代码:

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

using namespace std;
const int N=5e5+10;
//设给你的二次函数的解析式为y=a*x*x+b*x+c
//然后输入的 p为px t为Tx jiao为角AOX的大小

int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        double ans=0,a=0,b=0,jiao=0,p,t,c;
        cin>>p>>t>>jiao;
        c=p-t;
        b=tan(jiao);
        a=1.0*(-b*c-b*t)/(t*t+2*t*c);
        ans=(1.0/3.0)*t*t*t*a+0.5*b*t*t+0.5*(a*t*t+b*t)*c;
       printf("%.3lf\n",ans);

    }
    return 0;
}

B - Circle

题意:

n个数围成一个圈,编号0~n-1,从一个数到其上一个和下一个的数的概率相同(即都是0.5)。给出n,求从0出发到达一个数x所需要的步数的数学期望。

思路:

 d[i]=0.5*d[i+1]+0.5*d[i-1]

 d[n]=d[0]=0   起点

 d[i]=d[n-i]    对称

规律:

N    0 1 2 3 4 5 6 7 8 9

1    0

2    0 1

3    0 2 2

4    0 3 4 3

5    0 4 6 6 4

.....

N    0 n-1 2n-4 3n-9

即求期望的递归

#include <iostream>
#include <cmath>
#include <cstdio>
#include<cstring>
#include<cstdio>
using namespace std;
int main()
{
    int T;
    int n,x,i;
    double d[1010];
    cin>>T;
    while(T--)
    {
        cin>>n>>x;
        d[0]=0;
        if(x>n/2)
            x=n-x;
        for(i=1;i<=x;i++)
            d[i]=d[i-1]+n-(2*i-1);
        printf("%.4lf\n",d[x]);
    }
    return 0;
}

C - Colorful Cupcakes

题意

n块蛋糕 ABC三种颜色 分给一个圆桌n个人 相邻两个人的颜色不能相同,返回能给朋友的方式数 

思路

五维数组 1维代表开始的颜色 2,3,4维代表三种颜色使用的次数 5维代表上一次使用的蛋糕是哪一种 
然后用记忆化搜索dfs 

动态规划的一种变形就是记忆化搜索,就是根据动态规划转移方程写出递归式,然后在函数开头直接返回以前计算的结果当然这样做也需要一个存储结构记录下之前计算出的结果,所以称为记忆化搜索。 
接下来给出其定义。

记忆化算法在求解的时候还是按着自顶向下的顺序,但是每求一个解的状态就要将其保存下来,以后在遇到这个状态的时候就没有必要再求一遍了。这种算法综合了搜索和动态规划两方面的优点。

记忆化搜索/递归式动态规划:

思想:在搜索过程中,会有很多的重复计算,如果我们可以记录下得出答案,就可以减少重复搜索量。 
适用范围:它解决的是重复计算,而不是重复生成。也就是说,这些搜索必须是在搜索扩展路径的过程中分步计算的题目,也就是“搜索答案与路径相关”的题目,而不能是搜索一个路径之后才能进行计算的题目,一定要分步计算,并且在搜索过程中,一个搜索结果必须可以建立在同类型问题的结果上,也就是类似于动态规划解决的那种问题。 
这问题,不是单纯生成一个走步方案,而是生成一个走步方案的代价等,而且每走一步,在搜索树/图中生成一个新状态,都可以分步计算。 
实现: 
(1)通过一个表记录已经存储下的搜索结果,一般用哈希表实现。 
(2)状态表示,由于是要用哈希表实现,所以状态最好是用数字表示,常用的方法是把一个状态写成一个p进制数字,然后把这个数字对应的十进制数作为状态(该题目不需要用此方法) 
(3)在每一状态搜索的开始,高效的使用哈希表搜索这个状态是否出现过,如果出现过,直接调用答案,回溯 
(4)如果没有,则用正常方法搜索

记忆化搜索类似于动态规划,它是倒做的“递归式动态规划”。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N=55;
const long long M=1000000007;
long long dp[4][N][N][N][4];
//五维数组 1维代表开始的颜色 2,3,4维代表三种颜色使用的次数 5维代表上一次使用的蛋糕是哪一种
int len,tmpa,tmpb,tmpc;
//tmpa A颜色的数量 tmpb B颜色的数量 tmpc C颜色的数量

int dfs(int start,int a,int b,int c,int t)
{
    if(a>tmpa||b>tmpb||c>tmpc) //任意一种颜色的蛋糕被吃光
        return 0;
    if(a+b+c==len&&t==start)  //全部分配完并且结尾等于开头
        return 0;
    if(dp[start][a][b][c][t]!=-1)  //记忆化搜索的剪枝
        return dp[start][a][b][c][t];
    if(a+b+c==len&&t!=start) //最终完美状态
        return 1;
    long long tmpp=0;
    if(t==1)
    {
        tmpp=(tmpp%M+dfs(start,a,b+1,c,2)%M)%M;
        tmpp=(tmpp%M+dfs(start,a,b,c+1,3)%M)%M;
        dp[start][a][b][c][t]=tmpp;//记忆
        return tmpp;
    }
     if(t==2)
    {
        tmpp=(tmpp%M+dfs(start,a+1,b,c,1)%M)%M;
        tmpp=(tmpp%M+dfs(start,a,b,c+1,3)%M)%M;
        dp[start][a][b][c][t]=tmpp;
        return tmpp;
    }
     if(t==3)
    {
        tmpp=(tmpp%M+dfs(start,a+1,b,c,1)%M)%M;
        tmpp=(tmpp%M+dfs(start,a,b+1,c,2)%M)%M;
        dp[start][a][b][c][t]=tmpp;
        return tmpp;
    }
}
int main()
{
    ios::sync_with_stdio(false);
    int T;
    char s[1000];
    cin>>T;

    while(T--)
    {
        tmpa=0,tmpb=0,tmpc=0;
      memset(s,0,sizeof(s));
      memset(dp,-1,sizeof(dp));
     cin>>s;

      long long ans=0;
      len=strlen(s);
      for(int i=0;i<len;i++)
      {
          if(s[i]=='A')
            tmpa++;
          if(s[i]=='B')
            tmpb++;
          if(s[i]=='C')
             tmpc++;
      }
      ans=(ans%M+dfs(1,1,0,0,1)%M)%M;
      ans=(ans%M+dfs(2,0,1,0,2)%M)%M;
      ans=(ans%M+dfs(3,0,0,1,3)%M)%M;

      cout<<ans<<endl;
    }
    return 0;
}

E - Factorial

题意:

给你T组数a,问你它的阶乘

思路:

n小于等于10 直接暴力即可

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
using namespace std;
const int N=5e5+10;

int main()
{
    ios::sync_with_stdio(false);
    int T;
    cin>>T;
    while(T--)
    {
        int n;
        cin>>n;
        long long ans=1;
        for(int i=1;i<=n;i++)
            ans*=i;
        cout<<ans<<endl;
    }
    return 0;
}

F - Full Binary Tree

题意:

在一个满二叉树中,任意给出两点的下标,求出这两点最短的路径长度

分析:

对于满二叉树来说,从叶子节点i 到根节点的最短路径长度为logi 向下取整。由于i ≤ 10^9,因此最短路径长度不会超过31,所以树中任意两个节点间的最短路径长度不会超过62。所以用递归找两个节点的共同根结点就可以了。

#include <iostream>
#include <cmath>
#include <cstdio>
#include<cstring>
using namespace std;
int ans;
void Find(long long n,long long m)
{

    if(n==m)
        return ;
        ans++;
    if(n>m)
         Find(n/2,m);
        else
         Find(n,m/2);
}
int main()
{
    int T;
    long long n,m;
    cin>>T;
    while(T--)
    {
        ans=0;
        cin>>n>>m;
        Find(n,m);
        cout<<ans<<endl;
    }
    return 0;
}

G - Hearthstone II

题意

新赛季开始,有n场比赛,m个场地,问你有多少种办法来规划。

思路

就是第二类stirling数中几种常见模型的第二种(因为每个场地都是不同的) 

接下来介绍一下stirling数:

Stirling数出现在许多组合枚举问题中。对第一类Stirling数 ,也可记为 。表示将 n 个不同元素构成m个圆排列的数目(有序)。同时还分为无符号第一类Stirling数 和带符号第一类Stirling数 。 
第二类Stirling数  ,同时可记为 [与第一类的表示有大小写的区别]。其表示将n个不同的元素分成m个集合的方案数(无序)。

在介绍第一类stirling数前,简单说一下 圆排列的定义:从n个不同元素中不重复取出m(1<=m<=n)个元素在一个圆周上,叫做这n个不同元素的圆排列。

无符号第一类stirling数的递推式:
有符号的第一类Stirling数的递推式:

第二类stirling数的递推式:

第二类stirling数应用举例(很重要)本题就为以下第二种情况 
第二类Stirling数主要是用于解决组合数学中的几类放球模型。主要是针对于球之前有区别的放球模型: 
(1)n个不同的球,放入m个无区别的盒子,不允许盒子为空。 
方案数: 。这个跟第二类Stirling数的定义一致。 
(2)n个不同的球,放入m个有区别的盒子,不允许盒子为空。 
方案数:  。因盒子有区别,乘上盒子的排列即可。 
(3)n个不同的球,放入m个无区别的盒子,允许盒子为空。 
方案数: 。枚举非空盒的数目便可。 
(4)n个不同的球,放入m个有区别的盒子,允许盒子为空。 
①方案数:  。同样可以枚举非空盒的数目,注意到盒子有区别,乘上一个排列系数。 
②既然允许盒子为空,且盒子间有区别,那么对于每个球有m中选择,每个球相互独立。有方案数:  。
 

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
using namespace std;
const int N=200;
const long long M=1e9+7;
long long dp[N][N];

int main()
{
    ios::sync_with_stdio(false);
    int n,m;
    memset(dp,0,sizeof(dp));
    dp[1][1]=1;
    for(int i=2;i<=101;i++)
        for(int j=1;j<=i;j++)
           dp[i][j]=(dp[i-1][j-1]+j*dp[i-1][j])%M;
    while(cin>>n>>m)
    {
        long long ans=1;
        for(int i=1;i<=m;i++)
            ans=(ans%M*i%M)%M;
        cout<<(ans*dp[n][m])%M<<endl;
    }
    return 0;
}

J - Weighted Median

题意

给你n个数然后第一行是这些数xi,第二行是他们分别对应的权值wi。s是权值总和。然后让你找一个数xk满足公式,(x1~xk)求和>s/2,(xk~xn)求和小于等于s/2;可看题目中的公式因为两边求和都不存在xk的权值,则只要找到一半小于s/2则另一半一定小于等于s/2。

思路

将其存在一个结构体中,然后按x的大小进行排序(代码按降序排序),然后开一个变量ans=0遍历排完序的序列,当ans>=s/2时,记录下标k跳出循环即可,最后输出k所对应的x即可。

这里的数据量很大 所以结构体数组要开到1e7+10 并且 下面求和的两个变量都需要开long long 或double 不然会RE

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
using namespace std;
const int N=1e7+10;
struct F
{
  int x;
  int w;
}a[N];

bool cmp(struct F aa,struct F bb)
{
   return aa.x>bb.x;
}

int main()
{
    ios::sync_with_stdio(false);
    int n;
    while(cin>>n)
    {
       long long s=0;
       for(int i=1;i<=n;i++)
       {
           cin>>a[i].x;
       }
       for(int i=1;i<=n;i++)
       {
           cin>>a[i].w;
           s+=a[i].w;
       }
       s/=2;
       sort(a+1,a+n+1,cmp);
       long long ans=0;
       int k=0;
       for(int i=1;i<=n;i++)
       {
           ans+=a[i].w;
           if(ans>=s)
           {
               k=i;
               break;
           }
       }
       cout<<a[k].x<<endl;
    }
    return 0;
}

 

猜你喜欢

转载自www.cnblogs.com/aiguona/p/9231431.html