#P3585. 第1题-无害化处理
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 139
            Accepted: 23
            Difficulty: 6
            
          
          
          
                       所属公司 : 
                              京东
                                
            
                        
              时间 :2025年9月6日
                              
                      
          
 
- 
                        算法标签>动态规划          
 
第1题-无害化处理
解题思路
关键观察
- 只关心奇偶性(每种字母出现次数 mod 2)。
 - 令 
mask[i]表示前缀s[1..i]的 26 位奇偶掩码(第 k 位为该字母出现次数的奇偶)。 - 子串 
s[l..i]的掩码为mask[l-1] ^ mask[i]。 - 若该子串“无害”,则 
popcount(mask[l-1] ^ mask[i]) ∈ {0,1}(全 0 或恰好一个 1)。 
状态设计
设 dp[i] = 把前缀 s[1..i] 切成“无害”若干段的最少段数。
答案为 dp[n],dp[0]=0。
同时维护数组 best[mask]:到目前为止前缀掩码为 mask 时的最小 dp 值。
- 初始 
best[0]=0(空前缀),其余为 +∞。 
转移
处理到位置 i 时已知 mask[i]。最后一段从某个 j+1 切到 i:
- 若 
mask[j] == mask[i](该段掩码为 0),候选值best[mask[i]] + 1。 - 若 
mask[j] == mask[i] ^ (1<<k)(该段掩码只有一位为 1),候选值best[mask[i] ^ (1<<k)] + 1,遍历 k=0..25。 
因此
dp[i] = 1 + min( best[mask[i]],
                 min_{k=0..25} best[mask[i] ^ (1<<k)] )
随后用 dp[i] 更新 best[mask[i]] = min(best[mask[i]], dp[i])。
实现细节
- 字母表固定 26,故 
mask用 32 位整型即可。 - 只需线性扫描一次,外层 n,内层常数 26,整体 O(n⋅26)。
 - 空间:
best大小为 226 看似巨大,但我们只访问到出现过的掩码。实现中用哈希表(Pythondict/C++unordered_map/JavaHashMap)存储best,初始放入(0 -> 0)即可。 
复杂度
- 时间复杂度:O(n×26)。
 - 空间复杂度:平均 O(U),其中 U 为出现过的不同前缀掩码个数(≤n+1)。
 
代码
Python
# 读取输入,计算最少段数
import sys
s = sys.stdin.readline().strip()
n = len(s)
# best[mask] = 前缀掩码为 mask 的最小 dp 值
best = {0: 0}
dp = [0] * (n + 1)
mask = 0
for i, ch in enumerate(s, 1):
    # 更新当前位置前缀掩码(翻转对应字母的奇偶位)
    mask ^= 1 << (ord(ch) - 97)
    # 计算 dp[i]
    ans = best.get(mask, 10**9)  # 子串掩码为 0 的情况
    # 子串掩码只有一位为 1 的情况
    for k in range(26):
        ans = min(ans, best.get(mask ^ (1 << k), 10**9))
    dp[i] = ans + 1
    # 用 dp[i] 更新 best
    if dp[i] < best.get(mask, 10**9):
        best[mask] = dp[i]
print(dp[n])
Java
import java.io.*;
import java.util.*;
public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = br.readLine();
        int n = s.length();
        // best: 掩码 -> 最小 dp
        HashMap<Integer, Integer> best = new HashMap<>();
        best.put(0, 0); // 空前缀
        int[] dp = new int[n + 1];
        int mask = 0;
        for (int i = 1; i <= n; i++) {
            // 更新掩码:翻转当前字母位
            int c = s.charAt(i - 1) - 'a';
            mask ^= (1 << c);
            // 候选 1:子串掩码为 0
            int INF = 1_000_000_000;
            int cur = best.getOrDefault(mask, INF);
            // 候选 2:子串掩码仅 1 位为 1
            for (int k = 0; k < 26; k++) {
                int t = mask ^ (1 << k);
                cur = Math.min(cur, best.getOrDefault(t, INF));
            }
            dp[i] = cur + 1;
            // 更新 best
            best.put(mask, Math.min(best.getOrDefault(mask, INF), dp[i]));
        }
        System.out.println(dp[n]);
    }
}
C++
#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    string s;
    if (!getline(cin, s)) return 0;
    int n = (int)s.size();
    unordered_map<int,int> best;
    best.reserve(n * 2 + 5);
    best[0] = 0; // 空前缀
    const int INF = 1e9;
    vector<int> dp(n + 1, 0);
    int mask = 0;
    for (int i = 1; i <= n; ++i) {
        // 翻转当前字母位
        mask ^= 1 << (s[i-1] - 'a');
        // 候选:掩码为 0
        int cur = best.count(mask) ? best[mask] : INF;
        // 候选:掩码仅 1 位为 1
        for (int k = 0; k < 26; ++k) {
            int t = mask ^ (1 << k);
            auto it = best.find(t);
            if (it != best.end()) cur = min(cur, it->second);
        }
        dp[i] = cur + 1;
        // 更新 best
        auto it = best.find(mask);
        if (it == best.end()) best[mask] = dp[i];
        else it->second = min(it->second, dp[i]);
    }
    cout << dp[n] << "\n";
    return 0;
}
        题目内容
小明是一个魔法项链回收商,他的工作是将这些魔法项链进行无害化处理,魔法项链由若干颗魔法石组成,可以视作一个字符串,不同种类的魔法石可以用小写字母 a ~ z 来进行表示。小明可以将魔法项链切割为若于段(也可以不切割),每一段中相同种类的魔法石可以进行能量抵消,如果最终宝石全部抵消或者只剩一颗,那么就算完成了无害化处理。
例如:
项链段 zz 可以完成无害化。
项链段 aba 可以完成无害化,aa 可以抵消,最后只一颗 b ;
项链段 cccg 不可以完成,因为 cc 抵消后还剩下 cg ,不止一颗。
今天小明又回收到了一条魔法项链,他想知道最少需要将魔法项链切割成多少子段,可以使所有子段都可以完成无害化处理。
输入描述
一个字符串,只包含小写字母,用来表示小明回收的魔法项链,长度不超过 100000
输出描述
一个整数,表示最小需要切割为多少段,可以使所有项链子段都能完成无害化处理。
样例1
输入
xyz
输出
3
样例2
输入
abcdabcd
输出
1
说明
无需切割,最初的项链串本身就可以完成无害化处理,此时记为 1 段。
样例3
输入
rrzbbaau
输出
2
说明
可以切割为 rrz,bbaau 两个子串。