第十一届山东省省赛题解BCDFGHJM

本文探讨了几个信息技术领域的复杂问题,包括通过计算GCD构建最小生成树(MST),设计染色树以最大化染色方案数,以及处理图形、树形结构和矩阵操作。通过算法优化与实例分析,展示了如何在不同场景下解决这些挑战。
摘要由CSDN通过智能技术生成

题目

B_Build Roads

题意:给定一个N个点的无向完全图, i i i j j j之前的边权是 g c d ( a i , a j ) gcd(a_i,a_j) gcd(ai,aj),保证数组值随机,求MST(最小生成树)。
数据范围: n , L , R , s e e d ( 2 ≤ n ≤ 2 × 1 0 5 , 1 ≤ L ≤ R ≤ 2 × 1 0 5 , 1 ≤ s e e d ≤ 1 0 18 ) n,L,R,seed (2 \le n \le 2 \times 10^5, 1 \le L \le R \le 2 \times 10^5, 1 \le seed \le 10^{18}) n,L,R,seed(2n2×105,1LR2×105,1seed1018)

分析

在具体的数据不需要去思考如何得到,把代码誊写一遍就行,反正数组中所有的数值都在L到R之间。那么根据LR的范围,可以知道,其中相邻两个质数的差值不会大于1000。而一旦有质数,那么任意数和质数gcd的结果都是1,进而得到答案就是n-1。但有 L = R L = R L=R时,gcd的值为L,即最后答案为 ( n − 1 ) × L (n-1)\times L (n1)×L。对于其他情况,则采用Kruskral或者Prim算法计算MST即可,且不会TLE。

代码

#include<bits/stdc++.h>
using namespace std;
int n,L,R,a[200001];
unsigned long long seed;
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;
}
struct P{
    int u,v,w;
}edge[2000010];//i,j,gcd(ai,aj)
int fa[1010],t = 0;
bool cmp(P a,P b){
    return a.w<b.w;
}
int gcd(int a,int b){
    if(b) return gcd(b,a%b);
    else return a;
}
int find(int x){
    if(fa[x]==x) return x;
    else return fa[x] = find(fa[x]);
}
void add(int u,int v,int w){
    edge[t].u = u;
    edge[t].v = v;
    edge[t].w = w;
    ++t;
}
int Kruskral(int n){
    for(int i = 1;i<=n;++i) fa[i] = i;
    sort(edge,edge+t,cmp);
    int p,q,ans=0,cnt = 0;
    for(int i = 0;i<t;++i){
        p = find(edge[i].u);
        q = find(edge[i].v);
        if(q!=p){
            ans+=edge[i].w;
            fa[p] = q;
            ++cnt;
        }
        if(cnt==n-1) return ans;
    }
    return ans;
}
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 << 1LL*(n-1)*L;
    else if(n>100) cout << n-1;
    else{
        for(int i = 1;i<=n;++i){
            for(int j = i+1;j<=n;++j){
                add(i,j,gcd(a[i],a[j]));//加边入集合
            }
        }
        cout << Kruskral(n);
    }
    return 0;
}

C_Cat Virus

题意:给定一棵树,黑白染色方案,满足一个黑点的子树都是黑点,白点任意。让构造出这样的一棵树,使得它的染色方案数为K。
数据范围: K ( 2 ≤ K ≤ 2 ⋅ 1 0 18 ) K(2 \le K \le 2 \cdot 10^{18}) K(2K21018)

分析

这题需要画图,将K=2到10的图全部画出来,然后可以找到规律,K为偶数时,给它加一个子节点,那么还需要拼凑的K值减1。K为计数时,加上两个子节点,剩余K值是原本的一半(向下取整)。但是这里的规律要除去K=3这种情况。因为当K=3时,只要加一个节点就行。
综上,采取了不断简化K值得方法,不断加深树的深度,并且不断将树的子节点看做根节点向下构造。(脑子已经不够用了)并且整个过程需要模拟两遍,第一遍计算出n的大小,第二遍得出构造。

代码

