BZOJ1039-[ZJOI2008]无序运动Movement
BZOJ1039-[ZJOI2008]无序运动Movement
题意:
题解:
这题在xjoi上交了半天一直wa,然后上bzoj交了一发就a了.似乎xjoi数据是错的?
我们可以发现对于平移,旋转,放缩,如果我们确定了轨迹片段的前三个点的坐标,就能确定轨迹片段中的所有点的位置.然后就可以把点列和轨迹片段换一种方式表示,方法就是描述相邻3个点之间的位置关系.一开始我是想通过描述相邻点连成的线段的长度的比值和夹角来表达,但是这会用到double,似乎会有精度误差?然后我在网上看到了一种非常妙的做法.对于相邻的3个点,描述这3个点互相连接形成的3条线段的长度平方的比值,和后两个点连成的线段在前两个点连成的线段的左侧还是右侧.前者可以直接求,后者可以用叉积求.通过这个表示法,我们就把这个问题转化成了字符串匹配问题,即求轨迹片段在点列中的出现次数.然后就可以用ac自动机了.
对于翻转操作,我们可以将点列反过来再跑一次ac自动机,但是对于一个轨迹片段,如果所有点都共线,翻转后就会和原来一样,这时我们就得把答案除以二.
如果一个轨迹片段长度小于3,它可以匹配上所有和它长度相等的子串,需要特判一下.
由于这道题建ac自动机的话字符集很大,所以trie树得用map数组存,这样的话就必须多存一个next数组而不是直接把next数组代表的边表示到trie图中.
好像这一题是bzoj第一页上ac人数最少的,至于为什么这么少我也不知道.
#include<cstdio> #include<vector> #include<map> #include<queue> #include<algorithm> using namespace std; const int maxn=200000; int n,m,ans[maxn+10]; struct pnt{int x,y; pnt(int x=0,int y=0):x(x),y(y){}}; pnt operator-(const pnt &a,const pnt &b){return pnt(a.x-b.x,a.y-b.y);} int operator&(const pnt &a,const pnt &b){return a.x*b.x+a.y*b.y;} int operator|(const pnt &a,const pnt &b){return a.x*b.y-a.y*b.x;} struct tnode{int da,db,dc,crs;}; bool operator<(const tnode &a,const tnode &b){ if(a.da!=b.da) return a.da<b.da; if(a.db!=b.db) return a.db<b.db; if(a.dc!=b.dc) return a.dc<b.dc; return a.crs<b.crs; } vector<pnt> G[maxn+10]; bool isrev[maxn+10]; int gcd(int a,int b){return b?gcd(b,a%b):a;} tnode get(const pnt &a,const pnt &b,const pnt &c){ tnode ans; ans.da=(b-a)&(b-a); ans.db=(c-b)&(c-b); ans.dc=(a-c)&(a-c); ans.crs=(b-a)|(c-b); if(ans.crs>0) ans.crs=1; else if(ans.crs<0) ans.crs=-1; int g=gcd(ans.da,gcd(ans.db,ans.dc)); ans.da/=g; ans.db/=g; ans.dc/=g; return ans; } map<tnode,int> ch[maxn+10]; vector<int> ed[maxn+10]; int cnt; void work(const vector<pnt> &arr,int id){ if(arr.size()<3){ ans[id]=n-arr.size()+1; return; } isrev[id]=1; int t=0; for(int i=0;i<arr.size()-2;++i){ tnode now=get(arr[i],arr[i+1],arr[i+2]); if(now.crs) isrev[id]=0; map<tnode,int>::iterator it=ch[t].find(now); if(it==ch[t].end()) t=(ch[t][now]=++cnt); else t=it->second; } ed[t].push_back(id); } int nxt[maxn+10],last[maxn+10],tot[maxn+10]; void build_nxt(){ queue<int> Q; for(map<tnode,int>::iterator it=ch[0].begin();it!=ch[0].end();++it) Q.push(it->second); for(;!Q.empty();Q.pop()){ int x=Q.front(); for(map<tnode,int>::iterator it=ch[x].begin();it!=ch[x].end();++it){ tnode now=it->first; int e=it->second,j=nxt[x]; for(;j&&ch[j].find(now)==ch[j].end();j=nxt[j]); if(ch[j].find(now)!=ch[j].end()) j=ch[j][now]; nxt[e]=j; last[e]=ed[nxt[e]].empty()?last[nxt[e]]:nxt[e]; Q.push(e); } } } vector<pnt> D; void count(const vector<pnt> &arr){ for(int i=0,t=0;i<arr.size()-2;++i){ tnode now=get(arr[i],arr[i+1],arr[i+2]); for(;t&&ch[t].find(now)==ch[t].end();t=nxt[t]); if(ch[t].find(now)!=ch[t].end()) t=ch[t][now]; for(int j=t;j;j=last[j]) ++tot[j]; } } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;++i){ int k; scanf("%d",&k); G[i].resize(k); for(int j=0;j<k;++j) scanf("%d%d",&G[i][j].x,&G[i][j].y); work(G[i],i); } build_nxt(); D.resize(n); for(int i=0;i<n;++i) scanf("%d%d",&D[i].x,&D[i].y); count(D); for(int i=0;i<D.size();++i) D[i].x=-D[i].x; count(D); for(int i=1;i<=cnt;++i) for(int j=0;j<ed[i].size();++j) ans[ed[i][j]]+=tot[i]/(isrev[ed[i][j]]+1); for(int i=1;i<=m;++i) printf("%d\n",ans[i]); return 0; }