第十一届山东省大学生程序设计竞赛,补题+总结

B题

题目链接如下:

B题入口

样例输入

5 1 200000 123

输出

4

思路如下:

最小生成树问题(MST),对kruskal进行变形,但由于这题范围太大,不能暴力枚举所有任意两个数的最大公约数,所以要看到这道题目的特殊性。因为我们看到了边权是gcd即最大公约数,考虑到素数分布:
当n很大时(差不多大于1000时),就肯定存在素数,边权就都是1,得出的最小生成树值就是n - 1。在n小于1000时正常跑kruskal,另外还有一种情况,就是L = R时,说明边权都是L,得出的最小生成树就是(n - 1) * L,并且这里注意开long long,不然还会有用例过不了。

代码如下:

#include <iostream>
#include <bits/stdc++.h>

using namespace std;
typedef unsigned long long LL;
const int N = 100010;
int idx;//这里用来记录边数。
int p[N];
struct Edge
{
    int a, b, w;
    bool operator < (const Edge& t) const
    {
        return w < t.w;
    }
} edge[N];

int n,L,R,a[200001];
int gc[100][100];
LL seed;
LL xorshift64()
{
    LL x=seed;
    x^= x<<13;
    x^= x>>7;
    x^= x<<17;
    return seed=x;
}
int gen()
{
    return xorshift64() % (R-L+1)+L;
}

int gcd(int a,int b)
{
    if(b>a)
    {
        int c=a;
        a=b;
        b=c;
    }
    int yu=1;
    while(yu!=0)
    {

        yu=a%b;
        a=b;
        b=yu;
    }
    return a;
}

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}


LL kruskal()
{
    sort(edge + 1, edge + 1 + idx);
    LL res = 0, cnt = 0;

    for (int i = 1; i <= idx; i++)
    {
        int a = edge[i].a, b = edge[i].b, w = edge[i].w;

        a = find(a), b = find(b);
        if (a != b)
        {
            p[a] = b;
            res += w;
            cnt++;
        }
        if (cnt == n - 1) break;
    }
    return res;
}

int main()
{
    scanf("%d%d%d%llu",&n,&L,&R,&seed);

    for(int i=1; i<=n; i++)
    {
        a[i]=gen();
    }
    if(L==R)
    {
        cout<<(LL) (n-1)*L<<endl;
        return 0;
    }
    if(n>=1000)
    {
        cout<<n-1<<endl;
        return 0;
    }
    for(int i=1; i<=n; i++)
    {
        p[i]=i;
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
        {
            int w = gcd(a[i], a[j]);
            edge[idx ++ ] = {i, j, w};
        }
    cout<<kruskal()<<endl;
    return 0;
}

C题

题目链接如下:

C题入口

样例输入

5

输出

3
1 2
1 3

思路如下:

这道题我存在很大的问题,主要就是看好几遍题目后不知道这道题究竟想让输出什么,后来经过朋友提醒和自己思考才算是明白了。
结合前面的题目描述,大概就是这棵树如果根节点为黑那么其所有子结点为黑。如果其根节点为白的话,则其子结点没有限制。
考虑完全二叉树:得出规律,f=l*r+1,f表示数目,l表示左子树数目,r表示右子树数目

结点数目种类数目
23
35
47
511
616
726

代码如下:

#include <iostream>
#include <bits/stdc++.h>
using namespace std;

int main()
{
    long long k;
    long long a[20000]={0};
    long long b[20000]={0};
    int i=1;
    cin>>k;
    if(k==2)
        cout<<"1"<<endl;
    else
    {
        long long n=k-1;
        long long c=1;
        long long d=2;
        for(i=1;n>0;i++)
        {
            if(n==2)
            {
                a[i]=c;
                b[i]=d;
                cout<<d<<endl;
                break;
            }
            if(!(n&1))
            {
                a[i]=c;
                b[i]=d;
                d++;
                n=n/2;
            }
            else
            {
                a[i]=c;
                b[i]=d;
                c=d;
                d++;
                n--;
            }
        }
        for(int j=1;j<=i;j++)
        {
            cout<<a[j]<<" "<<b[j]<<endl;
        }
    }


    return 0;
}

D题

题目链接如下:

D题入口

样例输入

4
1 2
3 2
2 1
4 1

输出

4 4
8 6
8 8
10 8

思路如下:

本题的思路就是将两个方向分开算,因为开一个二维数组明显是不太现实的,因为规模太大了,而且处理起来时间肯定也是会超限的。
所以我们直接开两个数组,一个表示x轴方向上的情况,另一个表示y轴方向上的情况,我们以x轴方向为例说明本题的规律。
如下图右上角部分所示:
本张图片即是示例的情况
每一列出现一个元素就在该列上+1,而该列的周长和总共的周长分别满足下面关系。
设该列上有i个元素,则在该列上的周长为d=2
*i+2,考虑到左右之间有重叠的,我们可以每加一个处理一次,每加一次该列上的周长就加上二,然后将重叠的去掉。

代码如下:

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int a[200005]= {0};
int b[200005]= {0};
 
int main()
{
    int n;
    scanf("%d",&n);
    int x,y;
    int sumx=0,sumy=0;
    for(int i=0; i<n; i++)
    {
        cin>>x>>y;
        a[x]++;
        b[y]++;
        if(a[x]==1)
            sumx+=4;
        else
            sumx+=2;
        if(a[x]<=a[x+1])
            sumx-=2;
        if(a[x]<=a[x-1])
            sumx-=2;
 
        if(b[y]==1)
            sumy+=4;
        else
            sumy+=2;
        if(b[y]<=b[y+1])
            sumy-=2;
        if(b[y]<=b[y-1])
            sumy-=2;
        cout<<sumx<<" "<<sumy<<endl;
 
    }
    return 0;
}

G题

题目链接如下:

G题入口

样例输入

3 10
94 100 99

输出

97.6666666666

思路如下:

本题的思路很简单,就是给定一个数n,表示输入数的个数,给定一个数k,题目要求的是求平均数,该数保留小数点后k位。很明显k最大达到了100000,那一定不能直接算。很多人是用字符串来模拟的,我这个思路省去了这个步骤:
先把整数部分求出来,点上小数点,再一遍遍循环求小数部分的每一位,循环k次即可。

代码如下:

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
 
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    int sum=0;
    int zan;
    for(int i=0;i<n;i++)
    {
        scanf("%d",&zan);
        sum+=zan;
    }
    int chu=sum/n;
    int yu=sum%n;
    cout<<chu<<".";
    for(int i=0;i<k;i++)
    {
        yu*=10;
        chu=yu/n;
        yu=yu%n;
        cout<<chu;
    }
    return 0;
}

