Educational Codeforces Round 132 (Rated for Div. 2) C,D+AC自动机

D. Rorororobot
题意:n*m的矩阵,从下到上是1到n行,从左到右是1到m列,每列从下到上有连续x个单元格损毁,如果给出起点和终点坐标,并且规定每次只能向同一个方向移动k次,是否能够从起点到达终点。

思路:走到最上层后往终点方向走,如果被挡住了则不能,如果两点横纵坐标的分别的差有一个不能整除k也不行。所以只要看看区间最值和整除关系就行了

#include <iostream>
#include <cmath>
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)/2)
using namespace std;
const int N = 2e5+100;

int tree[N<<2];
int a[N];

void up(int p)
{
    tree[p]=max(tree[ls],tree[rs]);
}
void bulid(int l,int r,int p)
{
    if(l==r)
    {
        tree[p]=a[l];
        return;
    }
    bulid(l,mid,ls);
    bulid(mid+1,r,rs);
    up(p);
}
int query(int nl,int nr,int l,int r,int p)
{
    if(nl<=l&&r<=nr)
    {
        return tree[p];
    }
    int res=-1;
    if(nl<=mid)
    {
        res=max(res,query(nl,nr,l,mid,ls));
    }
    if(nr>mid)
    {
        res=max(res,query(nl,nr,mid+1,r,rs));
    }
    return res;
}
int main()
{
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(0);
    int n,m,q;
    cin>>n>>m;
    for(int i=1;i<=m;i++)
        cin>>a[i];
    bulid(1,m,1);
    cin>>q;
    for(int i=1,xs,ys,xf,yf,k;i<=q;i++)
    {
        cin>>xs>>ys>>xf>>yf>>k;
        if(abs(xs-xf)%k||abs(ys-yf)%k)
        {
            cout<<"NO"<<endl;
            continue;
        }
        if(ys>yf)
            swap(ys,yf);
        int res=query(ys,yf,1,m,1);
        xs=n-((n-xs)%k);
        if(res>=xs)
        {
            cout<<"NO"<<endl;
        }
        else
        {
            cout<<"YES"<<endl;
        }

    }
    return 0;
}

C. Recover an RBS
题意:给定一个合法的括号序列,有些字符用’?'代替了,是否能让所有的‘?’唯一表示呢?
括号序列的(越靠前越容易合法,至少有一种合法的填法,就是先把所有的(填掉,然后再填).如果有合法的序列,这种一定合法
然后根据这个结论,我们把第一个‘)'和最后一个‘('互换,如果还是合法的那么输出no,否则输出yes。

思路:

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
typedef long long LL;
const int maxn = 2e5 + 5, INF = 0x3f3f3f3f;
char s[maxn];

int main(){

    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(0);

    int T;
    cin >> T;
    while(T--){
        cin >> s + 1;
        int n = strlen(s + 1);
        int cnt[2] = {0};
        for(int i = 1; i <= n; i++){
            if (s[i] == '(') cnt[0]++;
            else if (s[i] == ')') cnt[1]++;
        }
        if (cnt[0] == n / 2 || cnt[1] == n / 2){
            cout << "YES" << '\n';
            continue;
        }
        int left = n / 2 - cnt[0];
        int maxl = 0, minr = INF;
        for(int i = 1; i <= n; i++){
            if (s[i] == '?'){
                if (left){
                    left--, s[i] = '(';
                    maxl = max(maxl, i);
                }
                else{
                    s[i] = ')';
                    minr = min(minr, i);
                }
            }
        }
        swap(s[maxl], s[minr]);
        bool success = 0;
        int sum = 0;
        for(int i = 1; i <= n; i++){
            sum += (s[i] == '(' ? 1 : -1);
            if (sum < 0){
                success = 1;
                break;
            }
        }
        cout << (success ? "YES" : "NO") << '\n';
    }

}

C2. k-LCM (hard version)
数论题,,考察了LCM的性质。

#include <iostream>

using namespace std;

