中国石油大学ACM俱乐部开放训练赛の七题菜鸡题解(Done)

目录

问题 A: sciorz画画

问题 B: 奎奎发红包

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

问题 D: 大数

问题 E: Ktree

问题 F: 求和

问题 G: 奎奎画画

问题 H: qiqi and sciorz

问题 I: 星区划分

问题 J: 加油2020

问题 K: 数学问题


问题 A: sciorz画画

一. 题目大意

求凸包最优三角抛分,三角权函数为 w(i, j, k) = a[i] * a[j] * a[k].

二. 分析:

裸题直接上

小吐槽:这 100 组数据是假的...

三. 代码实现:

#include 
using namespace std;

typedef long long ll;

const int M = (int)1e2;
const ll inf = 0x3f3f3f3f3f3f3f3f;

int a[M + 5];
ll dp[M + 5][M + 5];

int main()
{
    int T;
    scanf("%d", &T);
    for(int ca = 1; ca <= T; ++ca)
    {
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        memset(dp, 0, sizeof(dp));
        for(int len = 3; len <= n; ++len)
        {
            for(int l = 1; l + len - 1 <= n; ++l)
            {
                int r = l + len - 1;
                for(int k = l + 1; k <= r - 1; ++k)
                {
                    dp[l][r] = max(dp[l][r], dp[l][k] + dp[k][r] + 1ll * a[l] * a[k] * a[r]);
                }
            }
        }
        printf("Case #%d: %lld\n", ca, dp[1][n]);
    }
    return 0;
}

问题 B: 奎奎发红包

一. 题目大意:

有 n 个人,每人有两个属性 v[i], t[i].

n 个人按照一定顺序前来,第 i 个人的花费为 v[s[i]] * (t[s[1]] + t[s[2]] + ... + t[s[i]]).

求 n 个人的最小花费.

二. 分析:

简单贪心,临项交换法即可证明正确性.

三. 代码实现:

#include 
using namespace std;

typedef long long ll;

const int M = (int)1e5;
const ll inf = 0x3f3f3f3f3f3f3f3f;

struct node
{
    int v, t;
}s[M + 5];

bool cmp(node a, node b)
{
    return a.t * b.v < a.v * b.t;
}

int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
    {
        scanf("%d %d", &s[i].v, &s[i].t);
    }
    sort(s + 1, s + n + 1, cmp);
    int t = 0;
    ll ans = 0;
    for(int i = 1; i <= n; ++i)
    {
        t += s[i].t;
        ans += 1ll * t * s[i].v;
    }
    printf("%lld\n", ans);
    return 0;
}

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

一. 题目大意:

一个沙雕要吃 s 坨屎,第一天任意吃但不能全吃完,第 i 天的食屎量必须是第 i - 1 天的 2~9 整数倍,最后一天需要恰好吃完.

求最少食屎天数.

二. 分析:

设第一天吃 x,第 i 天吃的为第 i - 1 的 a[i] 倍.

s = x + xa[1] + xa[1]a[2] + ...+xa[1]a[2]..a[n-1]

s=x(1+a[1]+a[1]a[2]+...+a[1]a[2]a[n-1])

s = x(1+a[1](1+a[2]+...+a[2]...a[n-1]))

暴搜就完事~

三.代码实现:

#include 
using namespace std;

typedef long long ll;

const int inf = 0x3f3f3f3f;

int dfs(int s)
{
    if(s >= 2 && s <= 9)    return 1;
    int ans = inf;
    for(int i = 2; i <= 9; ++i)
    {
        if(s % i == 0)
        {
            ans = min(ans, dfs(s / i - 1) + 1);
        }
    }
    return ans;
}

int main()
{
    int s, ans = inf;
    scanf("%d", &s);
    for(int i = 1; 1ll * i * i <= s; ++i)
    {
        if(s % i == 0)
        {
            ans = min(ans, dfs(s / i - 1) + 1);
            if(i != s / i)  ans = min(ans, dfs(i - 1) + 1);
        }
    }
    printf("%d\n", ans == inf ? -1 : ans);
    return 0;
}

问题 D: 大数

一. 题目大意:

求字符串的最小循环节,不存在输出 -1.

二. 分析:

枚举长度 n 的约数,暴力 check 即可.

三. 代码实现:

#include 
using namespace std;

typedef long long ll;

const int M = (int)1e6;
const ll inf = 0x3f3f3f3f3f3f3f3f;

