2022上海市赛SHCPC【个人题解AEGHILMN】

A - Another A+B Problem(暴力)

思路

暴力枚举第一个数字和第二个数字(99*99),判断是否满足GPB的条件即可。
判断是否满足条件的话就乱搞一下就可以了,具体看代码。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

typedef pair<int, int> PII;

char a[10], b[10];

bool B[10];
int P[10];
int G[10];

bool vis[10];
int cnt[10];

bool check(int i, int j, int k)
{
    vector<int> c(10);
    c[0] = i / 10, c[1] = i % 10;
    c[3] = j / 10, c[4] = j % 10;
    c[6] = k / 10, c[7] = k % 10;

    for (int i = 0; i < 10; i ++ )
        cnt[i] = 0;
    for (int j = 0; j <= 7; j ++ )
        if (j != 2 && j != 5)
            cnt[c[j]] ++;

    for (int j = 0; j <= 7; j ++ )
    {
        if (j == 2 || j == 5) continue;
        if (b[j] == 'G')
        {
            if (c[j] != a[j] - '0') return false;
        }
        else 
        {
            if (c[j] == a[j] - '0') return false;
            if (b[j] == 'B')
            {
                if (cnt[a[j] - '0'] != G[a[j] - '0'] + P[a[j] - '0']) 
                    return false;
            }
        }
    }

    for (int i = 0; i < 10; i ++ )
        if (cnt[i] - G[i] < P[i]) return false;
    
    return true;
            
}

int main()
{
    #ifdef ZYCMH
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
    #endif

    scanf("%s", a);
    scanf("%s", b);

    for (int i = 0; b[i]; i ++ )
        if (b[i] == 'B')
            B[a[i] - '0'] = true;
        else if(b[i] == 'P')
            P[a[i] - '0'] ++;
        else if (b[i] == 'G' && a[i] != '+' && a[i] != '=') 
            G[a[i] - '0'] ++;
    
    vector<PII> ans;
    for (int i = 0; i <= 99; i ++ )
        for (int j = 0; j <= 99; j ++ )
            if (i + j <= 99 && check(i, j, i + j))
                ans.push_back(make_pair(i, j));

    int sz = ans.size();
    printf("%d\n", sz);
    for (int i = 0; i < sz; i ++ )
        printf("%d%d+%d%d=%d%d\n", ans[i].first / 10, ans[i].first % 10, ans[i].second / 10, ans[i].second % 10, (ans[i].first + ans[i].second) / 10, (ans[i].first + ans[i].second) % 10);
    return 0;
}

E - Expenditure Reduction(dp)

思路

f [ i ] [ j ] f[i][j] f[i][j]表示包含 F [ 1.. i ] F[1..i] F[1..i]的以 S [ j ] S[j] S[j]结尾的子串中,最短的子串的开始位置,那么显然有以下转移方程:
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ k ] ) , ( S [ k ] = = F [ i − 1 ] , k < = j − 1 ) f[i][j] = max(f[i - 1][k]), (S[k] == F[i - 1], k <= j - 1) f[i][j]=max(f[i1][k]),(S[k]==F[i1],k<=j1)
由于要满足 S [ k ] = = F [ i − 1 ] S[k] == F[i - 1] S[k]==F[i1],于是只需要记下每个字符的 f [ i − 1 ] f[i - 1] f[i1]的最大前缀状态(这个前缀是针对于S串中的位置)即可实现 O ( 1 ) O(1) O(1)转移,详细见代码。

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100005, INF = 0x3f3f3f3f;

int n, m;
char S[N], F[105];
int f[105][N];
int mx[N][36];

int get(char c)
{
    if (c >= '0' && c <= '9') return c - '0';
    else return c - 'a' + 10;
}

