[ZJOI2008]无序运动MOVEMENT------ac自动机+计算几何

原题
大佬源码
整个就一ac自动机,不过把建树文本换成了许多的Q点列,把搜索文本换成了P点列,把平常所熟悉的字符串换成了一个一个点,可以用map函数存。易知,任何一个节点,就是它整个边与上一条边的长度的比值,还要此3个点所拼成的角度,这两个关键量可以用结构体c存。
为了确保精度,边长比例可以用两边平方的比例。而角度就比较麻烦了,当时就卡在这里(晕),后来在网上查到了一种很方便的方法,就是两向量边的叉积和点积的比例可以表示角度。
还有,关于翻转,可以在原来的线上,将y反转后,重新造一条它的翻转线出来。最后还要记录一下,q如果是直线的情况,因为我们是把p翻转,那么就会求2次直线Q的情况,要除以2。
最后,一定要仔细,一定要仔细, 一定要仔细。

#include<bits/stdc++.h>
using namespace std;
const int MX=2e5+9;
int n,m,k,tot=0,fail[MX],last[MX],cnt[MX],ans[MX],tp[MX];
struct point{
    int x,y;
    point operator - ( const point &rhs){ return point{x-rhs.x,y-rhs.y};}
    int operator * (const point &rhs){ return x*rhs.y-y*rhs.x;}
    int operator ^ (const point &rhs){ return x*rhs.x+y*rhs.y;}
}po[MX];

struct node{
    int a,b,c,d;
    bool operator < (const node &rhs) const {
        if( a!=rhs.a ) return a<rhs.a;
        if( b!=rhs.b ) return b<rhs.b;
        if( c!=rhs.c ) return c<rhs.c;
        return d<rhs.d;
    }
}s[MX];

node change(point a,point b,point c){   // 存两边长的比值还有角度,一定要记得gcd!
    node an;
    an.a=(b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y);
    an.b=(c.x-b.x)*(c.x-b.x)+(c.y-b.y)*(c.y-b.y);
    an.c=(b-a)*(c-b),an.d=(b-a)^(c-b);
    int t=__gcd(an.a,an.b);
    if( t!=0 ) an.a/=t,an.b/=t;
    t=__gcd(abs(an.c),abs(an.d));
    if( t!=0 ) an.c/=t,an.d/=t;
    return an;
}

map<node,int> nex[MX];
vector<int> ed[MX];
#define iter map<node,int>::iterator

void ins(int len,int id){
    int now=0;
    for( int i=1 ; i<=len ; i++ ){
        iter it=nex[now].find(s[i]);
        if( it==nex[now].end() )   //看看s[i]是否存在,不在的话就加在now的子节点中
            now=nex[now][s[i]]=++tot;
        else
            now=it->second;
    }
    ed[now].push_back(id);
}

void build(){
    queue<int> que;
    for( iter it=nex[0].begin() ; it!=nex[0].end() ; it++ )
        que.push(it->second);
    while( !que.empty() ){
        int now=que.front();
        que.pop();
        for( iter it=nex[now].begin() ; it!=nex[now].end() ; it++ ){
            node a=it->first;
            int b=it->second,c=fail[now];
            while( c && nex[c].find(a)==nex[c].end() ) c=fail[c];   // 这里找的是个拥有子节点a的now,否则一直找到原节点0,
            if( nex[c].find(a)!=nex[c].end() )
                c=nex[c][a];  // 因为这是点,不是字符串,所以要这样赋值
            fail[b]=c;
            last[b]=ed[c].empty()?last[c]:c;  // last寻找上一个有ed的节点
            que.push(b);
        }
    }
}

void solve(){
    int now=0;
    for( int i=1 ; i<=n-2 ; i++ ){
        int x=now;
        while( x && nex[x].find(s[i])==nex[x].end() )   // 寻找下一个有s[i]的now节点
            x=fail[x];
        if( nex[x].find(s[i])!=nex[x].end() )   
            x=nex[x][s[i]];
        now=x;
        for( ; x ; x=last[x] )
            cnt[x]++;   
    }
}

int main()
{
    freopen("input.txt","r",stdin);
    scanf("%d %d",&n,&m);
    for( int i=1 ; i<=m ; i++ ){
        int falg=1;
        scanf("%d",&k);
        for( int j=1 ; j<=k ; j++ )
            scanf("%d %d",&po[j].x,&po[j].y);
        if( k<=2 ){
            ans[i]=n-1;  // 如果就2个点连成的一条线,答案一定是n-1,
            continue;
        }
        for( int j=2 ; j<k ; j++ ){
            s[j-1]=change(po[j-1],po[j],po[j+1]);
            if( s[j-1].c )  // 判断是否是直线
                falg=0;
        }
        if( falg )
            tp[i]=1; // 这个tp是存模板q是否hi是一条直线,如果是一条直线,那么翻转或不翻转就没有意义了
        ins(k-2,i);  //,但是我们将把p链翻转再求一次,所以如果q是直线,那么最终答案要/2
    }
    build();
    for( int i=1 ; i<=n ; i++ )
        scanf("%d %d",&po[i].x,&po[i].y);
    for( int i=2 ; i<n ; i++ )
        s[i-1]=change(po[i-1],po[i],po[i+1]);
    solve();
    for( int i=1 ; i<=n ; i++ )
        po[i].x=-po[i].x;   // 将所需要找的链翻转
    for( int i=2 ; i<n ; i++ )
        s[i-1]=change(po[i-1],po[i],po[i+1]);
    solve();
    for( int i=1 ; i<=tot ; i++ )
        for( int j=0 ; j<ed[i].size() ; j++ )
            ans[ed[i][j]]+=cnt[i];
    for( int i=1 ; i<=m ; i++ )
        printf("%d\n",ans[i]/(tp[i]+1));
    return 0;
}

