第八届山东省省赛题解

Posted aiguona

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第八届山东省省赛题解相关的知识,希望对你有一定的参考价值。

A - Problem A. Return of the Nim

题型:

博弈

题意:

有n堆石子,两个人进行游戏,每一步有两种操作方式

操作1:从任意堆里拿任意个,

操作2:从所有堆里拿走x个,其中x不小于最小的那堆石子的个数

保证n是质数

题解:

1.威佐夫博弈:有两堆各若干个物品,两个人轮流从任一堆取或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

2.Nim博弈:有N堆物品,每堆有M[i](1 <= i <= N)个物品,两个人轮流从任意一堆上取任意多的物品,最后取光者胜。两人都采取最优策略,问,是先手赢还是后手赢?

 

3.关于本题,当n=2时,也就是两堆石子的时候,完全符合威佐夫博弈,这一部分可以考虑到。但是当n>2时,这并不是一个严格的尼姆博弈,因为尼姆博弈规定了只能从一堆中选取。这就需要灵活的考虑这个为什么加上操作二也就是从所有堆中取k个仍然是尼姆博弈。这里从网上抄一部分内容。

 

而进行这个取k个操作会产生什么影响呢,举个例子,(偷挪网上大佬的例子..)

有3堆石子,分别是15,6,9 然后转换为二进制形式

1 1 1 1

0 1 1 0

1 0 0 1

此时为P态,进行普通Nim操作肯定会破会P态转移到N态,所以此时不会选取这种操作,那么只能尝试对每堆进行取任意满足条件的k个石子,发现不管怎么取完之后P态也必定会被破坏。原因是,假如对每堆取K个它们二进制最小的位代表的个数个,则取完之后此位的二进制值都会被取反,所以破坏了P态,而且题目的要求是素数个堆(除2外都是奇数),对其他满足条件的二进制位进行取也是如此。所以当>2堆时,对P态进行取同k个操作只会破坏P态,而不会得到想要的P态——>P态,而当为N态时,只需进行普通的Nim操作就可使N态转化为P态,故加入同取k个操作之后也是满足普通Nim堆的。

代码:

#include<bits/stdc++.h>
using namespace std;
double _=(sqrt(5)+1)/2.0;
double eps=1e-5;
int main()
{
    int g,n,a[100],i;
    cin>>g;
    while(g--)
    {
        cin>>n;
        for(i=0; i<n; i++)
            cin>>a[i];
        if(n==2)
        {
            int a1=min(a[0],a[1]);
            int a2=max(a[0],a[1]);
            if(floor((a2-a1)*_)==a1)
                cout<<"Watson"<<endl;
            else
                cout<<"Sherlock"<<endl;
        }
        else
        {
            int ans=0;
            for(i=0; i<n; i++)
                ans^=a[i];
            if(ans)
                cout<<"Sherlock"<<endl;
            else
                cout<<"Watson"<<endl;
        }
    }
    return 0;
}

 

B - Problem B. Quadrat

题型:

规律

题意:

求满足以下条件

①.这个数为n位(可以有前导零)

②.取它的平方的后n位,与它本身每一位对应之差≤d(这里的差指的是数字之间的距离,而这个距离是将数字按圈排列,0与9相邻所求得的)

的数字的个数。

题解:

这个题个人觉得数据量不算大, 可以考虑本地打表存入数组(我没看清是否能存下来),当然也可以打表找规律,再写标准代码。(所谓的本地打表就是暴力的情况,直接根据题意来写程序,然后将运行出来的结果存到数组中)

本地打表代码

#include <bits/stdc++.h>  
using namespace std;  
typedef long long LL;  
const int MAX = 10005;  
const int MOD = 1e9+7;  
const int INF = 0x3f3f3f3f;  