int n;
string s;
vector  fac;

bool check(int l)
{
    for(int i = 0; i + l - 1 < n; i += l)
    {
        if(s.substr(0, l) != s.substr(i, l))    return 0;
    }
    return 1;
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> s;
    n = s.size();
    for(int i = 1; 1ll * i * i <= n; ++i)
    {
        if(n % i == 0)
        {
            fac.push_back(i);
            if(i != n / i)  fac.push_back(n / i);
        }
    }
    sort(fac.begin(), fac.end());
    for(auto x: fac)
    {
        if(x != n && check(x))
        {
            cout << s.substr(0, x) << endl;
            return 0;
        }
    }
    cout << -1 << endl;
    return 0;
}

问题 E: Ktree

一. 题目大意:

给出一棵树以及所有边权的和,现让你给每条边分配权值,使得树的直径最小.

求树的最小直径.

二. 分析:

由于直径一定是连接两个叶子结点的路径,简单贪心可得最优策略为每个叶子结点平均分摊权值和.

三. 代码实现:

#include 
using namespace std;

const int M = (int)1e5;

int de[M + 5];

int main()
{
    int n, s;
    scanf("%d %d", &n, &s);
    for(int i = 0, u, v; i < n - 1; ++i)
    {
        scanf("%d %d", &u, &v);
        de[u]++, de[v]++;
    }
    int cnt = 0;
    for(int i = 1; i <= n; ++i) cnt += (de[i] == 1);
    printf("%.2f\n", 2.0 * s / cnt);
    return 0;
}

问题 F: 求和

一. 题目大意:

给出 n 阶矩阵 A,求 A + A^2 + ... + A^m

二. 分析:

构造如下矩阵

B = \begin{bmatrix} A& A \\ 0& E \end{bmatrix}

可得:B^m = \begin{bmatrix} A^m &A + A^2 + ... + A^m \\ 0&E \end{bmatrix}

三. 代码实现:

#include 
using namespace std;

typedef long long ll;

const int mod = (int)1e9 + 7;

int n, m, t;
struct Matrix
{
    int mat[60][60];
}B;

Matrix mul(Matrix A, Matrix B)
{
    Matrix C;
    memset(C.mat, 0, sizeof(C.mat));
    for(int i = 0; i < t; ++i)
    {
        for(int j = 0; j < t; ++j)
        {
            for(int k = 0; k < t; ++k)
            {
                C.mat[i][j] = (C.mat[i][j] + 1ll * A.mat[i][k] * B.mat[k][j]) % mod;
            }
        }
    }
    return C;
}

Matrix quick(Matrix A, int b)
{
    Matrix Sum = A;
    --b;
    while(b)
    {
        if(b & 1)   Sum = mul(Sum, A);
        A = mul(A, A);
        b >>= 1;
    }
    return Sum;
}

int main()
{
    scanf("%d %d", &n, &m);
    t = n * 2;
    for(int i = 0; i < n; ++i)
    {
        for(int j = 0; j < n; ++j)
        {
            scanf("%d", &B.mat[i][j]);
            B.mat[i][j + n] = B.mat[i][j] %= mod;
            B.mat[i + n][j + n] = (i == j);
        }
    }
    B = quick(B, m);
    for(int i = 0; i < n; ++i)
    {
        for(int j = n; j < t; ++j)
            printf("%d%c", B.mat[i][j], j == t - 1 ? '\n' : ' ');
    }
    return 0;
}

问题 G: 奎奎画画

一. 题目大意:

n * m 的矩阵,初始格均为白色,q 次操作,每次操作选择平行于 x 轴或 y 轴的线段并染为黑色,求每次操作后的白色格连通块数.

二. 分析:

先进行 q 次操作,同时记录 (i, j) 点被覆盖的次数.

之后遍历矩阵,找到一个白色格便搜索其四周是否有白色格,并查集标记连通块.

之后依次撤销 q 次操作,操作与上面相似.

详见代码.

三. 代码实现:

#include 
using namespace std;

const int M = (int)1e3;
const int N = (int)1e4;

int n, m, q, cnt;
int fa[M * M + 5];
int st[N + 5], top;
int c[M + 5][M + 5];
int x1[N + 5], y1[N + 5];
int x2[N + 5], y2[N + 5];
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};

int tofind(int x)
{
    if(x == fa[x])  return x;
    return fa[x] = tofind(fa[x]);
}