这种情况在Q例很多的时候,会超时。

#include<bits/stdc++.h>
using namespace std;
const int MX=2e5+9;
int n,m,k,tot=0,fail[MX],last[MX],cnt[MX],ans[MX],tp[MX];
struct point{
    int x,y;
    point operator - ( const point &rhs){ return point{x-rhs.x,y-rhs.y};}
    int operator * (const point &rhs){ return x*rhs.y-y*rhs.x;}
    int operator ^ (const point &rhs){ return x*rhs.x+y*rhs.y;}
}po[MX];
 
struct node{
    int a,b,c,d;
    bool operator < (const node &rhs) const {
        if( a!=rhs.a ) return a<rhs.a;
        if( b!=rhs.b ) return b<rhs.b;
        if( c!=rhs.c ) return c<rhs.c;
        return d<rhs.d;
    }
}s[MX];
 
node change(point a,point b,point c){
    node an;
    an.a=(b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y);
    an.b=(c.x-b.x)*(c.x-b.x)+(c.y-b.y)*(c.y-b.y);
    an.c=(b-a)*(c-b),an.d=(b-a)^(c-b);
    int t=__gcd(an.a,an.b);
    if( t!=0 ) an.a/=t,an.b/=t;
    t=__gcd(abs(an.c),abs(an.d));
    if( t!=0 ) an.c/=t,an.d/=t;
    return an;
}
 
map<node,int> nex[MX];
vector<int> ed[MX];
#define iter map<node,int>::iterator
 
void ins(int len,int id){
    int now=0;
    for( int i=1 ; i<=len ; i++ ){
        iter it=nex[now].find(s[i]);
        if( it==nex[now].end() )
            now=nex[now][s[i]]=++tot;
        else
            now=it->second;
    }
    ed[now].push_back(id);
}
 
void build(){
    queue<int> que;
    for( iter it=nex[0].begin() ; it!=nex[0].end() ; it++ )
        que.push(it->second);
    while( !que.empty() ){
        int now=que.front();
        que.pop();
        for( iter it=nex[now].begin() ; it!=nex[now].end() ; it++ ){
            node a=it->first;
            int b=it->second,c=fail[now];
            while( c && nex[c].find(a)==nex[c].end() ) c=fail[c];
            if( nex[c].find(a)!=nex[c].end() )
                c=nex[c][a];
            fail[b]=c;
            last[b]=ed[c].empty()?last[c]:c;
            que.push(b);
        }
    }
}
 
void solve(){
    int now=0;
    for( int i=1 ; i<=n-2 ; i++ ){
        int x=now;
        while( x && nex[x].find(s[i])==nex[x].end() )
            x=fail[x];
        if( nex[x].find(s[i])!=nex[x].end() )
            x=nex[x][s[i]];
        now=x;
        for( ; x ; x=last[x] )
            for( int j=0 ; j<ed[x].size() ; j++ )
                ans[ed[x][j]]++;
    }
}
 
int main()
{
  //  freopen("input.txt","r",stdin);
    scanf("%d %d",&n,&m);
    for( int i=1 ; i<=m ; i++ ){
        int falg=1;
        scanf("%d",&k);
        for( int j=1 ; j<=k ; j++ )
            scanf("%d %d",&po[j].x,&po[j].y);
        if( k<=2 ){
            ans[i]=n-1;
            continue;
        }
        for( int j=2 ; j<k ; j++ ){
            s[j-1]=change(po[j-1],po[j],po[j+1]);
            if( s[j-1].c )
                falg=0;
        }
        if( falg )
            tp[i]=1;
        ins(k-2,i);
    }
    build();
    for( int i=1 ; i<=n ; i++ )
        scanf("%d %d",&po[i].x,&po[i].y);
    for( int i=2 ; i<n ; i++ )
        s[i-1]=change(po[i-1],po[i],po[i+1]);
    solve();
    for( int i=1 ; i<=n ; i++ )
        po[i].x=-po[i].x;
    for( int i=2 ; i<n ; i++ )
        s[i-1]=change(po[i-1],po[i],po[i+1]);
    solve();
    for( int i=1 ; i<=m  ; i++ )
        printf("%d\n",ans[i]/(tp[i]+1));
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值