SDNU ACM-ICPC 2020 Extra Training Contest 14 训练赛 A B C D G K 题解

A A water problem

一.题目大意

\quad n n n 是否同时为 73 73 73 137 137 137 的倍数.

\quad l e n g t h ( n ) ≤ 1 0 7 length(n) \leq 10^7 length(n)107.

二.分析

\quad 赛时产生失智行为,水题直接写.

三.代码实现

#include <bits/stdc++.h>
using namespace std;

typedef unsigned long long ull;
typedef long long ll;

const int M = (int)1e7;
const int N = (int)3e2;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = (ll)1e9 + 7;
const double eps = 1e-6;

char s[M + 5];

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
    int ca = 0;
    while(~scanf("%s", s + 1))
    {
        printf("Case #%d: ", ++ca);
        int n = strlen(s + 1);
        int r = 0; for(int i = 1; i <= n; ++i) r = (r * 10 + s[i] - '0') % 10001;
        puts(r ? "NO" : "YES");
    }
    return 0;
}

B Zhu and 772002

一.题目大意

\quad 给出 n n n 个数 a [ 1.. n ] a[1..n] a[1..n],其中 a [ 1.. n ] a[1..n] a[1..n] 的最大质因数 ≤ 2 × 1 0 3 \leq 2\times 10^3 2×103.

\quad 每个数至多选一次,求有多少种选数方案使得选出来数的乘积为完全平方数.

\quad n ≤ 3 × 1 0 2 , a [ i ] ≤ 1 0 18 n \leq 3 \times 10^2, a[i] \leq 10^{18} n3×102,a[i]1018.

二.分析

\quad 对每一个质因子列一个方程 ∑ j = 1 n A i , j x j = 0    ( m o d    2 ) \begin{aligned} \sum_{j=1}^n A_{i,j}x_{j} = 0 \; ( mod \; 2) \end{aligned} j=1nAi,jxj=0(mod2).

\quad 上式中, A i , j A_{i, j} Ai,j 表示 a [ j ] a[j] a[j] 含有第 i i i 个质数的指数的奇偶性, x j x_j xj 表示 a [ j ] a[j] a[j] 选不选.

\quad 由此,得到异或方程组,用高斯消元求出矩阵的秩 r r r。由于 x j ∈ { 0 , 1 } x_j \in \{0, 1\} xj{0,1},所以答案为 2 n − r − 1 2^{n-r} - 1 2nr1

三.代码实现

#include <bits/stdc++.h>
using namespace std;

typedef unsigned long long ull;
typedef long long ll;

const int M = (int)2e3;
const int N = (int)3e2;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = (ll)1e9 + 7;
const double eps = 1e-6;

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

bool a[N + 5][N + 5];

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

void divide(int p, ll n)
{
    for(int i = 1; i <= cnt; ++i)
    {
        if(n % prime[i] == 0)
        {
            int c = 0; ll t = n;
            while(t % prime[i] == 0) ++c, t /= prime[i];
            a[i][p] = (c & 1);
        }
    }
}

int gauss(int n, int m)
{
    int c, r;
    for(c = 1, r = 1; c <= m; ++c)
    {
        int t = r;
        for(int i = r; i <= n; ++i)
        {
            if(fabs(a[i][c]) > fabs(a[t][c]))   t = i;
        }
        if(fabs(a[t][c]) < eps) continue;
        for(int i = c; i <= m + 1; ++i) swap(a[t][i], a[r][i]);
        for(int i = m + 1; i >= c; --i) a[r][i] /= a[r][c];
        for(int i = 1; i <= n; ++i)
        {
            if(i == r)  continue;
            if(fabs(a[i][c]) > eps)
            {
                for(int j = m + 1; j >= c; --j)
                {
                    a[i][j] -= a[r][j] * a[i][c];
                }
            }
        }
        ++r;
    }
    for(int i = r; i <= n; ++i)
    {
        if(fabs(a[i][m + 1]) > eps)   return -1;//无解
    }
    return m - r + 1;//自由元个数
}

