训练赛1(第十一届山东省大学生程序设计竞赛复现)

训练赛1(第十一届山东省大学生程序设计竞赛复现)

导语

第一次训练赛,根据队内安排,选择值得参考的题目进行整理

涉及的知识点

整数除法、01背包、思维、数据量&最小生成树、树、矩阵

题目:签到题G,简单题DH,中等题BCM

链接:复现

题目

G

题目大意:给出n个整数和一个整数k,计算这n个整数的平均值,保留k位

思路:整数除法,注意取余

代码

#include <bits/stdc++.h>
using namespace std;
int N,K,T,sum,le;
int main() {
    string s;
    scanf("%d%d",&N,&K);
    for(int i=0; i<N; i++) {
        int t;
        scanf("%d",&t);
        sum+=t;
    }
    T=sum/N;//获取整数
    le=sum%N;//获取余数,用来获得小数部分
    printf("%d.",T);
    while(K--)
    {
        le*=10;
        printf("%d",le/N);
        le=le%N;
    }
    return 0;
}

D

题目大意:二维空间里放了n个盒子,有水平往左和竖直往下两种重力,求每次重力作用之后形成的轮廓周长

思路:模拟过程即可,每次放一个判断是否添加或减少边数

代码

#include <bits/stdc++.h>
using namespace std;
int n,numx[200001],numy[200001],ansx,ansy;
int main() {
    scanf("%d",&n);
    while(n--) {
        int x,y;
        scanf("%d%d",&x,&y);
        if(numx[x-1]<=numx[x])//当前列大于等于左边列
            ansx++;//左边可加一
        else
            ansx--;//重合,左边减一
        if(numx[x+1]<=numx[x])//当前列大于等于右边列
            ansx++;//右边可加一
        else
            ansx--;//重合,右边减一
        if(numx[x]==0)//底部无方块,下部加一
            ansx++;
        else
            ansx--;//重合,下部减一
        ansx++,numx[x]++;//上部加一,该列高度加一
        if(numy[y-1]<=numy[y])
            ansy++;
        else
            ansy--;
        if(numy[y+1]<=numy[y])
            ansy++;
        else
            ansy--;
        if(numy[y]==0)
            ansy++;
        else
            ansy--;
        ansy++,numy[y]++;
        printf("%d %d\n",ansx,ansy);
    }
    return 0;
}

H

题目大意:每个怪会扣指定的血量和防御,并有各自价值,给出初始防御与血量,防御为0时可以用血量来抵,血量不能为0,求获得的最大价值

思路:有约束的01背包,将条件分开来看即可,最后输出血量为1时的最优解

代码

#include <bits/stdc++.h>
using namespace std;
long long n,H,S,h[1212],s[1212],w[1212],dp[301][301];
int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    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][k]=max(dp[j][k],dp[j+k-h[i]-s[i]][0]+w[i]);
    cout <<dp[H-1][S];
    return 0;
}

B

题目大意:给定一个n个点的无向完全图,每个点有自己对应的权重a[t],i和j之间的边的权重是gcd(a[i],a[j]),保证a[t]随机,给出a[t]产生的范围,求最小生成树

思路:当n很大的时候(且数值取值范围不为单值),存在一个质数或者一个与其他数都互质的质数,所以答案为n-1(即1*(n-1)),当n很小的时候,当做最小生成树的模板题即可,当取值范围为单值,只需要取n-1个单值即可,开long long
粗略证明:当n很大的时候,如果R-L很小,说明数据密集,n≥R-L,数基本上都是连续+重复的,存在一个跟其他数都互质的数,R-L很大的时候,数据很分散,也存在这样一个数,int范围内两个素数间最小间距不到几百

通过模拟,在题目给定数据范围内未能推翻结论

代码

#include <bits/stdc++.h>
using namespace std;
unsigned long long n,L,R,a[200001],fa[12121];
unsigned long long seed,ans;
unsigned long long xorshift64() {
    unsigned long long 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) {
    int res=0;
    while(b) {
        res=b;
        b=a%b;
        a=res;
    }
    return a;
}
typedef struct node {
    int x,y,value;
    node(int _x,int _y,int _v) {
        x=_x;
        y=_y;
        value=_v;
    }
    bool operator<(node a)const {
        return value>a.value;
    }
} node;
int Seek(int x) {//路径压缩
    if(x==fa[x])
        return x;
    return fa[x]=Seek(fa[x]);
}
bool Union(int x,int y) {//合并
    int _x=Seek(x),_y=Seek(y);
    if(_x==_y)
        return 0;
    fa[_x]=fa[_y];
    return 1;
}
priority_queue<node>Q;
int main() {
    scanf("%llu%llu%llu%llu",&n,&L,&R,&seed);//L~R是所获得的数的范围
    for(int i=1; i<=n; i++)
        a[i]=gen();
    if(R==L) {//n个数都相同
        printf("%llu",L*(n-1));
        return 0;
    }
    else if(n>5)//n较大,可以试出来边界值
    {
        printf("%llu",n-1);
        return 0;
    }
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=n; j++)
            if(i!=j)
                Q.push(node(i,j,gcd(a[i],a[j])));
        fa[i]=i;
    }//录入边
    while(!Q.empty()) {//构造最小生成树
        node t=Q.top();
        Q.pop();
        if(!Union(t.x,t.y))
            continue;
        ans+=t.value;
    }
    printf("%llu",ans);
    return 0;
}