int a[15][15];    
LL mypow[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000};  
int main()  
{  
    memset(a, 0, sizeof(a));  
  
    for(int i = 0; i <= 9; ++i)  
    {  
        for(int j = 0; j <= 9; ++j)  
        {  
            a[i][j] = abs(i-j);  
            if(a[i][j] > 5) a[i][j] = 10 - a[i][j];  
            //cout << a[i][j] << " ";  
        }  
        //puts("");  
    }  
  
    for(int n = 1; n <= 7; ++n)  
    {  
        for(int eps = 0; eps <= 3; ++eps)  
        {  
            int tot = 0;  
            for(int i = 0; i < mypow[n]; ++i)  
            {  
                //cout << "i=" << i << endl;  
                int tt = i;  
                LL tm = (i%mypow[n])*(i%mypow[n]);  
                tm %= mypow[n];  
                int j;  
                for(j = 0; j < n; ++j)  
                {  
                    if(a[tm%10][tt%10] > eps) break;  
                    tm /= 10;  
                    tt /= 10;  
                }  
                if(j == n)  
                {  
                    ++tot;  
//                    cout << i << endl;  
                }  
            }  
            cout << tot << " ";  
            //cout << "tot=" << tot << endl;  
        }  
        puts("");  
    }  
    return 0;  
}  

 

本地打表运行结果:

 技术分享图片

 

观察发现,对应的【第i行的第j列的值】等于【第i-1行第j列的值】*【(j*2)+1】

 

代码:

#include <bits/stdc++.h>  
using namespace std;  
typedef long long LL;  
const int MAX = 10005;  
const int MOD = 1e9+7;  
const int INF = 0x3f3f3f3f;  
  
LL ans[20][5];  
void init()  
{  
    ans[1][0] = ans[1][1] = 4;  
    ans[1][2] = ans[1][3] = 8;  
    for(int n = 2; n <= 18; ++n)  
        for(int d = 0; d <= 3; ++d)  
            ans[n][d] = ans[n-1][d] * (d * 2 + 1);  
}  
  
