2020中國石油大學ACM俱樂部開放訓練賽

首先,感謝中國石油大學的出題組,題的質量很高,很適合剛入門但有一定提高的ACMer。傳送門

本題解順序由易到難(個人主觀因素),共12道題,但是由於本蒟蒻水平優先,暫時做了7道。

問題 D: 大數

題解:尋找最小循環子序列,利用kmp中的next數組進行計算。
lenlen modmod (lennext[len])=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前面:vata+vb(ta+tb)<vbtb+va(ta+tb)va*ta+vb(ta+tb)<vb*tb+va(ta+tb)
化簡得:vbta<vatbvb*ta<va*tb,我們就構造:t/vt/v,越小則說明優先度越高。
坑點:當v=0v=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,dp[i][j]dp[i][j]代表從iijjnn邊形,而若是要構成nn邊形,則需要j>=i+2j>=i+2。利用記憶優化+深搜的思想進行函數的構建。
當我們確定iijj後,我們設kk,使得i<k<ji<k<j,代表的含義是在kk點進行切割,一分爲二。這就推出了狀態轉移方程:dp[i][j]=max(dp[i][k]+dp[k][j]+js(i,j,k),dp[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: 數學問題

題解:打表+二位前綴和記錄答案。注意兩個個坑點:①是:jmin(i,m)j≤min(i,m)
②是:由於jij≤i所以前綴和的ans[i][i+1]=ans[i][i]ans[i][i + 1] = ans[i][i]爲下一次的前綴和提供ans[i][j1]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: 求和

題解:矩陣構造+矩陣快速冪取模。我記着這個矩陣的構造是線性代數課本上的原題。
我們可以構造矩陣:
B1={AA0E}B^1= \left\{ \begin{matrix} A & A \\ 0 & E \\ \end{matrix} \right\}
則:
Bm={AmA+A2+...+Am0E}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。
第一天取aa
nn天取s=a+an1+an1n2+.....+an1n2nns=a+a*n1+a*n1*n2+.....+a*n1*n2*nn
我們可以進行提取公因式處理:
如:s=a(1+n1+n1n2+.....+n1n2nn)s=a*(1+n1+n1*n2+.....+n1*n2*nn)
化簡:(s/a1)=1+n1+n1n2+.....+n1n2nn(s/a-1)=1+n1+n1*n2+.....+n1*n2*nn
一步步進行,直到左式爲0。
而我們的剪枝就是從ss的因子進行處理,上述式子可以看成因子的乘積,這樣可以減少搜索量。
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章