C

题目大意:构造一棵树,这棵树满足这样的条件:如果一个点为黑色,其子树都为黑色,如果为白色,则不为然,给出一个数K( 2 ≤ 2\le 2K ≤ 2 × 1 0 8 \le2×10^8 2×108),求一棵能够满足有K个染色方案的树,并将其存在的边输出

思路:设函数 f ( x ) f(x) f(x)表示子树黑白染色的方案数(编号为x的点为白色,x为黑色的时候方案数为1),可以得到转移方程: f ( x ) = 1 + ∏ ( f ( y ) + 1 ) f(x)=1+\prod(f(y)+1) f(x)=1+(f(y)+1)
对于这个方程,x和y都为白,第一个1代表所有子树(即y)都为黑的方案,第二个1代表所有y各自的子树都为黑的方案。累乘所有子树的构造方案。
选择策略:由上到下构造这棵树,对于剩下的方案数K,如果K为偶数,在这一层添加一个与父节点相连接的点,可以很容易知道,此时染色方案乘2,等价于K除以2,当K为奇数,下接一层,此时K-1(下接一层,即向下加一个点,这个点之后增加一个染色为白的方案,不可能能加一个染色为黑,因为当该点为黑时,其父节点已经先一步染成黑了),具体构造如图:
在这里插入图片描述

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    long long int n;
    vector<long long int>v1;
    vector<long long int>v2;
    scanf("%lld",&n);
    if(n==2)//特判
        printf("%d",1);
    else {
        n--;
        long long int count1 = 1;//当前层所有点的父节点
        long long int count2 = 2;//当前层的第一个待连接的点,也是当前已经用过的点的数目+1
        while(n) {
            v1.push_back(count1);
            v2.push_back(count2);//将解存入
            if(n==2) {
                printf("%lld\n",count2);//当剩余的构造量为2时
                //只需要再连接一个点就能构造完成,此时的count2便是所用的点的数目
                break;
            }
            if(n%2==0) {//本层加一个点
                count2++;
                n>>=1;
            } else {
                count1=count2++;//下接一层
                n--;
            }
        }
        int len=v1.size();
        for(int i=0; i<len; i++)
            printf("%lld %lld\n",v1[i],v2[i]);
    }
    return 0;
}

递归解法

#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=1e5+100;
int n,line,ver=1,tot;
pair<int,int> st[maxn];
void create(int x,int par) {
    if(x==2)
        return;
    if(x==3) {
        st[tot++]= {par,++ver};
        create(x-1,ver);
    } else if(x&1) {
        st[tot++]= {par,++ver};
        st[tot++]= {par,++ver};
        create((x-1)/2,ver);
    } else {
        st[tot++]= {par,++ver};
        create(x-1,ver);
    }
}
signed main() {
    scanf("%lld",&n);
    create(n,1);
    printf("%lld\n",ver);
    for(int i=0; i<tot; i++)
        printf("%lld %lld\n",st[i].first,st[i].second);
}

M

题目大意:给出一个只有0/1的矩阵C,将这个矩阵经过变化得到两个矩阵A、B,要求如果A、B的大小规模与C相同,C对应位置为1,则A、B对应位置也都为1,C对应位置为0,则A、B对应位置的或为1,保证C的边缘都为0

思路:一种简单的策略,A的最左都为1,最右都为0,B的最右都为1,最左都为1,之后按照奇偶相间将A、B的对应行设置为0,1,如A的奇数行(除最右)都为1,偶数行(除最左)都为0,B则相反

代码

#include <bits/stdc++.h>
using namespace std;
int n,m,data[501][501];
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++)
            scanf("%1d",&data[i][j]);
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++)
            if(j==1)
                printf("1");
            else if(j==m)
                printf("0");
            else if(i%2==1)
                printf("1");
            else
                data[i][j]==1?printf("1"):printf("0");
        printf("\n");
    }
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++)
            if(j==1)
                printf("0");
            else if(j==m)
                printf("1");
            else if(i%2==0)
                printf("1");
            else
                data[i][j]==1?printf("1"):printf("0");
        printf("\n");
    }
    return 0;
}

总结

本周的测试,列出了6个需要整理的题目,细细看来,其实还是有很多题都是能做的,M题和B题都可以争取一下,也需要注意更正忘记开long long 的缺点,今后需要更多的练习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值