void work(int x, int y)
{
    int xx, yy, u, v;
    for(int i = 0; i < 4; ++i)
    {
        xx = x + dx[i];
        yy = y + dy[i];
        if(xx >= 1 && xx <= n && yy >= 1 && yy <= m && !c[xx][yy])
        {
            u = (x - 1) * m + y;
            v = (xx - 1) * m + yy;
            u = tofind(u), v = tofind(v);
            if(u != v)
            {
                --cnt;
                fa[u] = v;
            }
        }
    }
}

int main()
{
    scanf("%d %d %d", &n, &m, &q);
    for(int i = 1; i <= q; ++i)
    {
        scanf("%d %d %d %d", &x1[i], &y1[i], &x2[i], &y2[i]);
        for(int x = x1[i]; x <= x2[i]; ++x)
        {
            for(int y = y1[i]; y <= y2[i]; ++y)
            {
                c[x][y]++;
            }
        }
    }
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= m; ++j)
        {
            fa[(i - 1) * m + j] = (i - 1) * m + j;
        }
    }
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= m; ++j)
        {
            if(!c[i][j])
            {
                ++cnt;
                work(i, j);
            }
        }
    }
    for(int i = q; i >= 1; --i)
    {
        st[++top] = cnt;
        for(int x = x1[i]; x <= x2[i]; ++x)
        {
            for(int y = y1[i]; y <= y2[i]; ++y)
            {
                --c[x][y];
            }
        }
        for(int x = x1[i]; x <= x2[i]; ++x)
        {
            for(int y = y1[i]; y <= y2[i]; ++y)
            {
                if(!c[x][y])
                {
                    ++cnt;
                    work(x, y);
                }
            }
        }
    }
    while(top)  printf("%d\n", st[top--]);
    return 0;
}

问题 H: qiqi and sciorz

一. 题目大意:

K 倍博弈.

二. 分析(照抄题解)

中国石油大学ACM俱乐部开放训练赛の七题菜鸡题解(Done)_第1张图片

三. 代码实现

#include 
using namespace std;

const int M = (int)1e5;

int a[M + 5];
int b[M + 5];

int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        int n, k;
        scanf("%d %d", &n, &k);
        int i = 0, j = 0;
        a[i] = b[j] = 1;
        while(a[i] < n)
        {
            ++i;
            a[i] = b[i - 1] + 1;
            while(k * a[j + 1] < a[i])  ++j;
            if(k * a[j] < a[i]) b[i] = a[i] + b[j];
            else                b[i] = a[i];
        }
        if(a[i] == n)
        {
            printf("qiqi lose\n");
            continue;
        }
        int ans;
        while(n)
        {
            if(n >= a[i])
            {
                n -= a[i];
                ans = a[i];
            }
            --i;
        }
        printf("%d\n", ans);
    }
    return 0;
}

问题 I: 星区划分

一. 题目大意:

三维坐标中给出 n 个点,每个点有权值 v.

定义两个点为同一系当且仅当两个点满足上下左右前后之一相邻 且 权值差的绝对值不超过 m.

关系具有传递性,求共有多少个系.

二. 分析:

N^2 枚举 + 并查集

三. 代码实现:

#include 
using namespace std;

typedef long long ll;

const int M = (int)1e3;
const ll inf = 0x3f3f3f3f3f3f3f3f;

int x[M + 5];
int y[M + 5];
int z[M + 5];
int v[M + 5];
int fa[M + 5];

ll dist(int i, int j)
{
    ll dis = 0;
    dis += (ll)abs(x[i] - x[j]);
    dis += (ll)abs(y[i] - y[j]);
    dis += (ll)abs(z[i] - z[j]);
    return dis;
}

int cal(int i, int j)
{
    return (int)abs(v[i] - v[j]);
}

int tofind(int x)
{
    if(x == fa[x])  return x;
    return fa[x] = tofind(fa[x]);
}

int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; ++i)
    {
        fa[i] = i;
        scanf("%d %d %d %d", &x[i], &y[i], &z[i], &v[i]);
    }
    for(int i = 1; i <= n; ++i)
    {
        for(int j = i + 1; j <= n; ++j)
        {
            if(dist(i, j) <= 1 && cal(i, j) <= m)
            {
                int u = tofind(i);
                int v = tofind(j);
                if(u != v)
                {
                    fa[u] = v;
                }
            }
        }
    }
    int cnt = 0;
    for(int i = 1; i <= n; ++i) cnt += (fa[i] == i);
    printf("%d\n", cnt);
    return 0;
}