int main()
{
    int t;
    for(cin>>t;t;t--)
    {
        int n,k;
        cin>>n>>k;
        for(int i=1;i<=k-3;i++)
            cout<<1<<" ";
        n-=(k-3);
        if(n&1)
            cout<<1<<" "<<n/2<<" "<<n/2<<endl;
        else
        {
            n>>=1;
            if(n&1)
                cout<<2<<" "<<n-1<<" "<<n-1<<endl;
            else
                cout<<n/2<<" "<<n/2<<" "<<n<<endl;
        }
    }
    return 0;
}

AC自动机

前置知识:字典树(精通),KMP(了解)

用处:
主要用于多模式匹配,说白了就是,给你一个字符串,我想看看这个字符串在很多很多字符串中能够匹配到的个数。这时,如果用kmp暴力求解,我们势必要求多个字符串你的next数组,复杂度爆炸。

这个时候我们就要用到ACZ自动机 ACAM

既然是要用AC自动机,我们就要先构造一个AC自动机。构造分两大部分:字典树构造+失配指针构造

1.将用于匹配的多个母串做成一棵字典树,默认学会字典树再来学的,所以不做赘述

2.fail指针,fali指针构造的目的就是为了快速找到下一个有可能匹配的节点,所以fail指针指向的总是这样一个节点此节点所代表的串能够包含目前节点所代表的串,并且其能够匹配剩余的后缀(至少是下一个字母)
我们用BFS的方式求解
我们先处理出以26个英文字母为开端的串的fail,显然,这些fail都应该指向根节点。将其统统压入队列
然后对于每一个队列指向的节点,队列里每一个数均代表目前节点,然后按照==目前节点的子节点的fail指针指向的节点就是目前节点fail指针指向节点的子节点。==来构造整个fail

AC自动机

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
struct tree//字典树
{
    int fail;//失配指针
    int vis[26];
    int end;
};
tree AC[1000000];//Trie树,大小和取决于cnt的上限
int cnt=0;//Trie的指针
void add(string s)
{
    int l=s.length();
    int now=0;//字典树的当前指针
    for(int i=0;i<l;++i)//构造Trie树
    {
        if(AC[now].vis[s[i]-'a']==0)//Trie树没有这个子节点
        {
            AC[now].vis[s[i]-'a']=++cnt;
        }
        now=AC[now].vis[s[i]-'a'];//向下构造
    }
    AC[now].end+=1;//标记单词结尾
}
void Get_fail()//构造fail指针
{
    queue<int> Q;//队列
    for(int i=0;i<26;++i)//第二层的fail指针提前处理一下
    {
        if(AC[0].vis[i]!=0)
        {
            AC[AC[0].vis[i]].fail=0;//指向根节点
            Q.push(AC[0].vis[i]);//压入队列
        }
    }
    while(!Q.empty())//BFS求fail指针
    {
        int u=Q.front();
        Q.pop();
        for(int i=0;i<26;++i)//枚举所有子节点
        {
            if(AC[u].vis[i]!=0)//存在这个子节点
            {
                AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i];//目前节点的子节点的fail指针指向的节点就是目前节点fail指针指向节点的子节点。
           
                Q.push(AC[u].vis[i]);
            }
            else//不存在这个子节点
            AC[u].vis[i]=AC[AC[u].fail].vis[i];
            //当前节点的这个子节点指向当
            //前节点fail指针的这个子节点
        }
    }
}
int AC_Query(string s)//AC自动机匹配
{
    int l=s.length();
    int now=0,ans=0;
    for(int i=0;i<l;++i)
    {
        now=AC[now].vis[s[i]-'a'];//向下一层
        for(int t=now;t&&AC[t].end!=-1;t=AC[t].fail)//循环求解
        {
            ans+=AC[t].end;
            AC[t].end=-1;
        }
    }
    return ans;
}
int main()
{
     int n;
     string s;
     cin>>n;
     for(int i=1;i<=n;++i)
     {
        cin>>s;
        add(s);
     }
     AC[0].fail=0;//根节点不存失配指针
     Get_fail();//求出失配指针
     cin>>s;//文本串
     cout<<AC_Query(s)<<endl;
     return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值