第十一届山东省大学生程序设计竞赛部分题解

第十一届山东省大学生程序设计竞赛部分题解

B

思路

如果给出的点权里面有素数,由于素数仅能被1和其自身整除,因此所有和点权为素数的点相连的所有边的边权大小均为1,所以最小生成树的权值为n-1。当点的数量特别多的时候,可以认为点集里一定存在素数;当点的数量少的时候直接使用暴力算法即可。

代码
#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;
}

const int maxn = 2e5+5;

struct Edge {
    int u, v, w;
} edges[maxn*10];
int tot = -1;

int fa[maxn];
int find(int i) {
    if(fa[i] != i) {
        fa[i] = find(fa[i]);
    }
    return fa[i];
}
void merge(int i, int j) {
    int f1 = find(i);
    int f2 = find(j);
    fa[f1] = f2;
}
void init(int n) {
    for(int i = 0; i <= n+4; ++i) {
        fa[i] = i;
    }
}
void add(int u, int v, int w) {
    ++tot;
    edges[tot].u = u;
    edges[tot].v = v;
    edges[tot].w = w;
}
bool cmp(Edge& a, Edge& b) {
    return a.w < b.w;
}
int Kruskral(int n) {
    init(n);
    sort(edges, edges+tot+1, cmp);
    int p, q, ans = 0, cnt = 0;
    for(int i = 0; i <= tot; ++i) {
        p = find(edges[i].u);
        q = find(edges[i].v);
        if(q != p) {
            ans += edges[i].w;
            fa[p] = q;
            ++cnt;
        }
        if(cnt == n-1) return ans;
    }
    return ans;
}

int main(void)
{
    scanf("%d%d%d%llu", &n, &L, &R, &seed);
    for(int i = 1; i <= n; ++i) {
        a[i] = gen();
    }
    if(L == R) printf("%lld", (n-1)*1ll*L); // L=R的情况需要特判
    else if (n >= 20) printf("%lld", 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]));
            }
        }
        printf("%lld", Kruskral(n));
    }
    return 0;
}

C

思路

当只有一个点时,染色的可能数有两个。如果有多个点,根节点有左子树也有右子树,这这个树的染色的可能数为:左子树的可能数*右子树的可能数 + 1;如果根节点只有一个子树,则这个数的染色数为:子树的染色数+1 。这样就可以看到明显的递归结构。

代码
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;

const ll maxn = 1e5+5;
PLL tree[maxn];
ll tot = -1;
ll cur = 1;

void build(ll x, ll fa) {  // 建造染色数为x,根节点为fa的一个数
    if(x == 2) return;
    else if(x == 3) {
        tree[++tot] = {fa, ++cur};
        build(x-1, cur);
    } else if(x&1) {
        tree[++tot] = {fa, ++cur};
        build(2, cur);
        tree[++tot] = {fa, ++cur};
        build((x-1)/2, cur);
    } else {
        tree[++tot] = {fa, ++cur};
        build(x-1, cur);
    }
}
int main(void)
{
    ll n;
    scanf("%lld", &n);
    build(n, 1);
    printf("%lld\n", cur);
    for(int i = 0; i <= tot; ++i) {
        printf("%lld %lld\n", tree[i].first, tree[i].second);
    }
    return 0;
}

D

思路

先拿在竖直方向施加重力为例:每次记录新添加的块放在哪一行,如果新添加的块是这一列的第一行,则先向边数里面加4,如果不是新添加的块,则先向边数里面加2;再看新添加的块是否和左右的列有相邻的块,每有一个相邻的块就在总边数里面减2。

代码
#include <bits/stdc++.h>

using namespace std;

const int maxn = 2e5+5;
typedef long long ll;

int n, v[maxn], h[maxn];
int main(void)
{
    int x, y;
    memset(v, 0, sizeof(v));
    memset(h, 0, sizeof(h));
    scanf("%d", &n);
    ll ansv = 0, ansh = 0;
    for(int i = 0; i < n; ++i) {
        scanf("%d%d", &x, &y);
        ++v[x];
        ++h[y];
        if(v[x] == 1) {
            ansv += 4;
        } else {
            ansv += 2;
        }
        if(v[x] <= v[x-1]) {
            ansv -= 2;
        }
        if(v[x] <= v[x+1]) {
            ansv -= 2;
        }

        if(h[y] == 1) {
            ansh += 4;
        } else {
            ansh += 2;
        }
        if(h[y] <= h[y-1]) {
            ansh -= 2;
        }
        if(h[y] <= h[y+1]) {
            ansh -= 2;
        }
        printf("%lld %lld\n", ansv, ansh);
    }
    return 0;
}

G

思路

模拟除法。

代码
#include <bits/stdc++.h>

using namespace std;

int main(void)
{
    int n, k;
    scanf("%d%d", &n, &k);
    int sum = 0, x;
    for(int i = 0; i < n; ++i) {
        scanf("%d", &x);
        sum += x;
    }
    int left = sum;
    for(int i = 0; i < k+1; ++i) {
        if(i == 1) printf(".");
        if(left) {
            printf("%d", left/n);
            left %= n;
            left *= 10;
        } else {
            printf("0");
        }
    }
    return 0;
}

H

思路

背包问题,多了一个维度。如果开三维数组会爆,所以应该采用优化方案。

代码
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

const int maxn = 1005;
ll dp[maxn][maxn];
ll h[maxn], s[maxn], w[maxn];

int main(void)
{
    int n, H, S;
    memset(dp, 0, sizeof(dp));

    scanf("%d%d%d", &n, &H, &S);
    for(int i = 1; i <= n; ++i) {
        scanf("%lld%lld%lld", &h[i], &s[i], &w[i]);
    }
    for(int i = 1; i <= n; ++i) {
        for(int j = H; j > h[i]; --j) {
            for(int k = S; k >= 0; --k) {
                if(k-s[i]<0) {
                    if(j-h[i]-(s[i]-k) <= 0) continue;
                    dp[j][k] = max(dp[j-h[i]-(s[i]-k)][0]+w[i], dp[j][k]);
                } else {
                    dp[j][k] = max(dp[j-h[i]][k-s[i]]+w[i], dp[j][k]);
                }
            }
        }
    }
    printf("%lld", dp[H][S]);
    return 0;
}

M

思路

照着题解看的,题解写的非常通俗易懂,可惜当时没看这个题😭。

代码
#include <cstdio>

using namespace std;
const int maxn = 505;
int arr[maxn][maxn];

int main(void)
{
   // freopen("in.txt", "r", stdin);
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 0 ;i < n; ++i) {
        for(int j = 0; j < m; ++j) {
            scanf("%1d", &arr[i][j]);
        }
    }
    for(int i = 0; i < n; ++i) {
        for(int j = 0; j < m; ++j) {
            if(j == 0) {
                printf("1");
            } else if(!(i&1) && j != m-1) {
                printf("1");
            } else {
                printf("%d", arr[i][j]);
            }
        }
        printf("\n");
    }

    for(int i = 0; i < n; ++i) {
        for(int j = 0; j < m; ++j) {
            if(j == m-1) {
                printf("1");
            } else if((i&1) && j != 0) {
                printf("1");
            } else {
                printf("%d", arr[i][j]);
            }
        }
        printf("\n");
    }
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值