2020中国石油大学ACM俱乐部开放训练赛

首先,感谢中国石油大学的出题组,题的质量很高,很适合刚入门但有一定提高的ACMer。传送门

本题解顺序由易到难(个人主观因素),共12道题,但是由于本蒟蒻水平优先,暂时做了7道。

问题 D: 大数

题解:寻找最小循环子序列,利用kmp中的next数组进行计算。
l e n len m o d mod ( l e n n e x t [ l e n ] ) = 0 (len-next[len])=0 则证明他是存在最小循环子序列。
本题有一个坑点,就是这些数都是由比他小的某个数重复构成,所以要特判。
AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
const int maxn = 1000005;
int net[maxn];
int main()
{
    //freopen("in.in","r",stdin);
    //freopen("out.out","w",stdout);
    ios::sync_with_stdio(false);
    string a;
    cin >> a;
    int len = a.size();
    int i = 0, j = -1;
    net[0] = -1;
    while (i < len)
    {
        if (j == -1 || a[i] == a[j])
        {
            i++;
            j++;
            net[i] = j;
        }
        else
            j = net[j];
    }
    if (len % (len - net[len]) == 0)
    {
        int tmp = len - net[len];
        if (net[len])
            for (int i = 0; i < tmp; i++)
                cout << a[i];
        else
            cout << -1;
        cout << endl;
    }
    else
        cout << -1 << endl;
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}

问题 B: 奎奎发红包

题解:贪心算法。类似于排队接水问题,但又不太一样。
我们假设a排在b前面: v a t a + v b ( t a + t b ) < v b t b + v a ( t a + t b ) va*ta+vb(ta+tb)<vb*tb+va(ta+tb)
化简得: v b t a < v a t b vb*ta<va*tb ,我们就构造: t / v t/v ,越小则说明优先度越高。
坑点:当 v = 0 v=0 时要进行特判。
AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
const int maxn = 100005;
ll ans = 0;
int n;
struct node
{
    int v, t;
    double value; //t/v
} a[maxn];
bool cmp(node x, node y)
{
    return x.value < y.value;
}
int main()
{

    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i].v >> a[i].t;
        if (a[i].v) //特判
            a[i].value = (a[i].t * 1.0) / a[i].v;
        else //当v==0时,value趋近正无穷
            a[i].value = 0x3f3f3f;
    }
    sort(a + 1, a + 1 + n, cmp); //结构体排序
    ll sum_t = 0;
    for (int i = 1; i <= n; i++)
    {
        sum_t += a[i].t;
        ans += (ll)a[i].v * sum_t;
    }
    cout << ans << endl;
    return 0;
}

问题 I: 星区划分

题解:并查集+遍历搜索。写两个for循环,遍历点之间的关系,然后进行7个判断,若符合则进行并查集操作。
AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
int n, m, ans;
int s[1005]; //并查集
bool flag[10];
struct star
{
    int x, y, z, v;
} a[1005];
int find_set(int x) //找到父结点,压缩状态
{
    if (x != s[x])
        s[x] = find_set(s[x]);
    return s[x];
}

void union_set(int x, int y) //合并
{
    x = find_set(x);
    y = find_set(y);
    if (x != y)
        s[x] = s[y];
}
void solve(int i, int j)
{
    if (a[i].x == a[j].x && a[i].y == a[j].y && abs(a[i].z - a[j].z) == 1) //上下
    {
        if (abs(a[i].v - a[j].v) <= m)
            union_set(i, j);
    }
    if (abs(a[i].x - a[j].x) == 1 && a[i].y == a[j].y && a[i].z == a[j].z) //左右
    {
        if (abs(a[i].v - a[j].v) <= m)
            union_set(i, j);
    }
    if (a[i].x == a[j].x && abs(a[i].y - a[j].y) == 1 && a[i].z == a[j].z) //前后
    {
        if (abs(a[i].v - a[j].v) <= m)
            union_set(i, j);
    }
    if (a[i].x == a[j].x && a[i].y == a[j].y && a[i].z == a[j].z) //本身
    {
        if (abs(a[i].v - a[j].v) <= m)
            union_set(i, j);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i].x >> a[i].y >> a[i].z >> a[i].v;
        s[i] = i; //并查集初始化
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
        {
            if (i == j)
                continue;
            solve(i, j);
        }
    for (int i = 1; i <= n; i++)
        if (s[i] == i)
            ++ans;
    cout << ans << endl;
    return 0;
}