问题 J: 加油2020

一. 题目大意:

二. 分析:

中国石油大学ACM俱乐部开放训练赛の七题菜鸡题解(Done)_第2张图片

三. 代码实现:

#include 
using namespace std;

typedef long long ll;

const int M = (int)1e3;
const int N = (int)2019;

int n; ll p;

bool is_prime[N + 5];
int cnt, prime[N + 5];

int tot;
struct node
{
    int p, c;
}fac[M + 5];
ll f[M + 5];

ll g[M + 5];

void get_prime()
{
    memset(is_prime, 1, sizeof(is_prime));
    is_prime[0] = is_prime[1] = 0;
    for(int i = 2; i <= N; ++i)
    {
        if(is_prime[i]) prime[++cnt] = i;
        for(int j = 1; j <= cnt && i * prime[j] <= N; ++j)
        {
            is_prime[i * prime[j]] = 0;
            if(i % prime[j] == 0)   break;
        }
    }
}

ll mul(ll a, ll b)
{
    ll sum = 0;
    while(b)
    {
        if(b & 1)   sum = (sum + a) % p;
        a = (a + a) % p;
        b >>= 1;
    }
    return sum;
}

void get_fac()
{
    for(int i = 1; i <= cnt && prime[i] <= N; ++i)
    {
        int cnt = 0;
        int p = prime[i];
        while(p <= N)
        {
            cnt += N / p;
            if(1ll * p * prime[i] > N)  break;
            p *= prime[i];
        }
        if(cnt) fac[++tot].p = prime[i], fac[tot].c = cnt;
    }
}

void get_f()
{
    get_prime();
    get_fac();
    for(int i = 1; i <= n; ++i)
    {
        f[i] = 1;
        for(int j = 1; j <= tot; ++j)
        {
            f[i] = mul(f[i], i * fac[j].c + 1);
        }
    }
}

ll quick(ll a, ll b)
{
    ll sum = 1;
    while(b)
    {
        if(b & 1)   sum = mul(sum, a);
        a = mul(a, a);
        b >>= 1;
    }
    return sum;
}

ll inv(int a)
{
    return quick(a, p - 2);
}

ll get_sum(ll a, int b)
{
    return mul(a, inv(b));
}

void get_g()
{
    ll a = 673, b = 3;
    for(int i = 1; i <= n; ++i)
    {
        a = mul(a, 673), b = mul(b, 3);
        g[i] = mul(get_sum(a - 1, 673 - 1), get_sum(b - 1, 3 - 1));
    }
}

int main()
{
    scanf("%d %lld", &n, &p);
    get_f();
    get_g();
    ll ans = 0;
    for(int i = 1; i <= n; ++i) ans = (ans + mul(f[i], g[i])) % p;
    printf("%lld\n", ans);
    return 0;
}

问题 K: 数学问题

一. 题目大意:

给一个整数 g,接下来 T 组询问,每组给出 n 和 m.

求有多少个 (i, j) 对,使得 g 能整除 C_{\;j}^{\; i},其中0 ≤ i ≤ n,0 ≤ j ≤ min(i,m).

二. 分析:

O(2000 * 2000)预处理出所有组合数及其前缀对和,O(1)查询.

三. 代码实现:

#include 
using namespace std;

typedef long long ll;

const int M = (int)2e3;
const int mod = (int)1e9 + 7;

int T, g;
int c[M + 5][M + 5];
int s[M + 5][M + 5];

void init()
{
    for(int i = 1; i <= M + 1; ++i)
    {
        c[i][1] = 1 % g;
        for(int j = 2; j <= i; ++j)
        {
            c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % g;
        }
    }
    for(int i = 1; i <= M + 1; ++i)
    {
        for(int j = 1; j <= i; ++j)
        {
            c[i][j] = !c[i][j];
        }
    }
    for(int i = 1; i <= M + 1; ++i)
    {
        for(int j = 1; j <= M + 1; ++j)
        {
            s[i][j] = s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + c[i][j];
        }
    }
}

int main()
{
    scanf("%d %d", &T, &g);
    init();
    while(T--)
    {
        int n, m;
        scanf("%d %d", &n, &m);
        printf("%d\n", s[n + 1][m + 1]);
    }
    return 0;
}

 

你可能感兴趣的:(#,矩阵运算)