Codeforces Round 855 (Div. 3) F. Dasha and Nightmares(题解 + 详细思考过程讲解)

F. Dasha and Nightmares(二进制)

链接:F. Dasha and Nightmares

题目最重要的破题点思路 即第4点 由某银牌佬提供,唉膜拜大佬。

题意

给定 n n n 个字符串, ( 1 < = n < = 2 ∗ 1 0 5 ) (1 <=n<=2 * 10^5) 1<=n<=2105 字符串长度之和不超过 5 ∗ 1 0 6 5 *10^6 5106
求字符串对 s i , s j si,sj si,sj个数,要满足以下条件
条件1.拼接后恰好有 25 25 25 种字母。
条件2.拼接后每个字母个数为奇数。

题解 + 思考过程

1. 最开始想到用二进制,将每个字符串转化为一个长度为 26 26 26 的二进制数,字符 c h ch ch 的个数, 奇数 = 1 =1 =1 ,偶数 = 0 =0 =0
例如 s t r = " a " str = "a" str="a" 二进制串 = 1000 … … = 1000…… =1000…… s t r = " a b " = 1100 … … str = "ab" = 1100…… str="ab"=1100…… s t r = " a a b " = 0100 … … str = "aab" = 0100…… str="aab"=0100……
对于条件1:可以想到对于每个字符串枚举哪一个字符舍去。
对于条件2:用位运算直接去找到合法的,异或全 1 1 1 二进制数再减去应该舍去的字符位上的1。

2. 但是必须只有 25 25 25 个,但是偶数对应 0 0 0, 那么不知道该字符是否有,因为没有该字符为 0 0 0 也对应 0 0 0
例如枚举字符 a a a 没有, 但字符串 s t r = " a a " str = "aa" str="aa" ,那么该字符串代表的二进制为全 0 0 0,就可以和 字符串 " b ∼ z " "b\sim z" "bz" 拼接 但拼接后有 26 26 26个字母,显然不合法。

3. 于是想到用三进制来表示, 0 = 0 0 = 0 0=0, 奇数 = 1 = 1 =1 大于 0 0 0 的偶数 = 2 = 2 =2, 用字典树去计算
但是由于 1 1 1 对应 0 0 0 2 2 2 (字符串该位上为 1 1 1,那么该位上为 0 0 0 2 2 2 的都可以与之拼接)
这样递归时间复杂度可能达到 2 2 2 的幂次方级别。不可行。

4. 最后想到 枚举的不存在的那个字符,只有两个字符串都没有该字符才能拼接后满足。
所以我们可以每次遍历不存在 字符 c h ch ch 的字符串集合,这样就不用考虑该位的 0 0 0 是大于 0 0 0 的偶数还是 0 0 0 的问题了。
因为除了我们枚举的该位必须不存在的其他字符 都必须为奇数,所以至少存在,对应拼接的是 0 0 0 还是大于 0 0 0 的偶数就无所谓了。
可以直接采取1.做法,不过需要在每次计算后清除贡献。(如果不清除,影响可以参考2.)。

代码

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 2e5 + 10, M = 1 << 26;
const int K = (1 << 26) - 1;//长度为26的全1二进制数
string s[N];
int t[N], val[M];//t[i]:转化后二进制数 val[i]:计数
vector<int>g[26];//g[i]:没有字符i的字符串的下标
int main()
{
    ios::sync_with_stdio(false);
    cout.tie(NULL);

    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++){
        cin >> s[i];
        int len = s[i].length();
        int vis[30] = {0};
        for(int j = 0; j < len; j ++){
            vis[s[i][j] - 'a'] ++;
        }
        int num = 0;
        for(int j = 0; j < 26; j ++){
            if(vis[j] & 1) num += (1 << j);
            if(!vis[j]) g[j].push_back(i);
        }
        t[i] = num;
    }
    
    ll ans = 0;
    for(int i = 0; i < 26; i ++){//枚举没有i字符的字符串,只有都没有某个字符的字符串才能匹配
        for(int id : g[i]){
            val[t[id]] ++;
            int v = t[id] ^ K ^ (1 << i);//能与si拼接的字符串所代表的二进制数
            ans += val[v];
        }
        for(int id : g[i]) val[t[id]] --;//清除贡献
    }
    cout << ans;
    return 0;
}
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值