问题 A: sciorz画画

题解:区间dp, d p [ i ] [ j ] dp[i][j] 代表从 i i j j n n 边形,而若是要构成 n n 边形,则需要 j > = i + 2 j>=i+2 。利用记忆优化+深搜的思想进行函数的构建。
当我们确定 i i j j 后,我们设 k k ,使得 i < k < j i<k<j ,代表的含义是在 k k 点进行切割,一分为二。这就推出了状态转移方程: d p [ i ] [ j ] = m a x ( d p [ i ] [ k ] + d p [ k ] [ j ] + j s ( i , j , k ) , d p [ i ] [ j ] ) dp[i][j] = max(dp[i][k] + dp[k][j] + js(i, j, k), dp[i][j])
AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
int a[105];
ll dp[105][105];
ll js(int i, int j, int k)
{
    return a[i] * a[j] * a[k];
}
int main()
{
    int t, cnt = 0;
    cin >> t;
    while (t--)
    {
        memset(dp, 0, sizeof(dp));
        int n;
        cin >> n;
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        for (int i = n - 2; i >= 1; i--)
            for (int j = i + 2; j <= n; j++)
                for (int k = i + 1; k <= j - 1; k++)
                    dp[i][j] = max(dp[i][k] + dp[k][j] + js(i, j, k), dp[i][j]);
        printf("Case #%d: %lld\n", ++cnt, dp[1][n]);
    }
    return 0;
}

问题 K: 数学问题

题解:打表+二位前缀和记录答案。注意两个个坑点:①是: j m i n ( i , m ) j≤min(i,m)
②是:由于 j i j≤i 所以前缀和的 a n s [ i ] [ i + 1 ] = a n s [ i ] [ i ] ans[i][i + 1] = ans[i][i] 为下一次的前缀和提供 a n s [ i ] [ j 1 ] ans[i][j - 1] ,实在不理解可以画图进行模拟一下。
AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
const int maxn = 2002;
int t, g, n, m;
ll c[maxn][maxn];
ll ans[maxn][maxn];
void init()
{
    c[1][1] = 1;
    for (int i = 0; i <= 2000; i++)
        c[i][0] = 1;
    for (int i = 1; i <= 2000; i++)
        for (int j = 1; j <= i; j++)
            c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % g;
}
void get_ans()
{
    for (int i = 2; i <= 2000; i++) //g>1
    {
        for (int j = 1; j <= i; j++)
        {
            ans[i][j] = ans[i - 1][j] - ans[i - 1][j - 1] + ans[i][j - 1];
            if (c[i][j] == 0)
                ans[i][j]++;
        }
        ans[i][i + 1] = ans[i][i]; //坑点2
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> t >> g;
    init();
    get_ans();
    while (t--)
    {
        cin >> n >> m;
        m = min(n, m); //坑点1
        cout << ans[n][m] << endl;
    }
    return 0;
}

问题 F: 求和

题解:矩阵构造+矩阵快速幂取模。我记着这个矩阵的构造是线性代数课本上的原题。
我们可以构造矩阵:
B 1 = { A A 0 E } B^1= \left\{ \begin{matrix} A & A \\ 0 & E \\ \end{matrix} \right\}
则:
B m = { A m A + A 2 + . . . + A m 0 E } B^m= \left\{ \begin{matrix} A^m & A+A^2+...+A^m \\ 0 & E \\ \end{matrix} \right\}
剩下的就是矩阵快速幂取模的事了。
AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
const ll mod = 1000000007;
int n;
struct Matrix
{
    ll m[70][70];
    Matrix() //初始化
    {
        memset(m, 0, sizeof(m));
    }
};
Matrix Multi(Matrix a, Matrix b) //矩阵乘法
{
    Matrix res;
    for (int i = 1; i <= 2 * n; i++)
        for (int j = 1; j <= 2 * n; j++)
            for (int k = 1; k <= 2 * n; k++)
                res.m[i][j] = (res.m[i][j] + a.m[i][k] * b.m[k][j]) % mod;
    return res;
}
Matrix fastm(Matrix a, int m) //矩阵快速幂
{
    Matrix res;
    for (int i = 1; i <= 2 * n; i++) //相当于普通快速幂中的res=1
        res.m[i][i] = 1;
    while (m)
    {
        if (m & 1)
            res = Multi(res, a);
        a = Multi(a, a);
        m >>= 1;
    }
    return res;
}
int main()
{
    ios::sync_with_stdio(false);
    int m;
    Matrix num;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) //构造矩阵B
        for (int j = 1; j <= n; j++)
        {
            cin >> num.m[i][j];
            num.m[i][j + n] = num.m[i][j];
        }
    for (int i = n + 1; i <= 2 * n; i++) //构造矩阵B
        num.m[i][i] = 1;
    Matrix ans = fastm(num, m);
    for (int i = 1; i <= n; i++)
        for (int j = n + 1; j <= 2 * n; j++)
        {
            cout << ans.m[i][j];
            if (j != 2 * n)
                cout << " ";
            else
                cout << endl;
        }
    return 0;
}