int main()
{
    #ifdef ZYCMH
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
    #endif
    int _; scanf("%d", &_);
    while (_ --)
    {
        scanf("%s", S + 1);
        scanf("%s", F + 1);
        n = strlen(S + 1);
        m = strlen(F + 1);

        for (int j = 1; j <= n; j ++ ) 
        {
            for (int i = 0; i < 36; i ++ )
                mx[j][i] = mx[j - 1][i];
            mx[j][get(S[j])] = j;
        }

        for (int i = 1; i <= m; i ++ )
            for (int j = 1; j <= n; j ++ )
                f[i][j] = -1;

        for (int i = 1; i <= n; i ++ )
            if (S[i] == F[1])
                f[1][i] = i;
        
        for (int i = 2; i <= m; i ++ )
            for (int j = 1; j <= n; j ++ )  
                if (S[j] == F[i] && mx[j - 1][get(F[i - 1])])
                    f[i][j] = f[i - 1][mx[j - 1][get(F[i - 1])]];

        int pos = 0, ans = n + 1;
        for (int i = 1; i <= n; i ++ )
            if (S[i] == F[m] && f[m][i] != -1)
            {
                int len = i - f[m][i] + 1;
                if (len < ans)
                    ans = len, pos = i;
            }

        // printf("pos = %d ans = %d\n", pos, ans);
        for (int i = pos - ans + 1; i <= pos; i ++ )
            printf("%c", S[i]);
        puts("");
    }
    return 0;
}

G - Gua! (签到)

思路

实际造成伤害的次数为时间内休息时间段的段数加一,即为 S ∗ R / 60 + 1 S * R / 60 + 1 SR/60+1,那么算出理论最高伤害与实际伤害 D D D比较即可,注意特判 R = 0 R=0 R=0的情况。

代码

#include <iostream>

using namespace std;

typedef long long LL;

int main()
{
    #ifdef ZYCMH
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
    #endif
    int _; scanf("%d", &_);
    while (_ --)
    {
        int B, R, D, S;
        scanf("%d%d%d%d", &B, &R, &D, &S);
        if (R == 0)
        {
            if (D)
                puts("gua!");
            else puts("ok");
            continue;
        }
        LL t = S * R / 60 + 1;
        t *= B;
        if (D <= t)
            puts("ok");
        else puts("gua!");
    }
    return 0;
}

H - Heirloom Painting(思维)

思路

vp时队友写滴,就是判断有没有一段连续段(考虑了环之后的)长度大于 k k k,没有的话则无解。有解的情况下,最优解就是每一段暴力涂满,最优次数也就是段的长度除以k上取整。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int t,m,n,k,col[1000005],fir,las,ans,colm[1000005],u;
int main()
{
    #ifdef ZYCMH
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
    #endif
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&m,&n,&k);
        u=0;
        col[0]=0;
        ans=0;
        for(int i=1;i<=m;i++)
        {
            scanf("%d",&col[i]);
            if(col[i]!=col[i-1])
            {
                u++;
                colm[u]=1;
            }
            else
            {
                colm[u]++;
            }
        }
        if(col[1]==col[m]&&u!=1)
        {
            colm[1]+=colm[u];
            colm[u]=0;
            u--;
        }
        int flag=0;
        for(int i=1;i<=u;i++)
        {
            if(colm[i]>=k)
            {
                flag=1;
            }
            ans+=(colm[i]+k-1)/k;
        }
        if(flag)printf("%d\n",ans);
        else printf("-1\n");
    }
}

I - It Takes Two of Two(期望dp)

思路

我们把每个人抽象为一个点,把每次比赛抽象成连边,那么题目的限制就是,不能重边不能自环,每个点的度数不能超过2。故最终形成的图里,只有孤立点、链和环三种可能。
于是原题意转化为,每一次随机找两个点连边,如果连边之后的图不满足限制,则该边不加进图中,如果当前图中不存在点对使得连边之后仍然满足限制,则停止连边,问停止的期望次数。
由于图中只有孤立点、链和环三种可能,考虑一下所有合法的连边:

  • 两个不同的孤立点连边
  • 孤立点和长度为1的链的一端连边
  • 孤立点和长度大于1的链的一端连边
  • 两条不同的链的某两端连边
  • 长度大于1的链的两端连边
    于是,我们需要记下孤立点的数量,长度为1的链的数量,长度大于1的链的数量以方便我们转移。
    f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]为当前图中存在 i i i个孤立点, j j j条长度为1的链, k k k条长度大于1的链,停止的期望步数。
    转移的话,按照上面合法的连边用记忆化搜索转移即可。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <vector>
#include <map>
#include <queue>
#include <cmath>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 205;

int n;
double f[N][N][N];

