Atcoder CODE FESTIVAL 2017 qual C D

题目链接

题意

给定一个字符串(长度(leq 2e5)),将其划分成尽量少的段,使得每段内重新排列后可以成为一个回文串。

题解

分析

每段内重新排列后是一个回文串( ightarrow)该段内至多只有一个字符出现过奇数次

考虑哈希到一个(26)位的(01)串,出现过奇数次的元素位置上的值为(1),否则为(0).

于是可以继续往下推:( ightarrow)该段的哈希值为(0)或者是(2)的幂次。

转化

于是问题转化为:将字符串切割成尽量少的若干段,使得每段的哈希值均满足为(0)或者是(2)的幂次

预处理异或前缀和可以(O(1))算得每段的哈希值。

问题至此一个很显然的做法就是(O(n^2))(dp),然而显然是无法承受的。

对dp形式的思考

我们来考虑一下原先想写的(dp)的形式:

[dp[i]=min_{j<i,ok(j+1,i)}{dp[j]}+1 ]

这里的(ok(j+1,i))指的是(s[j+1..i])一段的哈希值满足要求。

仔细思考一下,这里有两个约束条件,

  1. (j<i)
  2. (ok(j+1,i))

我们常规的思路是拿第一个条件去循环,然后判断第二个条件是否被满足。
然而在这里,对于给定的(i),有(i-1)(j)满足第一个条件,而只有(leq 27)(j)满足第二个条件。
更优的考虑是拿第二个条件去循环。

O(n)-dp

什么叫做拿第二个条件去循环呢?

意思是我们直接找到那个(h[j]),它和(h[i])异或起来的值满足条件。

由异或的性质,(h[i]oplus h[j]=powerleftrightarrow h[i]oplus power=h[j])

所以,我们应该存的信息有:

  1. (dp[x])代表哈希值为(x)的最少切割次数
  2. (opt[i])代表([1..i])段最少的切割次数

则显然转移有$$opt[i]=min{dp[h[i]oplus mask]}+1$$$$dp[x]=min(dp[x],opt[i])$$

官方题解

Code

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define maxn 200010
using namespace std;
typedef long long LL;
char s[maxn];
int opt[maxn], h[maxn];
int main() {
    scanf("%s", s+1);
    int len = strlen(s+1);
    for (int i = 1; i <= len; ++i) h[i] = h[i-1] ^ (1<<(s[i]-'a'));
    map<int, int> dp;
    for (int i = 1; i <= len; ++i) {
        opt[i] = (!h[i] || dp[h[i]]) ? dp[h[i]] : inf;
        int mask = 1;
        for (int j = 0; j < 26; ++j) {
            if ((h[i]^mask) == 0 || dp[h[i]^mask]) opt[i] = min(opt[i], dp[h[i]^mask]);
            mask <<= 1;
        }
        ++opt[i];
        if (!dp[h[i]] && h[i]) dp[h[i]] = opt[i];
        else dp[h[i]] = min(dp[h[i]], opt[i]);
    }
    printf("%d
", opt[len]);
    return 0;
}

原文地址:https://www.cnblogs.com/kkkkahlua/p/7722578.html