[HNOI2014]抄卡组------hash

原题
大佬链接:https://blog.csdn.net/clover_hxy/article/details/72779023
这一题主要是说,如果一个字符串中含有*的话,那么它只要比左右两头就行了,如果存在一个字符串没有,那么就需要把所有的字符串与它比较了。
自己写的kmp匹配疯狂LTE,看了下大佬用hash比两端过,最后就是要注意的是数组和p的大小需要开大一点, 否则卡数据(当时到处测bug测晕了,才发现是p和MX开小了)。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MX 10500000
#define p 20001001019
#define ull unsigned long long
using namespace std;
ull mi[MX],has[MX];
char ch[MX],s[MX];
struct node{
    int l,r,x,y,len,cnt;
}a[MX];
bool cmp1(node &a,node &b){
    return a.x<b.x;
}
bool cmp2(node &a,node &b){
    return a.y<b.y;
}
int main()
{
  //  freopen("input.txt","r",stdin);
    int T,n,m,mx,pos;
    scanf("%d",&T);
    mi[0]=1;
    for( int i=1 ; i<=MX-1 ; i++ )
        mi[i]=mi[i-1]*p;
    while( T-- ){
        m=0,mx=MX;
        scanf("%d",&n);
        for( int i=1 ; i<=n ; i++ ){
            scanf("%s",s+1);
            int len=strlen(s+1),l=len+1,r=0;
            for( int j=1 ; j<=len ; j++ ){
                ch[m+j]=s[j];
                if( j==1 ) has[m+j]=mi[j]*s[j];
                else has[m+j]=has[m+j-1]+mi[j]*s[j];
                if( s[j]=='*' ){
                    l=min(l,j-1),r=max(r,j+1);
                    a[i].cnt++;
                }
            }
            a[i].l=m+1,a[i].r=m+len,a[i].len=len;  // l,r是存第i个字符串在ch中的范围
            if( l!=len+1 )   // x与y是存第i个字符串的左右两边直到遇见第一个*的长度
                a[i].x=l,a[i].y=len+1-r;
            else{
                a[i].x=a[i].y=len;
                mx=min(mx,len);
                if( mx==len )
                    pos=i;   // 若该字符串中没有*,则它是模板串,用pos存最小模板串的标号
            } 
            m+=len;
        }
        int pd=1;
        for( int i=1 ; i<=n ; i++ ){
            if( a[i].len-a[i].cnt>mx ){   // 任何一个字符串都不可以比最小模板串有更多的字符
                pd=0;
                break;
            }
        }
        if( !pd ){
            printf("N\n");
            continue;
        }
        if( mx!=MX ){   // 若有模板串,则把所有的字符串和最小模板串匹配
            for( int i=1 ; i<=n ; i++ ){
                int t=a[i].l-1,l=a[pos].l;
                for( int j=1 ; j<=a[i].len ; j++ ){
                    if( ch[j+t]=='*' )
                        continue;
                    while( ch[t+j]!=ch[l] && l<=a[pos].r ) l++;
                    if( ch[t+j]!=ch[l] ){
                        pd=0;
                        break;
                    }
                    l++;
                }
                if( !pd )
                    break;
            }
        }
        if( !pd ){
            printf("N\n");
            continue;
        }   // 记住,以上所有的只是确定了所有的串均可以在模板串里出现(如果有模板串的话),并不能保证其它的两两匹配
        ull x,y;
        sort(a+1,a+1+n,cmp1);  //  开始匹配左边的,从小到大匹配,左右方便很多(不愧是大佬)
        for( int i=1 ; i<n ; i++ ){
            if( a[i].x==0 )
                continue;
            x=has[a[i].l+a[i].x-1];
            y=has[a[i+1].l+a[i].x-1];
            if( x!=y ){
                pd=0;
                break;
            }
        }
        if( !pd ){
            printf("N\n");
            continue;
        }
        sort(a+1,a+1+n,cmp2);   // 匹配右边的
        for( int i=1 ; i<n ; i++ ){
            if( a[i].y==0 )
                continue;
            if( a[i].len==a[i].y )  x=has[a[i].r];   // 要进行判断我们要的最右边的字符串,是真个字符串的部分或者就是整个字符串
            else x=has[a[i].r]-has[a[i].r-a[i].y];  
            if( a[i+1].len==a[i].y ) y=has[a[i+1].r];
            else y=has[a[i+1].r]-has[a[i+1].r-a[i].y];  
            int len=a[i+1].len-a[i].len;  // 不要忘记hash中的求部分hash要乘差间
            if( len>=0 )
                x*=mi[len];
            else
                y*=mi[-len];
            if( x!=y ){
                pd=0;
                break;
            }
        }
        if( !pd )
            printf("N\n");
        else
            printf("Y\n");
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值