#include<iostream>
using namespace std;
typedef long long ll;
int main(){
    ll k,n = 1;
    cin >> k;
    ll s = k;
    while(k>3){
        if(k&1){
            n+=2;//节点总数
            k/=2;
        }
        else {
            ++n;
            --k;
        }
    }
    if(k==3) ++n;
    cout << n<<"\n";
    int r = 1,t = 1;
    while(s>3){//构造树
        if(s&1){
            ++t;
            cout << r <<" "<<t<<"\n";
            ++t;
            cout << r << " "<<t<<"\n";
            r = t;//随便一个子节点做当前根节点
            s/=2;
        }
        else{
            ++t;
            cout <<r<<" "<<t<<"\n";
            r = t;
            --s;
        }
    }
    if(s==3) cout << r<<" "<<++t<<"\n";
    return 0;
}

D_Dyson Box

题意:向二维空间里放n个盒子,给出每个盒子的坐标,有水平往左和竖直往下两种重力,求重力作用之后形成的轮廓周长。
数据范围: n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) ; x i , y i ( 1 ≤ x i , y i ≤ 2 ⋅ 1 0 5 ) . n (1 \le n \le 2 \cdot 10^5);x_i, y_i(1 \le x_i, y_i \le 2 \cdot 10^5). n(1n2105);xi,yi(1xi,yi2105).

分析

稍加思考可以发现,不管是在哪里,在按照某一向重力移动后,只受其x或者y轴坐标上相邻两边的大小影响,当邻边有同样高度的盒子时,增加的边数减一。

代码

#include<bits/stdc++.h>
using namespace std;
int h[200001],s[200001];
int main()
{
    long long ans1=0,ans2=0;
    int n;
    cin>>n;
    int x,y;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&x,&y);
        if(h[x]==0)
        {
            ans1+=2;
            
        }
        if(h[x]>=h[x-1])
                ans1++;
        else
            ans1--;
        if(h[x]>=h[x+1])
                ans1++;
        else
            ans1--;
        h[x]++;
        if(s[y]==0)
        {
            ans2+=2;
            
        }
        if(s[y]>=s[y-1])
                ans2++;
        else
            ans2--;
        if(s[y]>=s[y+1])
            ans2++;
        else
            ans2--;
        s[y]++;
        printf("%lld %lld\n",ans1,ans2);
    }
    return 0;
}

F_Birthday Cake

题意:给定n个串,求有多少对串能拼出平方串(能够表示成两个相同的字符串连接在一起的,即AA)。
数据范围: n ( 1 ≤ n ≤ 4 ⋅ 1 0 5 ) ; s i ( 1 ≤ ∣ s i ∣ ≤ 4 ⋅ 1 0 5 ) n (1 \le n \le 4 \cdot 10^5) ;s_i(1 \le |s_i| \le 4 \cdot 10^5) n(1n4105);si(1si4105)

分析

题目的思路其实很明了,两个字符串相同可以连接,假如一个字符串的前缀等于后缀,和其中间剩余部分相同的字符串也可以连接。那么这里可以采用哈希的办法,计算出所有符合条件的字符串,然后匹配相加即可。但是这里的数据中字符串数目过多,单个哈希会发生自然溢出,即哈希的键值不够的情况。要尽可能减小冲突,要使用双哈希的办法来扩大范围。
对字符串哈希,还要知道其子串的哈希值,使用P进制的方法,采用了公式:
h = h a s h [ i ] − h a s h [ j − 1 ] × p [ r − l + 1 ] h = hash[i] - hash[j-1]\times p[r-l+1] h=hash[i]hash[j1]×p[rl+1]计算出字符串中 i i i j j j子串的哈希值。hash数组表示前 i i i个字符组成的字符串哈希值,p数组表示,有 i i i个长度的p进制。整体思想就是将字符串转化为一个P进制的数
acwing841的字符串哈希看了之后这个办法就很好理解。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char str[400010];
map<pair<ll,ll>,int> mp1,mp2;//mp1整体字符串,mp2内部字符串
pair<ll,ll> t;
ll h1[400010],h2[400010],p1[400010],p2[400010];
int mod1 = 1e9+7,mod2 = 998244353,pm = 107;//mod1和mod2不同的质数
pair<ll,ll> get(int l,int r){
    ll a = (h1[r]-h1[l-1]*p1[r-l+1]%mod1+mod1)%mod1;//计算l到r的哈希值
    ll b = (h2[r]-h2[l-1]*p2[r-l+1]%mod2+mod2)%mod2;
    return make_pair(a, b);//双哈希值作为一个key扩大哈希范围
}
int main(){
    ll n,ans = 0;
    int len;
    cin >> n;
    p1[0] = p2[0] = 1;
    for(int i = 1;i<=400001;++i){
        p1[i] = p1[i-1]*pm%mod1;//P进制,p^0,p^1,p^2...
        p2[i] = p2[i-1]*pm%mod2;
    }
    while(n--){
        cin >> str;
        len = strlen(str);
        for(int i = 1;i<=len;++i){
            h1[i] = (h1[i-1]*pm%mod1+str[i-1])%mod1;//hash值
            h2[i] = (h2[i-1]*pm%mod2+str[i-1])%mod2;
        }
        t = make_pair(h1[len],h2[len]);
        ans+=mp1[t];//整体
        mp1[t]++;
        ans+=mp2[t];//内部
        for(int i = 1;i*2<len;++i){
            if(get(1,i)==get(len-i+1,len)){//前后缀相同
                t = get(i+1,len-i);//内部字符串
                ans+=mp1[t];
                mp2[t]++;
            }
        }
    }
    cout << ans;
    return 0;
}