H题

题目链接如下:

H题入口

样例输入1

2 66 22
1 23 2
66 8 90

输出

2

样例输入2

4 16 22
1 23 11
5 8 14
2 36 99
15 22 27

输出

27

思路如下:

题目大意是Yuna去打怪,每个怪有生命值和能量值以及价值,打掉这个怪后Yuna获得其价值,但自身生命值和能量值减少同样的点,能量值不够的话减少相应的生命值,生命值小于等于零时Yuna死亡。保证其不死且获得最大价值。
这是一道典型的二维01背包问题,简单说一下就是创建一个二维数组,用元素dp[i][j]来表示有i点生命值,j点能量值的最优解。
其中的核心部分就是我们要将Yuna能量值减小到小于零的情况进行讨论。描述在注释部分。

代码如下:

#include <bits/stdc++.h>
 
using namespace std;
long long n,H,S,h[1212],s[1212],w[1212],dp[301][301];
int main()
{
    cin >>n>>H>>S;
    for(int i=1; i<=n; i++)
        cin >>h[i]>>s[i]>>w[i];
    for(int i=1; i<=n; i++)
    {
        for(int j=H; j>=0; j--)
        {
            for(int k=S; k>=0; k--)
            {
                if(k>=s[i]&&j>=h[i])//这是生命值和能量值都大于零的情况,即一般情况。
                    dp[j][k]=max(dp[j][k],dp[j-h[i]][k-s[i]]+w[i]);
                else if(k<s[i]&&j+k>=h[i]+s[i])//这就是能量值降为零之后的情况,注意更新时是在dp[j][0]处更新的。
                    dp[j][k]=max(dp[j][k],dp[j+k-h[i]-s[i]][0]+w[i]);
                else
                    dp[j][k]=dp[j][k];//这就是不更新的情况,直接使用本身的值。
            }
        }
    }
    cout <<dp[H-1][S];
    return 0;
}
/*
2 66 22
1 23 2
66 8 90
 
4 16 22
1 23 11
5 8 14
2 36 99
15 22 27
*/

M题

题目链接如下:

M题入口

样例输入

5 5
00000
00100
01010
01100
00000

输出

00000
00100
01110
01100
00000
00000
01110
01010
01110
00000

思路如下:

题目大意是给定一个n行m列的矩阵,只由0或1构成,且边框都为0,要找到两个相同规模的矩阵,只有这两个矩阵相应位置都为1时,原矩阵相应位置才为1,寻找这两个矩阵。(答案不唯一)
这里采用的思路是,A最左边一列是 1, B最右边一列是 1,然后行分奇偶全染成 1。

代码如下:

#include <bits/stdc++.h>
 
using namespace std;
char a[501][501]={0};
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            cin>>a[i][j];
        }
    }
 
    for(int i=1;i<=n;i++)
    {
        if(i&1)
        {
            cout<<'1';
            for(int j=2;j<m;j++)
            {
                cout<<a[i][j];
            }
            cout<<'0'<<endl;
        }
        else
        {
            for(int j=1;j<m;j++)
            {
                cout<<'1';
            }
 
            cout<<'0'<<endl;
        }
 
    }
    for(int i=1;i<=n;i++)
    {
        if(!(i&1))
        {
            cout<<'0';
            for(int j=2;j<m;j++)
            {
                cout<<a[i][j];
            }
 
            cout<<'1'<<endl;
        }
        else
        {
            cout<<'0';
            for(int j=2;j<=m;j++)
            {
                cout<<'1';
            }
            cout<<endl;
        }
 
    }
    return 0;
}

本次比赛总结

我的感觉就是太菜了,看着周围的同学都过了一道又一道题目,而我在比赛中只能干着急,有点难受。相关的知识点还是掌握的不够牢靠,之后还是需要多多练习,很多类型题还是需要多看。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值