int main()  
{  
    init();  
    int t;  
    int n, d;  
    scanf("%d",&t);  
    while(t--)  
    {  
        scanf("%d%d",&n,&d);  
        printf("%lld
", ans[n][d]);  
    }  
    return 0;  
}  

C - Problem C. fireworks

题型:

杨辉三角+逆元

题意:

有一种特殊的烟花,他每一秒可以向自己的左右两边分裂相同数量的烟花

求所给位置在T时间后有多少个烟花

要求输入n(烟花的数量),T(烟花的持续时间),w(所求的位置)

接下来n行每一行输入两个数xi(烟花的初始位置)  ci(该点的烟花数量)

题解:

我们假设在0的位置有一个烟花

下面这个图给出了在1,2,3,秒时,各个坐标点产生的烟花的数量(蓝色为烟花数量)

 技术分享图片

 

仔细观察我们就可以发现这是一个我们很熟悉的数学定理,杨辉三角

                                 1

                              1    1

                           1    2   1

                         1  3     3  1

                       1 4    6   4  1

                     ……………………

杨辉三角的计算过程就是C(n,m)。例如第五行第二个就是C(4,1);

有了这个之后就很好办了,只要能确定C(n,m)其中的n和m就可以了

在这里我们可以看出来n就是题目中给出的T。

要求m需要略作思考,这里我们设一个temp代表所求位置到烟花的绝对值,这里我们以图为例子推演一下,假设所求位置是-1,时间限制是3秒,烟花的初始位置是0,那么两点之间的绝对值temp=1,我们可以发现这里我们要求的m= (T+1/2)-(temp+1/2)。

同时我们看下图,只是将杨辉三角里面填上了0;

 技术分享图片

 

这个题用到的主要算法是逆元组合数。

 

#include <bits/stdc++.h>
typedef long long LL;
const long long mod = 1e9+7;
const int maxn = 1e5+10;
#define IO ios::sync_with_stdio(false);
    cin.tie(0);    cout.tie(0);
using namespace std;
LL fac[maxn];
void init()
{
    fac[0] = 1;
    for(int i = 1; i < maxn; i++)
    {
        fac[i] = (fac[i-1]*i)%mod;
    }
}
LL inv1(LL a)
{
    return a==1?1:(mod-mod/a)*inv1(mod%a)%mod;
}
LL C(int a,int b)
{
    if(a<b)
        return 0;
    return fac[a]*inv1(fac[b]*fac[a-b]%mod)%mod;
}
int main()
{
    IO;
    int n,t,w;
    init();
    while(cin>>n>>t>>w)
    {
        long long ans=0;
        for(int i=0; i<n; i++)
        {
            int x,c;
            cin>>x>>c;
            int l=x-t;
            int r=x+t;
            int te=w-l;
            if(w<l||w>r||te%2!=0)
            {
                continue;
            }
            else
            {
                ans+=(c*C(t,te/2))%mod;
                ans=ans%mod;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

 

 D - Problem D. HEX

 

题型:

组合数+逆元+思维

题意:

给你一个类似于蜂巢型的东西,每一次只能向左下,中间和右下走一步,给定你(x,y),问你从(1,1)走到(x,y)这个为位置有多少种情况。

题解:

我们通过观察可以看得出来从(1,1)到(A,B)有两种主要的方式

 技术分享图片

 

方式一:走的方式中不包括垂直向下

方式二:走的方式中有垂直向下

这里用一个例子来展开

我们假设所求为(1,1)到(5,3)的路径数

我们可以看到,

方式一:需要走两次左下和两次右下。

方式二:①一次左下一次右下,一次垂直向下   ②两次垂直向下

通过比较方式一和方式二我们发现了一件有趣的事,一次垂直向下操作相当于一次左下加一次右下操作

这里我们先来讨论方式一,方式一的路径数是

 技术分享图片

 

l是左下的次数,r是右下的次数,A代表全排列也就是阶乘

为什么要采用这个公式呢?这个可以看成是一个排列组合的问题,先把所有的步骤全排列然后去掉其中重复的组合数

方式二的求法只要枚举就可以了,枚举方式是,每次left--,right--,down++,这个时候的公式就变成了

 技术分享图片

 

枚举到right或者left其中一个为0,这就代表没有办法再抵消成一个down了

因为本题要求我们mod1e9+7,所以就想到了逆元。

#include <bits/stdc++.h>
typedef long long LL;
const long long mod = 1e9+7;
const int maxn = 2e5+10;
#define IO ios::sync_with_stdio(false);
    cin.tie(0);    cout.tie(0);
using namespace std;
LL fac[maxn];
LL inv[maxn];

void init()
{
    fac[0] = 1;
    for(int i = 1; i < maxn; i++)
    {
        fac[i] = (fac[i-1]*i)%mod;
    }
}
LL inv0(LL a)//递推法求逆元
{
    return a==1?1:(mod-mod/a)*inv0(mod%a)%mod;
}
void inv1()
{
    inv[0]=1;
    for(int i=1;i<maxn;i++)
        inv[i]=inv0(fac[i]);
}
int main()
{
    IO;
    LL a,b;
    init();
    inv1();
    while(cin>>a>>b)
    {
      LL right=b-1;
      LL left=a-b;
      LL down=0;
      LL sum=0;
      while(right>=0&&left>=0)
      {
          sum=(sum+(((fac[right+left+down]*inv[right])%mod*inv[left])%mod*inv[down])%mod)%mod;
          right--;left--;down++;
      }
      printf("%lld
",sum);
    }
    return 0;
}

 

E - Problem E. news reporter

真~不会

F - Problem F. quadratic equation

题型:

二元一次方程

题意:

用给定的整数a,b,c,要求你判断下面的陈述是否属实:对于任何x,如果a?x^2+b?x+ c = 0,那么x是一个整数。

题解:

这个题请多次读题,并且需要了解一下离散的内容(可能叫辩证关系?演绎推理?逻辑?)这里有一个说明:如果前件为真,当结论为真时,命题为真,结论为假时,命题为假;如果前件为假,那么命题一定为真,不管结论是否为真。

也就是说只有当前件成立后件不成立时输出  "NO"   其他情况都输出  "YES"   本题就是  当方程有解并且解不为整数时输出 "NO"  其他情况输出 "YES"

代码

#include <bits/stdc++.h>
typedef long long LL;
const long long mod = 1e9+7;
const int maxn = 2e5+10;
#define IO ios::sync_with_stdio(false);
    cin.tie(0);    cout.tie(0);
using namespace std;
int main()
{
    IO;
    int T;
    cin>>T;
    while(T--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        if(a==0)
        {
            if(b==0&&c==0)
                cout<<"NO"<<endl;
            else if(b!=0&&c!=0&&(c%b)!=0)
                cout<<"NO"<<endl;
            else
                cout<<"YES"<<endl;
        }
        else if(a!=0)
        {
            double temp=(b*b)-(4*a*c);
            if (temp>=0)
            {
                if ((int)sqrt(temp)!=sqrt(temp))
                    cout<<"NO"<<endl;
                else
                {
                    if ((-1*b-(int)sqrt(temp))%(2*a)!=0||(-1*b+(int)sqrt(temp))%(2*a)!=0)
                        cout<<"NO"<<endl;
                    else
                        cout<<"YES"<<endl;
                }
            }
            else
                cout<<"YES"<<endl;
        }
    }
    return 0;
}

G - Problem G. sum of power

题型:

签到 快速幂

题意:

计算从1到n的m次方的和对1E9+7取余。

题解:

简单的快速幂。

代码:

#include <bits/stdc++.h>

typedef long long LL;
const long long mod = 1e9+7;
const int maxn = 2e5+10;
#define IO ios::sync_with_stdio(false);
    cin.tie(0);    cout.tie(0);
using namespace std;
LL poww(LL a, LL b, LL c)//a^b%c
{
    LL ans = 1;
    a = a % c;
    while(b>0)
    {
        if(b % 2 == 1)
            ans = (ans * a) % c;
        b = b/2;
        a = (a * a) % c;
    }
    return ans;
}

int main()
{
    IO;
    int m,n;
    while(cin>>n>>m)
    {
        LL ans=0;
        for(int i=1; i<=n; i++)
            ans=(ans+poww(i,m,mod))%mod;
        cout<<ans<<endl;
    }
    return 0;
}

H - Problem H. triangle

真~不会

I - Problem I. Parity check

题型:

签到 规律

题意:

给出 f(n) 的递推式以及 n 的值,求 f(n)%2 的结果。

题解:

简单的找规律。给定的数会很大,一定要用数组存储。对2取余实际就是求奇偶。找出规律是一个偶数后接着两个奇数。所以对3取余,如果余数是0(代表偶数)输出0,余数是1(代表奇数)或者2则输出1。

代码:

#include <bits/stdc++.h>

typedef long long LL;
const long long mod = 1e9+7;
const int maxn = 2e5+10;
#define IO ios::sync_with_stdio(false);
    cin.tie(0);    cout.tie(0);
using namespace std;
int main()
{
    IO;
    char a[1010];
    while(cin>>a)
    {
        int len=strlen(a);
        int sum=0;
        for(int i=0;i<len;i++)
        {
            sum=(sum*10+a[i]-48)%3;//记得每次都要对3取余
        }
        if(sum%3==0)
            cout<<0<<endl;
        else
            cout<<1<<endl;
    }
    return 0;
}

 

J - Problem J. company

题型:

贪心

题意:

商店买东西,有n种商品,每种商品卖出去以后得到的价值是v,每种商品有c件,第i天卖出v价值的商品,得到的价值是i*v,时间是从第一天开始,每天只能卖一件,对于每件商品你可以选择卖或者不卖,求卖了这些商品的最大利益是多少。

题解:

把这个题归类到贪心里面去吧,最后求最大利益。中间需要对商品的价格进行排序。

把所有商品的价值存在数组中(包括数量大于一的),排序并顺便求出当前的 ans 。

然后从数组最左边开始枚举,判断是否需要去除当前商品,若去除,则 ans-=a[i]-sum[top-1]+sum[i] ,因为总天数减少了一天,所以后面的所有商品价值和也应该减少。

判断如果当前得到新的 ans 变小了,跳出,输出最大的 ans 。

代码:

#include <bits/stdc++.h>
 
typedef long long LL;
const long long INF = 0x3f3f3f3f;
const long long mod = 1e9+7;
const int maxn = 100000;
#define IO ios::sync_with_stdio(false);
    cin.tie(0);    cout.tie(0);
 
using namespace std;
 
int a[maxn],sum[maxn],d[maxn];
int main()
{
    IO;
    int n;
    while(cin>>n)
    {
        int logo=n;
        for(int i=0; i<n; i++)
            cin>>a[i];
        for(int i=0; i<n; i++)
        {
            cin>>d[i];
            for(int j=1; j<d[i]; j++)
                a[logo++]=a[i];
        }
        sort(a,a+logo);
        sum[0]=a[0];
        LL ans=a[0];
        for(int i=1; i<logo; i++)
        {
            sum[i]=sum[i-1]+a[i];
            ans+=(i+1)*a[i];
        }
        for(int i=0; i<logo; i++)
        {
            LL s=ans-a[i]-sum[logo-1]+sum[i];
            if(s>ans)
                ans=s;
            else break;
        }
        cout<<ans<<endl;
    }
    return 0;
}

K - Problem K. CF

题型:

贪心+背包

题意:

在持续t分钟的比赛中有n个问题,对于第i个问题,你可以得到ai-di * ti的分数,其中ai表示初始分数,di表示每分钟下降的分数(从比赛开始计数),ti代表解决问题时的分钟数(从比赛开始算起)。现在你知道艾德可以在多长解决第一个问题。你能帮他尽可能多的得分吗?

题解:

直接贪心肯定是不行的。

假设我们此时贪心出来一个做题顺序,使得减少的分数按照相对时间来讲最小。

那么我们直接按序模拟是不行的。因为涉及到这样一点:当前题目做不做对后边是有影响的。一道题的分数高不高和做题时间都会对后边内容有所影响的。

所以我们现在如果有一个相对贪心的顺序之后,我们不妨看做时间为花费,得到的收益就是a【i】.val-a【i】.del*tottime.

那么这里做一个01背包就行。那么考虑如何排序:

我们不能根据减小的速度来评判一个题是否有序,因为这一道题的时间也会影响下一个题的减少量。所以我们排序应当按照单位时间来排序。

那么总结概述就是:按照题的单位时间减少分数从小到大排序,然后做一个01背包即可。

代码:

#include <bits/stdc++.h>

typedef long long LL;
const long long INF = 0x3f3f3f3f;
const long long mod = 1e9+7;
#define IO ios::sync_with_stdio(false);
    cin.tie(0);    cout.tie(0);

using namespace std;
struct node
{
    int val;
    int del;
    int tim;
} a[5500];
int dp[70000];
int cmp(node a,node b)
{
    return (double)a.del/(double)a.tim>(double)b.del/(double)b.tim;
}
int main()
{
    int n,m;
    while(cin>>n>>m)
    {
        memset(dp,0,sizeof(dp));
        for(int i=0; i<n; i++)
            cin>>a[i].val;
        for(int i=0; i<n; i++)
            cin>>a[i].del;
        for(int i=0; i<n; i++)
            cin>>a[i].tim;
        sort(a,a+n,cmp);
        for(int i=0; i<n; i++)
        {
            for(int j=m; j>=a[i].tim; j--)
            {
                dp[j]=max(dp[j],dp[j-a[i].tim]+a[i].val-a[i].del*j);
            }
        }
        int ans=0;
        for(int i=0; i<=m; i++)
            ans=max(ans,dp[i]);
        cout<<ans<<endl;
    }
    return 0;
}

 

以上是关于第八届山东省省赛题解的主要内容,如果未能解决你的问题,请参考以下文章

第八届山东ACM省赛F题-quadratic equation

第八届蓝桥杯省赛题解

山东省第八届ACM程序设计大赛总结

第八届山东省ACM大学生程序设计竞赛个人总结

蓝桥日记①2017第八届省赛(软件类)JavaA组❤️答案解析

《蓝桥杯真题》:2017年单片机省赛(第八届)(内附两种代码实现风格)