double dfs(int i, int j, int k)
{
    if (f[i][j][k] != -1) return f[i][j][k];

    f[i][j][k] = 0;

    int all = 0;
    if (i >= 2) all += i * (i - 1), f[i][j][k] += (dfs(i - 2, j + 1, k) + 1) * i * (i - 1);
    if (i >= 1 && j >= 1) all += i * 4 * j, f[i][j][k] += (dfs(i - 1, j - 1, k + 1) + 1) * i * 4 * j;
    if (i >= 1) all += i * 4 * k, f[i][j][k] += (dfs(i - 1, j, k) + 1) * i * 4 * k;
    if (j >= 2) all += j * (j - 1) * 4, f[i][j][k] += (dfs(i, j - 2, k + 1) + 1) * j * (j - 1) * 4;
    if (j >= 1) all += j * k * 8, f[i][j][k] += (dfs(i, j - 1, k) + 1) * j * k * 8;
    if (k >= 1) all += k * (k - 1) * 4, f[i][j][k] += ( dfs(i, j, k - 1) + 1 ) * k * (k - 1) * 4;
    if (k >= 1) all += 2 * k, f[i][j][k] += (dfs(i, j, k - 1) + 1) * 2 * k;
    f[i][j][k] += n * n - all;
    f[i][j][k] /= all;

    return f[i][j][k];

}

int main()
{
    #ifdef ZYCMH
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
    #endif 
    scanf("%d", &n);
    for (int i = 0; i <= n; i ++ )
        for (int j = 0; j <= n; j ++ )
            for (int k = 0; k <= n; k ++ )
                f[i][j][k] = -1;
    
    f[1][0][0] = f[0][1][0] = f[0][0][0] = 0;
    printf("%.10f\n", dfs(n, 0, 0));
    return 0;
}

L - Last Warning of the Competition Finance Officer(AC自动机dp)

思路

不难想到一个状态定义,定义 f [ i ] f[i] f[i]表示 s [ 1... i ] s[1...i] s[1...i]中所有分配方案的分数和,令在 s [ i ] s[i] s[i]位置匹配上的 t t t串集合为 S S S,则有以下转移方程:
f [ i ] = ∑ j f [ i − l e n [ j ] + 1 ] + v a l [ j ] , t j ⊂ S f[i]=\sum_{j}f[i-len[j]+1]+val[j],t_j \subset S f[i]=jf[ilen[j]+1]+val[j],tjS
可以考虑利用AC自动机找到以 s [ i ] s[i] s[i]结尾的所有匹配的串(集合 S S S),直接在AC自动机上跳fail即可找到所有串,但是可能会超时。
考虑优化跳fail的过程,我们可以dfs一遍保存每个AC自动机上的结点在fail树上最近的已标记祖先(已标记表示为某个t串的节点)

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <vector>
#include <map>
#include <queue>
#include <cmath>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 200005, mod = 998244353;

int n;
char s[N];
char t[N];
int ch[N][26];
int tot;
int fail[N], last[N];
int ed[N];
int f[N];
int len[N], val[N];
struct edge
{
    int to, next;
}e[N << 1];
int head[N], cnt;

void add(int u, int v)
{
    e[++ cnt] = {v, head[u]};
    head[u] = cnt;
}

void insert(char s[], int idx)
{
    int u = 0;
    for (int i = 0; s[i]; i ++ )
    {
        int v = s[i] - 'a';
        if (!ch[u][v]) ch[u][v] = ++ tot;
        u = ch[u][v];
    }
    ed[u] = idx;
}

void get_fail()
{
    queue<int> q;
    for (int i = 0; i < 26; i ++ )
        if (ch[0][i]) q.push(ch[0][i]), add(0, ch[0][i]);
    while (!q.empty())
    {
        int u = q.front(); q.pop();
        for (int i = 0; i < 26; i ++ )
        {
            if (ch[u][i])
            {
                fail[ch[u][i]] = ch[fail[u]][i];
                add(fail[ch[u][i]], ch[u][i]);
                q.push(ch[u][i]);
            }
            else ch[u][i] = ch[fail[u]][i];
        }
    }
}

void dfs(int u)
{
    for (int i = head[u]; i; i = e[i].next)
    {
        int v = e[i].to;
        if (ed[u] == -1) last[v] = last[u];
        else last[v] = u;
        dfs(v);
    }
}

