暑假集训7月21日 manacher hash 并查集

题目链接:manacher、字符串hash、并查集
A:
思路:马拉车模板

#include <string>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
string str;
string s;
int p[2000100];
void getstr()
{
    str.clear();
    str.push_back('$');
    for (int i = 0; i < s.size(); i++)
    {
        str.push_back('#');
        str.push_back(s[i]);
    }
    str.push_back('#');
}
void manacher()
{
    int mx = 0, id;
    for (int i = 1; i < str.size(); i++)
    {
        if (mx > i) p[i] = min(p[2 * id - i], mx - i);
        else p[i] = 1;
        while (str[i + p[i]] == str[i - p[i]]) p[i]++;
        if (p[i] + i > mx) mx = p[i] + i, id = i;
    }
}
int main()
{
    int kase = 0;
    while (cin >> s)
    {
        if (s == "END") break;
        getstr();
        manacher();
        int ans = 1;
        for (int i = 1; i < str.size(); i++)
        {
            ans = max(ans, p[i]);
        }
        printf("Case %d: %d\n", ++kase, ans - 1);
    }
}

B:
思路:马拉车枚举断点,在马拉车的时候判断一下前i,后i是否是回文串,然后通过前缀和计算价值

#include <string.h>
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
string str;
string s;
int p[1000500];
int book[30];
int sum[1000500];
bool pre[1000500];
bool suf[1000500];
void getstr()
{
    str.clear();
    str.push_back('$');
    for (int i = 0; i < s.size(); i++)
    {
        str.push_back('#');
        str.push_back(s[i]);
    }
    str.push_back('#');
}
void manacher()
{
    int mx = 0, id;
    for (int i = 1; i < str.size(); i++)
    {
        if (mx > i) p[i] = min(p[2 * id - i], mx - i);
        else p[i] = 1;
        while (str[i + p[i]] == str[i - p[i]]) p[i]++;
        if (p[i] + i > mx) mx = p[i] + i, id = i;
        if (p[i] - i == 0) pre[p[i] - 1] = true;
        if (p[i] + i == str.size()) suf[p[i] - 1] = true;
    }

}
void getsum()
{
    for (int i = 1; i <= s.size(); i++)
    {
        sum[i] = sum[i - 1] + book[s[i-1] - 'a' + 1];
    }
}
//i-p[i]==0 则说明前面(2*i-1)/2=i-1个是回文串
//i+p[i]==0 同理 说明后i-1个是回文串
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        memset(pre, 0, sizeof(pre));
        memset(suf, 0, sizeof(suf));
        int ans = 0;
        for (int i = 1; i <= 26; i++) scanf("%d", &book[i]);
        cin >> s;
        getsum();
        getstr();
        manacher();
        for (int i = 1; i < s.size(); i++)
        {
            int tmp = 0;
            if (pre[i]) tmp += sum[i];
            if (suf[s.size() - i]) tmp += sum[s.size()] - sum[i];
            if (tmp > ans) ans = tmp;
        }
        printf("%d\n", ans);
    }
}

C:
思路:
kmp求最小循环节,由于我写的get_next从i=1,j=0开始
所以nextval[i]是前后缀长度+1,要减回去,算最小循环节需要L-nextval[L]+1,算出的最小循环节如果能整除总长,说明循环了若干下最小循环节,直接用总长除以最小循环节长度,否则直接输出1(表示整一个字符串循环了一次)
PS:字符串hash的解法之后有空再补上吧,这个用kmp求最小循环节解非常方便。

#include <iostream>
#include <string>
using namespace std;
char S[1001000];
int nextval[1001000];
int n;
void get_next()
{
    int i = 1, j = 0;
    while (i <= n)
    {
        if (j == 0 || S[i] == S[j]) nextval[++i] = ++j;
        else j = nextval[j];
    }
}
int main()
{
    while (~scanf("%s",&S[1]))
    {
        if (S[1] == '.') break;
        n = strlen(S+1);
        get_next();
        int len = n - nextval[n + 1] + 1;
        if (n % len == 0) printf("%d\n", n/len);
        else printf("1\n");
    }
}

D:
思路:字符串哈希,昨天一直被卡,细节处理有点难度,后来终于想出来了。
难点:
1.如何存放各个字符子串哈希值并且算出哈希值重复率最高的子串的长度和最右的一个
2.二分边界的确定(必须要确定正确,不然会WA)

