Problem Palindrome

 

 这道题容斥想了半天。。。

拉丁字母共 52个 2^52 = 4e15, 不会爆long long,于是先给字母编号

inline int calc(char c)
{
    if (c < 'a') return c - 'A';
    return (c - 'a' + 26);
}

  

这样对于当前状态 ll res, res的二进制第i位(以下进制都是二进制)为1表示标号为(i-1)的字母出现了奇数次

这样我们就可以

1.把状态相等的 res,归为一类,这一类对答案的贡献就是 size * (size - 1) / 2

2.然而不光是同类相减出现回文串,只要两个状态相差一个字母的奇偶不同相减就可以产生一个答案,所以我们还要让每个集合与相邻的集合去对答案做贡献

3.对于不靠状态相减就能对答案做贡献的只有 res = 0 或 1

对于单个字母,都是靠相减剪出来的(比如第i(i != 1)个字母,是靠res(i) - res(i - 1)剪出来对答案做贡献的 条件2)(对于第一个字母,是由 条件3对答案做贡献的)

具体实现见代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int maxn = 2e5 + 5;

int n, f[maxn], siz[maxn], cnt[52];
char s[maxn];
map<ll, int> mp;
vector<ll> ve[49];

inline int calc(char c)
{
    if (c < 'a') return c - 'A';
    return (c - 'a' + 26);
}

void unit(int x, int y)
{
    f[y] = x;
    ++siz[x];
}

int main()
{
    scanf("%d%s", &n, s + 1);
    int flag = 0; ll res = 0, ans = 0;
    for (int i = 1; s[i]; ++i)
    {
        f[i] = i, siz[i] = 1;
        int id = calc(s[i]);
        if (++cnt[id] % 2) ++flag;
        else --flag;
        if (flag <= 1) ++ans; //条件3
        res ^= 1ll << id;
        if (!mp.count(res)) mp[res] = i, ve[flag].emplace_back(res);
        else  unit(mp[res], i);
    }
    if (!ve[0].empty())
    {
        int id = mp[ve[0][0]];
        ans += (ll)siz[id] * (siz[id] - 1) >> 1; //条件1
    }
    for (int i = 48; i; --i)
    {
        for (int j = 0; j < ve[i].size(); ++j)
        {
            res = ve[i][j]; int id = mp[res];
            ans += (ll)siz[id] * (siz[id] - 1) >> 1; //条件1
            for (int k = 0; k < 52; ++k)
                if (res & (1ll << k))
                {
                    ll r = res ^ (1ll << k);
                    if (mp.count(r) == 0) continue;
                    ans += (ll)siz[id] * siz[mp[r]];  //条件2
                }
        }
    }
    printf("%lld", ans);
    return 0;
}

  

原文地址:https://www.cnblogs.com/2aptx4869/p/12636904.html