int main()
{
    #ifdef ZYCMH
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
    #endif

    memset(ed, -1, sizeof ed);

    scanf("%s", s);
    n = strlen(s);
    int _;
    scanf("%d", &_);
    ed[0] = 0;
    for (int i = 1; i <= _; i ++ )
    {
        scanf("%s", t), insert(t, i);
        scanf("%d", &val[i]);
        len[i] = strlen(t);
    }

    get_fail();

    dfs(0);
    // for (int i = 1; i <= n; i ++ )
    //     cout << "i = " << i << " fail[i] = " << fail[i] << endl;


    // for (int i = 1; i <= n; i ++ )
    //     cout << "i = " << i << " last[i] = " << last[i] << endl;

    // for (int i = 1; i <= n; i ++ )
    //     cout << "i = " << i << " ed[i] = " << ed[i] << endl;

    f[0] = 1;
    int u = 0;
    for (int i = 0; s[i]; i ++ )
    {
        int v = s[i] - 'a';
        u = ch[u][v];

        int t = u;
        if (ed[u] == -1) t = last[t];
        while (t)
        {
            // cout << "u = " << u << endl;
            f[i + 1] = (f[i + 1] + 1LL * f[i - len[ed[t]] + 1] * val[ed[t]] % mod) % mod;
            t = last[t];
        }

        f[i + 1] = (f[i + 1] + f[i]) % mod;
    }

    for (int i = 1; i <= n; i ++ )
        printf("%d ", f[i]);
    puts("");

    return 0;
}

M - My University Is Better Than Yours(强连通分量、缩点、拓扑排序)

思路

由于一个学校强于另一个学校具有传递性,即若有学校A强于学校B,学校B强于学校C,则有学校A强于学校C,那么不难想到把弱的学校向强的学校连单向边,组成的图中,一个强连通分量内所有的学校都比连通块中的其他学校强,一个强连通分量中的所有学校都是等价的,那么缩点之后拓扑排序即可。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <vector>
#include <map>
#include <queue>
#include <cmath>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 1000005;

int tot;
int a[N];

struct edge
{
    int to, next;
}e[N << 1], e2[N << 1];
int head[N], cnt;
int head2[N], cnt2;
int dfn[N], low[N];
int stack[N], ins[N], c[N];
vector<int> scc[N];
int num, top, scc_cnt;
int in[N], etr[N];
int ans[N];

void add(int u, int v)
{
    e[++ cnt] = {v, head[u]};
    head[u] = cnt;
}

void add2(int u, int v)
{
    e2[++ cnt2] = {v, head2[u]};
    head2[u] = cnt2;
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++ num;
    stack[++ top] = u, ins[u] = 1;
    for (int i = head[u]; i; i = e[i].next)
    {
        int v = e[i].to;
        if (!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])
        {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (dfn[u] == low[u])
    {
        scc_cnt ++;int y;
        do 
        {
            y = stack[top --], ins[y] = 0;
            c[y] = scc_cnt, scc[scc_cnt].push_back(y);
        }while(u != y);
    }
}

int main()
{
    #ifdef ZYCMH
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
    #endif
    int n, m;
    scanf("%d%d", &n, &m);

    int t = 0;
    for (int i = 1; i <= m; i ++ )
        for (int j = 1; j <= n; j ++ )
        {
            scanf("%d", &a[++ t]);
            if (j != 1)
                add(a[t], a[t - 1]);
        }
    for (int i = 1; i <= n; i ++ )
        if (!dfn[i])
            tarjan(i);
    // printf("%d\n", scc_cnt);
    set<PII> s;
    for (int u = 1; u <= n; u ++ )
    {
        for (int i = head[u]; i; i = e[i].next)
        {
            int v = e[i].to;
            PII t = make_pair(c[u], c[v]);
            if (c[v] == c[u] || s.count(t)) continue;
            s.insert(t);
            add2(c[u], c[v]), in[c[v]] ++;
        }
    }

    queue<int> q;
    for (int i = 1; i <= scc_cnt; i ++ )
        if (!in[i]) q.push(i);
    
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        for (int i = head2[u]; i; i = e2[i].next)
        {
            int v = e2[i].to;
            in[v] --;
            etr[v] += scc[u].size() + etr[u];
            if (!in[v])
                q.push(v);
        }
    }

    for (int i = 1; i <= scc_cnt; i ++ )
    {
        int sz = scc[i].size();
        for (int j = 0; j < sz; j ++ )
        {
            int u = scc[i][j];
            ans[u] = sz - 1 + etr[i];
        }
    }

    for (int i = 1; i <= n; i ++ )
        printf("%d ", ans[i]);

    return 0;
}

N - Nine Is Greater Than Ten(签到)

思路

判断两个字符串大小即可。代码略。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值