#include <string>
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
#define ll long long
char s[40400];
ll ha[40400];
ll p = 131, mod = 1e9 + 7;
ll pnum[40400];
ll R[40400];
ll HASH[40400];
ll pos;
int n, m;//n为输入串的总长度,m为相同子串的出现次数
bool cmp(const int& a, const int& b)//进行哈希值的字符串排序
{
    return HASH[a] < HASH[b] || (HASH[a] == HASH[b] && a < b);
}
void get_pnum()//算出p的若干次幂,方便之后O(1)读取
{
    pnum[0] = 1;
    for (int i = 1; i < 40200; i++)
    {
        pnum[i] = (pnum[i - 1] * p) % mod;
    }
}
ll get_hash(int l, int r)//计算区间子串的哈希值
{
    return ((ha[r] - ha[l - 1] * pnum[r - l + 1]) % mod + mod) % mod;
}
bool solve(int mid)
{
    //i是截取的字符串左端,i+mid-1是截取的字符串右端
    ll ans = 0;
    pos = 0;
    for (int i = 1; i + mid - 1 <= n; i++)
    {
        ll re = get_hash(i, i + mid - 1);
        //cout << re << endl;
        R[i]=i;
        HASH[i] = re;
        //用R数组存放起始位置,HASH数组存放哈希值
    }
    sort(R+1, R + n - mid + 1+1, cmp);
    //一开始用map,超时了,用数组会快些
    //cout << "mid:" << mid << endl << "ans:" << ans << endl;
    for (int i = 1; i + mid - 1 <= n; i++)
    {
        if (i == 1 || HASH[R[i]] != HASH[R[i - 1]]) ans = 0;
        if (++ans >= m) pos = max(pos, R[i]);
    }
    return pos >= 1;
}
int main()
{
    get_pnum();
    while (scanf("%d", &m) != EOF)
    {
        if (m == 0) break;
        scanf("%s", &s[1]);
        n = strlen(s + 1);
        ll l = 1, r = n+1;
        //最少的可能是连长度为1的子串都不符合,最多的可能是整个串全符合要求
        //l=1对应的是答案0,r=n+1对应的是答案n,在后面都统一减一了
        ll mid;
        for (int i = 1; i <= n + 1; i++)
        {
            ha[i] = (ha[i - 1] * p + (s[i] - 'a' + 1)) % mod;
        }
        //计算整个字符串的哈希值
        while (l < r)//二分答案模板
        {
            mid = (l + r) >> 1;
            if (solve(mid)) l = mid + 1;
            //满足要求就上右区间寻找看有没有更大答案的满足要求
            else r = mid;
            //否则就上左区间去找小答案
        }
        //因为ans>=m就会让二分区间跑到右区间去,即l=mid+1
        //此时长度mid是可以的,而mid+1未确定,所以答案是之前的这个mid
        solve(l-1);
        //确定最后一次solve:return true时pos的位置
        if (l - 1 == 0) printf("none\n");
        else printf("%lld %lld\n", l - 1, pos-1);
        //由于题意要求字符串从0开始编号,故pos-1
    }
}

E:
思路:通过字符串的 hash ,求出任意一个长度为 L 的子串的 hash 值。枚举字符串起始位置,从 0 枚举到 L-1 。然后,在这个位置开始,每 L 个字符作为一块,首先将前 M 块插入到 map 中,同时记录不相同字符串的个数,如果不相同字符串的个数是 M ,则满足要求。然后,将这个区间向右移,删掉第 1 块,加入第 M+1 块,同样记录不相同字符串的个数。

#include <string>
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
char s[100100];
#define ll long long
int len;
ll hsh[100100], p[100100];
map<ll, int> mp;
int gethash(int l, int r)
{
    return hsh[r] - hsh[l - 1] * p[r - l + 1];
}
void init()
{
    for (int i = 1; i <= len; i++)
    {
        hsh[i] = hsh[i - 1] * 131 + s[i] - 'a' + 1;
        if (i == 1) p[i] = 131;
        else p[i] = p[i - 1] * 131;
    }
}
int main()
{
    int m, l;
    while (scanf("%d%d",&m,&l)!=EOF)
    {
        scanf("%s", s + 1);
        int num = 0;
        len = strlen(s + 1);
        init();
        for (int i = 1; i <= l && i <= len-m*l; i++)
        {
            mp.clear();
            for (int j = i; j < i + m * l; j += l)
            {
                ll x = gethash(j, j + l - 1);
                mp[x]++;
            }
            if (mp.size() == m) num++;
            for (int j = i + m * l; j + l - 1 <= len; j += l)
            {
                ll x = gethash(j, j + l - 1);
                mp[x]++;
                ll y = gethash(j - m * l, j - m * l + l - 1);
                mp[y]--;
                if (mp[y] == 0) mp.erase(y);
                if (mp.size() == m) num++;
            }
        }
        printf("%d\n", num);
    }
}