G_Grade Point Average

题意:计算n个数的平均值,精确到小数点后k位。
数据范围: n , k ( 1 ≤ n , k ≤ 1 0 5 ) ; a i ( 0 ≤ a i ≤ 100 ) n,k (1 \le n, k \le 10^5);a_i(0\le a_i\le100) n,k(1n,k105);ai(0ai100)

分析

直接逐位除法,要多少位有多少位。。。不过先计算出小数点前的数。

代码

#include<iostream>
using namespace std;

int main()
{
    int n,dec,tt;
    int zong=0;
    cin>>n>>dec;
    for(int i=0;i<n;i++){
        cin>>tt;
        zong+=tt;
    }
    int m = zong/n;
    cout <<m<<".";
    zong-=m*n;
    for(int i = 0;i<dec;++i){
        zong*=10;
        cout << zong/n;
        zong%=n;
    }
    return 0;
}

H_Adventurer’s Guild

题意:Yuna 的生命值是H,体力值是S,有n个任务,每个任务有生命值 h i h_i hi和体力值 s i s_i si的花费。生命值不能降到0或0以下,体力值的多余消耗会算到生命值里。求最大任务收益w。
数据范围:
n , H , S ( 1 ≤ n ≤ 1000 , 1 ≤ H ≤ 300 , 0 ≤ S ≤ 300 ) n,H,S (1 \le n \le 1000, 1 \le H \le 300, 0 \le S \le 300) n,H,S(1n1000,1H300,0S300).
h i , s i , w i ( 0 ≤ h i , s i ≤ 300 , 1 ≤ w i ≤ 1 0 9 ) h_i, s_i, w_i(0 \le h_i, s_i \le 300, 1 \le w_i \le 10^9) hi,si,wi(0hi,si300,1wi109)

分析

比较经典的背包问题了,直接对生命值和体力值二维dp。

代码

#include<iostream>
using namespace std;
typedef long long ll;
int h,s,w;
ll dp[310][310];//S:H
int main(){
    int n,H,S;
    cin >> n >> H >> S;
    for(int i = 1;i<=n;++i){
        cin >> h >> s >> w;
        for(int j = S;j>=0;--j){
            for(int k = H;k>0;--k){
                if(j>=s&&k>h){//体力和生命大于任务所需
                    dp[j][k] = max(dp[j-s][k-h]+w,dp[j][k]);
                }
                else if(j<s&&k+j>h+s){//体力不足,总体大于所需
                    dp[j][k] = max(dp[0][j+k-h-s]+w,dp[j][k]);
                }
            }
        }
    }
    ll ans = 0;
    for(int i = 0;i<=S;++i){
        for(int j = 1;j<=H;++j){
            ans = max(ans,dp[i][j]);
        }
    }
    cout << ans;
    return 0;
}

J_Tuition Agent

