【题解】Atcoder ARC#94 F-Normalization

本文深入探讨了一道复杂算法题的解决思路,揭示了序列变换的神秘性质。通过对序列进行模意义下的求和分析,发现了转换前后序列的和保持不变的特性,并提出了解决方案的动态规划算法。文章详细介绍了状态定义、转移过程及特殊情况的处理,为理解序列变换提供了一种新颖而深刻的视角。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  再次膜拜此强题!神级性质之不可能发现系列收藏++;首先,对于长度<=3的情况,我们采取爆搜答案(代码当中是打表)。对于长度>=4的情况,则有如下几条玄妙的性质:

  首先我们将 a, b, c 三个字母看做 0, 1, 2。发现(不知道怎么发现的)当我们做出一次变换之后,数列的和在模意义下是不改变的。(*启示:很多关系好像都和取模之后的某些东西有关,例如食物链,此题,and so on)。

  那么:当一个序列 T 可以由 S 转化过来时,T必须满足如下几条性质:

  1.T的各位字母之和与S的各位字母之和在 %3 的意义下相等。

  2.T中必须存在有两个相邻的相同字母(如果不是,不可能是变换后的结果)

  当以上两条性质都不满足时,还剩下一种情况,即 S = T。

  如何证明是对的?题解如是说:当 n = 4 时,我们可以用打表来验证此性质。当 n > 4 时,我们可以让第一个字母通过变换变成一样的,然后就变成了两个 n - 1 的序列。这样递归下去,当递归到 4 时,即可得证。看起来好像很对的样子,然而怎么证明一定可以把第一个字母变成一样的呢?我并不会……

  建立状态 f[i][j][k][p] 表示 dp 到第 i 位,前面的和在模意义下为 j ,最后一位是 k ,是否已经有两个相邻的相同字母的方案数。然后枚举一下,转移就可以惹……

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 200050
#define mod 998244353
int n, ans, f[maxn][3][3][2], sum;
char a[maxn];

int read()
{
    int x = 0, k = 1;
    char c; c = getchar();
    while(c < '0' || c > '9') { if(c == '-') k = -1; }
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * k;
}

bool Check()
{
    for(int i = 2; i <= n; i ++) 
        if(a[i] != a[i - 1]) return 0;
    return 1;
}

bool Check2()
{
    for(int i = 2; i <= n; i ++) 
        if(a[i] == a[i - 1]) return 0;
    return 1;
}

void Up(int &x, int y) { x = (x + y) % mod; }
void DP(int i, int j, int k, int p)
{
    for(int q = 0; q <= 2; q ++)
        Up(f[i + 1][q][(q + k) % 3][p | (j == q)], f[i][j][k][p]);
}

signed main()
{
    scanf("%s", a + 1); n = strlen(a + 1);
    if(Check()) { puts("1"); return 0; }
    if(n <= 3)
    {
        if(n == 2) puts("2");
        else 
        {
            if(a[1] != a[2] && a[1] != a[3] && a[2] != a[3]) puts("3");
            else if(a[1] == a[3]) puts("7");
            else puts("6");
        }
        return 0;
    }
    for(int i = 1; i <= n; i ++) sum = (sum + a[i] - 'a') % 3;
    for(int i = 0; i <= 2; i ++) f[1][i][i][0] = 1;
    for(int i = 1; i <= n - 1; i ++)
        for(int j = 0; j <= 2; j ++)
            for(int k = 0; k <= 2; k ++)
                for(int p = 0; p <= 1; p ++)
                    DP(i, j, k, p);
    for(int i = 0; i <= 2; i ++) Up(ans, f[n][i][sum][1]);
    Up(ans, Check2());
    printf("%lld\n", ans);
    return 0;
}

 

转载于:https://www.cnblogs.com/twilight-sx/p/9732275.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值