F:
思路:没有在一个集合里的不知道他们的关系,在一个集合里就看一下值是否相同,相同就是同伙,不然就不是同伙,合并的时候用向量来计算。
带权并查集向量计算法:
在这里插入图片描述此处很明显s=1,然后+mod再取余mod防止出现负数

#include <string.h>
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
const int MAXN = 100100;
int par[MAXN];
int dis[MAXN];
int n,m;
void init()
{
    memset(dis, 0, sizeof(dis));
    for (int i = 1; i <= n; i++) par[i] = i;
}
int find(int v)
{
    if (par[v] == v) return v;
    int fv = par[v];
    par[v] = find(par[v]);
    dis[v] += dis[fv];
    dis[v] %= 2;
    return par[v];
}
void unite(int u,int v)
{
    int fu = find(u);
    int fv = find(v);
    par[fu] = fv;
    dis[fu] = (2 - dis[u] + dis[v] + 1) % 2;
}
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d%d", &n, &m);
        init();
        while (m--)
        {
            char op;
            int u, v;
            scanf(" %c %d %d", &op, &u, &v);
            if (op == 'D')
            {
                if(find(u)!=find(v))
                    unite(u, v);
            }
            else
            {
                if (find(u)!=find(v))
                    printf("Not sure yet.\n");
                else if ((dis[u] == 0 && dis[v] == 1) || (dis[u] == 1 && dis[v] == 0))
                    printf("In different gangs.\n");
                else
                    printf("In the same gang.\n");
            }
        }
    }
}

G:
思路:带权并查集模拟,并附带记录一下集合的大小。

#include <string>
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
#define ll long long
const int MAXN = 30010;
int par[MAXN];
int _size[MAXN];
int dis[MAXN];
int n;
void init()
{
    for (int i = 1; i <= 30005; i++)
    {
        par[i] = i;
        _size[i] = 1;
    }
}
int find(int v)
{
    if (par[v] == v) return v;
    int fv = par[v];
    par[v]=find(par[v]);
    dis[v] += dis[fv];
    return par[v];
}
void unite(int u, int v)
{
    int fu = find(u);
    int fv = find(v);
    par[fv] = fu;
    dis[fv] = _size[fu];
    _size[fu] += _size[fv];
}
int main()
{
    init();
    int m;
    scanf("%d", &m);
    while (m--)
    {
        char op;
        int u, v;
        scanf(" %c", &op);
        if (op == 'M')
        {
            scanf("%d%d", &u, &v);
            unite(u, v);
        }
        else
        {
            scanf("%d", &u);
            int fu = find(u);
            printf("%d\n", _size[fu] - dis[u] - 1);
        }
    }
}

H:
思路:跟上一题类似,带权并查集,通过取余3判断关系。

#include <iostream>
#include <string.h>
using namespace std;
const int MAXN = 50050;
int par[MAXN];
int dis[MAXN];
int n, k;
void init()
{
    memset(par, 0, sizeof(par));
    memset(dis, 0, sizeof(dis));
    for (int i = 1; i <= n; i++) par[i] = i;
}
int find(int v)
{
    if (par[v] == v) return v;
    int fv = par[v];
    par[v] = find(par[v]);
    dis[v] += dis[fv];
    dis[v] %= 3;
    return par[v];
}
void unite(int x, int y, int d)
{
    int fx = find(x);
    int fy = find(y);
    par[fx] = fy;
    dis[fx] = (3 - dis[x] + dis[y] + (d - 1)) % 3;
}
int main()
{
    scanf("%d%d", &n, &k);
    int cnt = 0;
    init();
    while (k--)
    {
        int ob, x, y;
        scanf("%d%d%d", &ob, &x, &y);
        if (x > n || y > n) 
        {
            cnt++;
        }
        else if (ob == 2 && x == y)
        {
            cnt++;
        }
        else
        {
            int fx = find(x);
            int fy = find(y);
            if (fx == fy) {
                if (ob == 1 && dis[x] != dis[y]) cnt++;
                else if (ob == 2 && (dis[x] + 2) % 3 != dis[y]) cnt++;
            }
            else
                unite(x, y, ob);
        }
    }
    printf("%d\n", cnt);
}

I:
思路:带权并查集或者2-set
不会做,留个坑,以后来补。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值