ll quick(ll a, ll b, ll p = mod)
{
    ll s = 1;
    while(b)
    {
        if(b & 1) s = s * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return s;
}

void work()
{
    memset(a, 0, sizeof(a));
    int n; scanf("%d", &n);
    ll x; for(int i = 1; i <= n; ++i) scanf("%lld", &x), divide(i, x);
    int r = gauss(cnt, n);
    printf("%lld\n", ((quick(2, r) - 1) % mod + mod) % mod);
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
    init();
    int T; scanf("%d", &T);
    for(int ca = 1; ca <= T; ++ca)
    {
        printf("Case #%d:\n", ca);
        work();
    }
    return 0;
}

C Magic boy Bi Luo with his excited tree

一.题目大意

\quad T T T 组测试,每次给一颗 n n n 个点的无根树,每个点有相应的价值 V [ i ] V[i] V[i],每条边有相应的花费 C [ i ] C[i] C[i].

\quad 当以 i i i 号点为根时,树的权值定义为从 i i i 号点出发,{价值和 - 花费和} 的最大值.

\quad 注意:每个点的价值至多获得一次,每条边的花费可多次.

\quad 求当根 i i i [ 1 , n ] [1,n] [1,n] 所有值时对应的树的权值.

\quad T ≤ 1 0 4 , n ≤ 1 0 5 , ∑ n < 1 0 6 , 1 ≤ V [ i ] , C [ i ] ≤ 1 0 4 T \leq 10^4, n \leq 10^5, \sum n < 10^6, 1 \leq V[i], C[i] \leq 10^4 T104,n105,n<106,1V[i],C[i]104.

二.分析

\quad 很明显的换根 DP,但还是写了很久…

\quad f [ i ] [ 0 ] f[i][0] f[i][0] 表示在以 1 1 1 号点为根的前提下,在以 i i i 号点为根的子树中走,终点不为 i i i 的最大收益.

\quad f [ i ] [ 1 ] f[i][1] f[i][1] 表示在以 1 1 1 号点为根的前提下,在以 i i i 号点为根的子树中走,终点为 i i i 的最大收益.

\quad f [ i ] [ 2 ] f[i][2] f[i][2] 表示在以 1 1 1 号点为根的前提下,在以 i i i 号点为根的子树中走,终点不为 i i i 的次大收益.

\quad p [ i ] p[i] p[i] 表示在以 1 1 1 号点为根的前提下,在以 i i i 号点为根的子树中走,终点不为 i i i 的最大收益对应的子节点编号.

\quad g [ i ] [ 0 ] g[i][0] g[i][0] 表示在以 1 1 1 号点为根的前提下,从 i i i 号点往 f a [ i ] fa[i] fa[i] 的方向走,终点不为 i i i 的最大收益.

\quad g [ i ] [ 1 ] g[i][1] g[i][1] 表示在以 1 1 1 号点为根的前提下,从 i i i 号点往 f a [ i ] fa[i] fa[i] 的方向走,终点为 i i i 的最大收益.

\quad 上述状态表示乍一看很晕,但其实画画图分析一下就自然而然地写出来了.

\quad 详见代码.

三.代码实现

#include <bits/stdc++.h>
using namespace std;

typedef unsigned long long ull;
typedef long long ll;

const int M = (int)1e5;
const int N = (int)25;
const int inf = 0x3f3f3f3f;
const ll mod = (ll)772002;
const double eps = 1e-6;

int n, cnt;
int head[M + 5];

struct node
{
    int v, w, nx;
}Edge[M * 2 + 5];

int V[M + 5];
int f[M + 5][3];
int p[M + 5];
int g[M + 5][2];

void init()
{
    cnt = 0;
    for(int i = 1; i <= n; ++i)
    {
        head[i] = -1;
    }
}

void add(int u, int v, int w)
{
    Edge[cnt].v = v;
    Edge[cnt].w = w;
    Edge[cnt].nx = head[u];
    head[u] = cnt++;
}

void dfs1(int u, int fa)
{
    f[u][0] = V[u], f[u][1] = V[u], f[u][2] = -inf;
    int f0, f1;
    for(int i = head[u]; ~i; i = Edge[i].nx)
    {
        int v = Edge[i].v;
        if(v == fa) continue;
        dfs1(v, u);
        f0 = max(0, f[v][0] - Edge[i].w);
        f1 = max(0, f[v][1] - 2 * Edge[i].w);
        if(f[u][0] + f1 < f[u][1] + f0) f[u][0] = f[u][1] + f0, p[u] = v;
        else                            f[u][0] = f[u][0] + f1;
        f[u][1] += f1;
    }
    for(int i = head[u]; ~i; i = Edge[i].nx)
    {
        int v = Edge[i].v;
        if(v == fa || v == p[u]) continue;
        f0 = max(0, f[v][0] - Edge[i].w);
        f1 = max(0, f[v][1] - 2 * Edge[i].w);
        f[u][2] = max(f[u][2], f[u][1] - f1 + f0);
    }
}

void dfs2(int u, int fa)
{
    int f1;
    for(int i = head[u]; ~i; i = Edge[i].nx)
    {
        int v = Edge[i].v;
        if(v == fa) continue;
        g[v][0] = V[v];
        f1 = max(0, f[v][1] - 2 * Edge[i].w);
        g[v][0] = max(g[v][0], V[v] + g[u][0] - V[u] + f[u][1] - f1 - Edge[i].w);
        if(p[u] != v) g[v][0] = max(g[v][0], V[v] + g[u][1] - V[u] + f[u][0] - f1 - Edge[i].w);
        else          g[v][0] = max(g[v][0], V[v] + g[u][1] - V[u] + f[u][2] - f1 - Edge[i].w);
        g[v][1] = V[v] + max(0, g[u][1] - V[u] + f[u][1] - f1 - 2 * Edge[i].w);
        dfs2(v, u);
    }
}

void work()
{
    scanf("%d", &n); init();
    for(int i = 1; i <= n; ++i) scanf("%d", &V[i]);
    for(int i = 1, u, v, w; i <= n - 1; ++i) scanf("%d %d %d", &u, &v, &w), add(u, v, w), add(v, u, w);
    dfs1(1, 0);
    g[1][0] = V[1], g[1][1] = V[1];
    dfs2(1, 0);
    for(int i = 1; i <= n; ++i) printf("%d\n", max(f[i][0] + g[i][1], f[i][1] + g[i][0]) - V[i]);
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
    int T; scanf("%d", &T);
    for(int ca = 1; ca <= T; ++ca)
    {
        printf("Case #%d:\n", ca);
        work();
    }
    return 0;
}

D Danganronpa

一.题目大意

\quad n n n 种礼物,每种物品有 a [ i ] 个 a[i] 个 a[i].

\quad 学生们排成一行,老师给每位学生发两个礼物,分别为普通礼物和神秘礼物,要求相邻两位同学的普通礼物不相同,神秘礼物没有限制,求最多能给多少位同学发礼物.

二.分析

\quad s s s 为所有礼物个数之和, m x mx mx 为所有礼物中礼物个数的最大值.

\quad m x ≥ s − m x − 1 mx \geq s - mx - 1 mxsmx1 时,可以让其他礼物排成一行,最多数量的礼物插空,保证了相邻的普通礼物不同。此时,若 m x − ( s − m x − 1 ) ≥ s − ( s − m x ) − ( s − m x − 1 ) mx - (s - mx - 1) \geq s-(s-mx) - (s-mx-1) mx(smx1)s(smx)(smx1),则答案为 2 ( s − m x − 1 ) + 1 2(s - mx - 1) + 1 2(smx1)+1;否则,需要结尾把后面的普通礼物放到神秘礼物中,答案为 s 2 \frac{s}{2} 2s.

\quad 否则,不难(用)证明答案为 s 2 \frac{s}{2} 2s.

三.代码实现

#include <bits/stdc++.h>
using namespace std;

typedef unsigned long long ull;
typedef long long ll;

const int M = (int)1e5;
const int N = (int)3e2;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = (ll)1e9 + 7;
const double eps = 1e-6;

int n, a[M + 5];

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
    int T; scanf("%d", &T);
    for(int ca = 1; ca <= T; ++ca)
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        int s = 0, mx = 0; for(int i = 1; i <= n; ++i) s += a[i], mx = max(mx, a[i]);
        int ans = 0;
        if(mx >= s - mx - 1)
        {
            if(2 * mx - s + 1 >= 2 * s - 2 * mx - 1) ans = 2 * s - 2 * mx - 1;
            else                                     ans = s / 2;
        }
        else                                         ans = s / 2;
        printf("Case #%d: %d\n", ca, ans);
    }
    return 0;
}