问题 C: 关于我转生变成史莱姆这档事

题解: 剪枝+bfs。
第一天取 a a
n n 天取 s = a + a n 1 + a n 1 n 2 + . . . . . + a n 1 n 2 n n s=a+a*n1+a*n1*n2+.....+a*n1*n2*nn
我们可以进行提取公因式处理:
如: s = a ( 1 + n 1 + n 1 n 2 + . . . . . + n 1 n 2 n n ) s=a*(1+n1+n1*n2+.....+n1*n2*nn)
化简: ( s / a 1 ) = 1 + n 1 + n 1 n 2 + . . . . . + n 1 n 2 n n (s/a-1)=1+n1+n1*n2+.....+n1*n2*nn
一步步进行,直到左式为0。
而我们的剪枝就是从 s s 的因子进行处理,上述式子可以看成因子的乘积,这样可以减少搜索量。
AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
vector<int> factor;
bool flag;
int ans = 0x3f3f3f;
void get_factor(int x) //提取因子
{
    for (int i = 1; i * i <= x; i++)
    {
        if (x % i == 0)
            factor.push_back(i);
    }
}
struct node
{
    int num;
    int step;
};
void bfs(int x)
{
    queue<node> q;
    node now, next;
    q.push({x, 1});
    while (!q.empty())
    {
        now = q.front();
        q.pop();
        if (now.num == 0 && now.step != 1)
        {
            flag = 1;
            ans = min(ans, now.step);
            break;
        }
        if (now.step >= ans) //剪枝
            break;
        for (int i = 2; i <= 9; i++)
        {
            if (now.num % i == 0)
            {
                next.num = now.num / i - 1;
                next.step = now.step + 1;
                q.push(next);
            }
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    int x;
    cin >> x;
    get_factor(x);                          //提取因子
    for (int i = 0; i < factor.size(); i++) //从x的因子出发
    {
        int tmp = x / factor[i] - 1;
        bfs(tmp);
        tmp = x / (x / factor[i]) - 1;
        bfs(tmp);
    }
    if (!flag)
        cout << -1 << endl;
    else
        cout << ans << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/acm_durante/article/details/104806439
今日推荐