题意:有n个人,对每个人可以花费x价值使其成为老师,也可以花费y价值使其成为学生。一个老师可以和一个学生配对,老师的R值小于学生R值,然后获得K的价值。计算最大获取价值。(结果可以为负)
数据范围:
n , k ( 2 ≤ n ≤ 100000 , 0 ≤ k ≤ 10000 ) n,k(2 \le n \le 100000, 0 \le k \le 10000) n,k(2n100000,0k10000)
R i , X i , Y i ( 1 ≤ R i ≤ n , 0 ≤ X i , Y i ≤ 10000 ) R_i,X_i,Y_i(1 \le R_i \le n, 0 \le X_i, Y_i \le 10000) Ri,Xi,Yi(1Rin,0Xi,Yi10000)

分析

本题采用dp的解法,设立dp[i][j].表示前i个人里,有j个人做为老师。当每加入一个学生,则j-1。考虑到只有R值较小的才能教导R值较大的人,那么需要先对所有人按照R值排序,排前面的一定可以教导后面的人。接下来只需要对每一个人寻找最大价值即可,并且数据保证随机,那么基本不可能出现前面有500以上的教师情况。

代码

#include<bits/stdc++.h>
using namespace std;
struct node{
    int r,x,y;
}a[100010];
long long dp[3][510];
bool cmp(node A,node B)
{
    return A.r<B.r;
}
int main()
{
    int t;
    cin >> t;
    long long n,k,ans=0;
    while(t--)
    {
        scanf("%lld%lld",&n,&k);
        ans = -0x3f3f3f3f;
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d%d",&a[i].r,&a[i].x,&a[i].y);
        }
        sort(a+1,a+n+1,cmp);
        for(int i = 0;i<=500;++i) dp[0][i] = -0x3f3f3f3f;
        dp[0][0] = 0;
        //n值过大,对dp优化,dp[0]为上一次状态,dp[1]为当前状态
        for(int i = 1;i<=n;++i){
            for(int j = 0;j<=500;++j) dp[1][j] = -0x3f3f3f3f;
            for(int j = 0;j<=500;++j){
                //tuition +1 
                if(j<500) dp[1][j+1] = max(dp[1][j+1],dp[0][j]-a[i].x);
                //student +1
                if(j) dp[1][j-1] = max(dp[1][j-1],dp[0][j]-a[i].y+k);
                else dp[1][j] = max(dp[1][j],dp[0][j]-a[i].y);
            }
            for(int j = 0;j<=500;++j) dp[0][j] = dp[1][j];
        }
        for(int i = 0;i<=500;++i) ans = max(ans,dp[0][i]);
        cout << ans<<"\n";
    }
    return 0;
}

M_Matrix Problem

题意:给定0/1矩阵C,构造两个矩阵A,B,其中1 形成了完整的不分散的一块四连通块,并且对于C中所有位置,若是1,则A,B对应位置必须都是1 ,否则A,B之中必须有一个这个位置为0。
保证C阵的边框都是0。
数据范围: n , m ( 3 ≤ n , m ≤ 500 ) n,m (3 \le n, m \le 500) n,m(3n,m500)

分析

构造矩阵AB,因为是上下左右四个方向有1即为相连,因此AB可以分奇偶行涂色,这样不论C中的1在哪里,一定和AB是连通的,同时,C的外围一定是0,那么只需要将左右两侧的列,AB各涂掉一列所有即可。

代码

#include<iostream>
using namespace std;
int a[510][510],b[510][510],c[510][510];
int main(){
    int n,m;
    char ch;
    cin >> n >> m;
    for(int i = 1;i<=n;++i){
        for(int j = 1;j<=m;++j){
            cin >> ch;
            if(ch=='1') a[i][j] = b[i][j] = c[i][j] = 1;
        }
    }
    for(int i = 1;i<=n;i+=2){
        for(int j = 1;j<=m;++j){
            a[i][j] = 1;
        }
    }
    for(int i = 2;i<=n;i+=2){
        for(int j = 1;j<=m;++j){
            b[i][j] = 1;
        }
    }
    for(int i = 1;i<=n;++i){
        a[i][1] = b[i][m] = 1;
        a[i][m] = b[i][1] = 0;
    }
    for(int i = 1;i<=n;++i){
        for(int j = 1;j<=m;++j){
            cout <<a[i][j];
        }
        cout <<"\n";
    }
    for(int i = 1;i<=n;++i){
        for(int j = 1;j<=m;++j){
            cout <<b[i][j];
        }
        cout <<"\n";
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

registor11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值