G Mountain

一.题目大意

\quad 给一张 n × m n \times m n×m 的地图 s s s s [ i ] [ j ] ∈ { ′ ′ X ′ , ′ . ′ } s[i][j] \in \{''X', '.'\} s[i][j]{X,.}.

\quad 要求在每个位置填数,每个位置上的数 ∈ [ 1 , n × m ] \in [1, n\times m] [1,n×m] 且两两不相同,满足 ′ X ′ 'X' X 位置上的数是它周围 8 8 8 个数的最小值且 ′ . ′ '.' . 不是.

\quad 求填数的方案数.

\quad n , m ≤ 5 n,m \leq 5 n,m5.

二.分析

\quad 这数据范围显然是状压 DP.

\quad 易知图中若存在相邻的 ′ X ′ 'X' X 则方案数为 0 0 0,则图中最多存在 9 9 9 ′ X ′ 'X' X.

\quad 考虑从小到大填数, f [ i ] [ j ] f[i][j] f[i][j] 表示用了前 i i i 个数, ′ X ′ 'X' X 的填充状态为 j j j 的方案数,即得状态转移方程.

\quad 可这样只保证了 ′ X ′ 'X' X 是极小的,无法保证 ′ . ′ '.' . 不是极小的,所以这里非常妙可以用容斥来做.

三.代码实现

#include <bits/stdc++.h>
using namespace std;

typedef unsigned long long ull;
typedef long long ll;

const int M = (int)5;
const int N = (int)25;
const int inf = 0x3f3f3f3f;
const ll mod = (ll)772002;
const double eps = 1e-6;

int n, m, ans;
char s[M][M];
int id[M][M];
int f[N + 1][1<<9];
int c1[1<<9], c2[1<<9];
int dx[] = {0, -1, -1, -1, 0, 1, 1, 1};
int dy[] = {1, 1, 0, -1, -1, -1, 0, 1};

bool check(int i, int j)
{
    for(int k = 0, x, y; k < 8; ++k)
    {
        x = i + dx[k], y = j + dy[k];
        if(x >= 0 && x < n && y >= 0 && y < m && s[x][y] == 'X') return 0;
    }
    return 1;
}

int cal(int c)
{
    int x, y, flag;
    for(int i = 0; i < (1<<c); ++i)
    {
        c1[i] = 0;
        for(int j = 0; j < n; ++j)
        {
            for(int k = 0; k < m; ++k)
            {
                if(s[j][k] != '.') continue;
                flag = 1;
                for(int l = 0; l < 8; ++l)
                {
                    x = j + dx[l], y = k + dy[l];
                    if(x >= 0 && x < n && y >= 0 && y < m && s[x][y] == 'X' && (((~i)>>id[x][y]) & 1)) flag = 0;
                }
                c1[i] += flag;
            }
        }
    }
    f[0][0] = 1;
    for(int i = 1; i <= n * m; ++i)
    {
        for(int j = 0; j < (1<<c); ++j)
        {
            f[i][j] = f[i - 1][j] * (c1[j] - (i - 1 - c2[j])) % mod;
            for(int k = 0; k < c; ++k)
            {
                if((j>>k) & 1) f[i][j] = (f[i][j] + f[i - 1][j^(1<<k)]) % mod;
            }
        }
    }
    return f[n * m][(1<<c) - 1];
}

void dfs(int u, int v, int e)
{
    if(u == n * m)
    {
        if(e & 1) ans = (ans - cal(v)) % mod;
        else      ans = (ans + cal(v)) % mod;
        return;
    }
    int x = u / m, y = u % m;
    if(s[x][y] == 'X') id[x][y] = v, dfs(u + 1, v + 1, e);
    else
    {
        dfs(u + 1, v, e);
        if(check(x, y))
        {
            s[x][y] = 'X';
            id[x][y] = v, dfs(u + 1, v + 1, e + 1);
            s[x][y] = '.';
        }
    }
}

void work()
{
    for(int i = 0; i < n; ++i) scanf("%s", s[i]);
    bool f = 1; for(int i = 0; i < n; ++i) for(int j = 0; j < m; ++j) if(s[i][j] == 'X' && !check(i, j)) f = 0;
    if(!f) {printf("0\n"); return;}
    ans = 0; dfs(0, 0, 0);
    ans = (ans % mod + mod) % mod;
    printf("%d\n", ans);
}

int getOne(int n)
{
    int c = 0;
    while(n) c += (n & 1), n >>= 1;
    return c;
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
    for(int i = 0; i < (1<<9); ++i) c2[i] = getOne(i);
    int ca = 0;
    while(~scanf("%d %d", &n, &m))
    {
        printf("Case #%d: ", ++ca);
        work();
    }
    return 0;
}

K Lweb and String

一.题目大意

\quad 给一个由小写字母组成的字符串,可将字母集合映射到任意数字集合,求将该字符串映射成数字串的严格最长上升子序列的长度.

\quad l e n g t h ( s ) ≤ 1 0 5 length(s) \leq 10^5 length(s)105.

二.分析

\quad 显然答案就是字符串的字符集大小.

三.代码实现

#include <bits/stdc++.h>
using namespace std;

typedef unsigned long long ull;
typedef long long ll;

const int M = (int)1e5;
const int N = (int)1e5;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = (ll)1e9 + 7;
const double eps = 1e-6;

char s[M + 5];
int cnt[26];

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
    int T; scanf("%d", &T);
    for(int ca = 1; ca <= T; ++ca)
    {
        memset(cnt, 0, sizeof(cnt));
        scanf("%s", s + 1);
        int n = strlen(s + 1);
        for(int i = 1; i <= n; ++i) cnt[s[i] - 'a']++;
        int c = 0; for(int i = 0; i < 26; ++i) c += (cnt[i] > 0);
        printf("Case #%d: %d\n", ca, c);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值