BZOJ1396:识别子串 后缀自动机+并查集(&&BZOJ2865)

思路:

我们建立原串的后缀树,那么发现出现次数为1的子串仅能出现在叶子节点上的父亲边上.(说起来很容易)

然后发现每一个更新可以分解为两端,每一段可以相当于一条斜率恒定的线段.

因此我们将两段分开,按照截距从小到大排序,并用并查集模拟覆盖即可.

(说起来很容易)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
 
#define N 100010
struct Node{
    Node*tranc[26],*pa;int len,begin,siz,deg;
    Node(){}
    Node(int _len,int _begin):len(_len),begin(_begin){}
}Tnull,*null=&Tnull,mem[N<<1],*P=mem,*root,*last;
Node*Newnode(int _len,int _begin){
    P->len=_len,P->begin=_begin;for(int i=0;i<26;++i)P->tranc[i]=null;P->pa=null;return P++;
}
 
char s[N];
 
struct Interval{
    int l,r,k;
    Interval(){}
    Interval(int _l,int _r,int _k):l(_l),r(_r),k(_k){}
    bool operator<(const Interval&B)const{return k<B.k;}
}S[N],S2[N];
 
int r[N];
inline void reset(int n){
    for(register int i=1;i<=n+1;++i)r[i]=i;
}
inline int find(int x){
    int q=x,rq;for(;x!=r[x];x=r[x]);for(;q!=x;q=rq)rq=r[q],r[q]=x;return x;
}
 
int v[N],vv[N];
 
Node*q[N<<1];int fr,ta;
 
int main(){
    scanf("%s",s+1);int l=strlen(s+1);
    register int i,j;int y;Node*p,*np,*q,*nq;
    for(last=root=Newnode(0,0),i=l;i>=1;--i){
        np=Newnode(last->len+1,i);np->siz=1;
        for(p=last,y=s[i]-'a';p!=null&&p->tranc[y]==null;p=p->pa)p->tranc[y]=np;
        if(p==null)np->pa=root;
        else{
            q=p->tranc[y];
            if(q->len==p->len+1)np->pa=q;
            else{
                nq=Newnode(p->len+1,0);nq->pa=q->pa,q->pa=np->pa=nq;
                memcpy(nq->tranc,q->tranc,sizeof q->tranc);
                for(;p!=null&&p->tranc[y]==q;p=p->pa)p->tranc[y]=nq;
            }
        }last=np;
    }
     
    for(p=mem;p<P;++p)++p->pa->deg;
    for(p=mem;p<P;++p)if(p->deg==0)::q[ta++]=p;
    while(fr^ta){
        p=::q[fr++];
        if(p->pa!=null){
            p->pa->siz+=p->siz;
            if(!--p->pa->deg)
                ::q[ta++]=p->pa;
        }
    }
     
    int id=0,id2=0;
    for(p=mem;p<P;++p){
        if(p->siz==1){
            //printf("%d %d %d\n",p->begin,p->len,p->pa->len);
            S[++id]=Interval(l-(p->len-p->pa->len)+1,l,1-p->begin);
            S2[++id2]=Interval(p->begin,S[id].l-1,S[id].l-p->begin+1);
        }
    }sort(S+1,S+id+1),sort(S2+1,S2+id2+1);
     
    reset(l);
    memset(v,0x3f,sizeof v);
    for(i=1;i<=id;++i){
        for(j=find(S[i].l);j<=S[i].r;j=r[j]){
            if(v[j]==0x3f3f3f3f)v[j]=S[i].k;
            r[j]=find(j+1);
        }
    }
    reset(l);
    memset(vv,0x3f,sizeof vv);
    for(i=1;i<=id2;++i){
        for(j=find(S2[i].l);j<=S2[i].r;j=r[j]){
            if(vv[j]==0x3f3f3f3f)vv[j]=S2[i].k;
            r[j]=find(j+1);
        }
    }
     
    for(i=1;i<=l;++i)printf("%d\n",min(l,min(v[i]+i,vv[i])));
     
    return 0;
}

BZOJ2780:[Spoj]8093 Sevenk Love Oimaster 后缀自动机+离线+dfs序+树状数组

思路:

首先建立多串后缀自动机(别问我怎么建的)

然后对于每个询问串在自动机上走,记录下走到的节点.那么在几个串中出现等价于逆序后缀树的子树中有几个原串的后缀.

转化为dfs序之后,这等价于每次询问一段区间有几种不同的数.

经典模型,离线+树状数组水.

(我TTMD坑了QoQ)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
   
int n,m;
struct Graph{
    int head[200010],next[200010],end[200010],ind;
    inline void addedge(int a,int b){static int q=1;end[q]=b,next[q]=head[a],head[a]=q++;}
}w,g;
   
int len[200010],deg[200010],pa[200010],cnt,root,last;
map<int,int>tranc[200010];
int newnode(int l){
    len[++cnt]=l;return cnt;
}
   
char s[360010];
   
int in[200010],out[200010],seq[200010],tclock;
inline void dfs(int x){
    in[x]=++tclock;seq[tclock]=x;
    for(int j=g.head[x];j;j=g.next[j])dfs(g.end[j]);
    out[x]=tclock;
}
   
int pre[10010];
struct Ask{
    int lab,l,r;
    Ask(){}
    Ask(int _lab,int _l,int _r):lab(_lab),l(_l),r(_r){}
    bool operator<(const Ask&B)const{return r<B.r;}
}S[60010];
   
int A[200010];
inline void mdf(int x,int add){
    for(;x<=cnt;x+=x&-x)A[x]+=add;
}
inline int ask(int x){
    int r=0;for(;x;x-=x&-x)r+=A[x];return r;
}
   
int lastins[10010];
int ans[60010];
   
void get(int x){for(int j=w.head[x];j;j=w.next[j])printf("%d ",w.end[j]);puts("");}
   
int main(){
    scanf("%d%d",&n,&m);
    register int i,j,k;int l,y,p,np,q,nq,rep,tmp;
    for(root=newnode(0),i=1;i<=n;++i){
        scanf("%s",s);l=strlen(s);
        for(last=root,j=0;j<l;++j){
            if((p=tranc[last][y=s[j]])!=0){
                if(len[p]==len[last]+1)last=p;
                else{
                    rep=newnode(len[last]+1);pa[rep]=pa[p],pa[p]=rep;
                    tranc[rep]=tranc[p];
                    for(tmp=last;tmp&&tranc[tmp][y]==p;tmp=pa[tmp])tranc[tmp][y]=rep;
                    last=rep;
                }
            }
            else{
                np=newnode(len[last]+1);
                for(p=last;p&&!tranc[p][y];p=pa[p])tranc[p][y]=np;
                if(!p)pa[np]=root;
                else{
                    q=tranc[p][y];
                    if(len[q]==len[p]+1)pa[np]=q;
                    else{
                        nq=newnode(len[p]+1),pa[nq]=pa[q],pa[np]=pa[q]=nq;
                        tranc[nq]=tranc[q];
                        for(;p&&tranc[p][y]==q;p=pa[p])tranc[p][y]=nq;
                    }
                }last=np;
            }
            w.addedge(last,i);
        }
    }
    for(i=1;i<=cnt;++i)if(pa[i])g.addedge(pa[i],i);
    dfs(1);
       
    bool find;
    for(i=1;i<=m;++i){
        scanf("%s",s);l=strlen(s);find=1;
        for(p=root,j=0;j<l;++j){
            y=s[j];if(!tranc[p][y]){find=0;break;}p=tranc[p][y];
        }
        if(find)S[i]=Ask(i,in[p],out[p]);else S[i]=Ask(i,-1,-1);
    }sort(S+1,S+m+1);
       
    int noww;k=1;while(k<=m&&S[k].l==-1)++k;
    for(i=1;i<=cnt;++i){
        for(j=w.head[seq[i]];j;j=w.next[j]){
            noww=w.end[j];
            mdf(i,1);if(lastins[noww])mdf(lastins[noww],-1);lastins[noww]=i;
        }
        for(;S[k].r==i;++k)ans[S[k].lab]=ask(S[k].r)-ask(S[k].l-1);
    }
       
    for(i=1;i<=m;++i)printf("%d\n",ans[i]);
    return 0;
}

[开新坑]对于后缀自动机的一些理解

感觉单纯地从"后缀自动机"的角度来入手并不是非常合理.因为我们懂得很多"后缀自动机"的性质,但却并不清楚"后缀自动机"在本质上是什么.

让我们从后缀树说起.

 

POJ3415 Common Substrings 后缀数组+单调栈

思路:

最近好像是被<囚人的旋律>里的主人公的心情所影响,所以效率很低.

例如,这样一道傻逼题我居然傻逼了3个小时!

我整个人都单调栈了.

 

就是求个height,然后在连续块内,我们对于每个属于串A的后缀分别计算出在他前面的属于串B的后缀的影响,在计算出在他之后的属于B的后缀的影响.

然后我们从前到后,再从后到前扫描一边,利用一个单调栈维护一下属于串B的后缀即可.

说起来简单...不过写起来...一定是我太鶸了.

 

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;

#define N 200010
char s1[N],s2[N],s[N];int v[N],tv[N],q[N],c[N],sa[N];
inline bool cmp(int x,int y,int hl,int n){
	return v[x]==v[y]&&((x+hl>=n&&y+hl>=n)||(x+hl<n&&y+hl<n&&v[x+hl]==v[y+hl]));
}
inline void calcsa(char*s,int n,int m,int*sa){
	register int i,j,k;int id,hl;
	for(i=0;i<m;++i)c[i]=0;
	for(i=0;i<n;++i)++c[v[i]=s[i]];
	for(i=1;i<m;++i)c[i]+=c[i-1];
	for(i=n-1;i>=0;--i)sa[--c[v[i]]]=i;
	for(int d=1;;++d){
		for(hl=1<<(d-1),id=0,i=n-hl;i<n;++i)q[id++]=i;
		for(i=0;i<n;++i)if(sa[i]>=hl)q[id++]=sa[i]-hl;
		for(i=0;i<m;++i)c[i]=0;
		for(i=0;i<n;++i)++c[v[q[i]]];
		for(i=1;i<m;++i)c[i]+=c[i-1];
		for(i=n-1;i>=0;--i)sa[--c[v[q[i]]]]=q[i];
		for(i=m=0;i<n;i=j+1,++m){
			for(j=i;j<n-1&&cmp(sa[j],sa[j+1],hl,n);++j);
			for(k=i;k<=j;++k)tv[sa[k]]=m;
		}for(i=0;i<n;++i)v[i]=tv[i];
		if(m==n)break;
	}
}
int rk[N],h[N];
inline void calch(char*s,int n,int*sa,int*rk,int*h){
	register int i,j;
	for(i=0;i<n;++i)rk[sa[i]]=i;
	for(i=0;i<n;++i)if(rk[i]){
		j=max(0,h[rk[i-1]]-1);while(i+j<n&&sa[rk[i]-1]+j<n&&s[i+j]==s[sa[rk[i]-1]+j])++j;h[rk[i]]=j;
	}
}

int lim;

struct Case{
	int v,c;
	Case(){}
	Case(int _v,int _c):v(_v),c(_c){}
}stk[N];
int top,nowcnt;long long sum[N];int cnt[N];

int belong[N];
int main(){
	int i,j,k;
	while(scanf("%d",&lim)&&lim){
		scanf("%s%s",s1,s2);int len1=strlen(s1),len2=strlen(s2);
		memset(s,0,sizeof s);int len=0;for(i=0;i<len1;++i)belong[len]=1,s[len++]=s1[i];s[len++]='$';for(i=0;i<len2;++i)belong[len]=2,s[len++]=s2[i];
		calcsa(s,len,256,sa),calch(s,len,sa,rk,h);

		LL ans=0;int tmp;
		for(i=1;i<len;){
			if(h[i]>=lim){
				for(j=i;j<len&&h[j]>=lim;++j);
				for(top=0,h[i-1]=h[i],tmp=h[j],h[j]=h[j-1],k=i-1;k<j;++k){
					nowcnt=0;
					while(top&&stk[top].v>=h[k])nowcnt+=stk[top--].c;
					if(nowcnt)stk[++top]=Case(h[k],nowcnt),sum[top]=sum[top-1]+(LL)stk[top].c*stk[top].v,cnt[top]=cnt[top-1]+stk[top].c;
					if(belong[sa[k]]==2)ans+=sum[top]-(LL)cnt[top]*(lim-1);
					else{
						nowcnt=1;while(top&&stk[top].v>=h[k+1])nowcnt+=stk[top--].c;
						stk[++top]=Case(h[k+1],nowcnt),sum[top]=sum[top-1]+(LL)stk[top].c*stk[top].v,cnt[top]=cnt[top-1]+stk[top].c;
					}
					
				}
				for(top=0,k=j-1;k>=i-1;--k){
					nowcnt=0;
					while(top&&stk[top].v>=h[k+1])nowcnt+=stk[top--].c;
					if(nowcnt)stk[++top]=Case(h[k+1],nowcnt),sum[top]=sum[top-1]+(LL)stk[top].c*stk[top].v,cnt[top]=cnt[top-1]+stk[top].c;
					if(belong[sa[k]]==2)ans+=sum[top]-(LL)cnt[top]*(lim-1);
					else{
						nowcnt=1;while(top&&stk[top].v>=h[k])nowcnt+=stk[top--].c;
						stk[++top]=Case(h[k],nowcnt),sum[top]=sum[top-1]+(LL)stk[top].c*stk[top].v,cnt[top]=cnt[top-1]+stk[top].c;
					}
				}h[j]=tmp;
				i=j;
			}else++i;
		}
		cout<<ans<<endl;
	}
	return 0;
}

POJ3693 Maximum repetition substring 后缀数组

思路:

这个题简直丧心病狂.终于是过了.

注意到重复出现一次的字串必定是字典序最小的字符.我们只考虑重复出现不少于两次的子串.

首先我们枚举重复出现的每一小段的长度\(l\).考虑若干个端点\(0,l,2l,...,i*l,(i+1)*l\),若存在每一小段长度为\(l\)且连续重复出现至少两次的子串,则至少覆盖两个端点.

因此我们枚举相邻的两个端点,分别找出向前最多能匹配多少,向后最多能匹配多少,我们就找到了两个完全相等的段,而且其错开了\(l\)的长度.不妨令其长度为\(d\),则(通过画图)此时的最大重复次数为\(\frac{d}{l}+1\).

如果不要求字典序的话,我们直接这样找到答案就行了.用后缀数组预处理一大堆东西能做到各种询问\(O(1)\)时间复杂度为:

\[\frac{n}{1}+\frac{n}{2}+...+\frac{n}{n}=O(nlogn)\]

因此总的时间复杂度为\(O(nlogn)\).

关键是要求字典序最小.

我们发现每次找到两个完全相等的段时都存在很多合法的答案,也就是若干长度相等,但起点位置不同的子串.我们预处理出起点区间内字典序最小的子串,并利用这个更新答案即可.

不管怎么样,反正时间复杂度\(O(nlogn)\).

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;

#define N 100010
int len;
char s[N],ss[N];

int v[N],tmpv[N],cnt[N],q[N],sa1[N],sa2[N];
inline bool cmp(int x,int y,int hl,int n){
	return v[x]==v[y]&&((x+hl>=n&&y+hl>=n)||(x+hl<n&&y+hl<n&&v[x+hl]==v[y+hl]));
}
inline void calcsa(char*s,int n,int m,int*sa){
	register int i,j,k;int hl,id;
	for(i=0;i<m;++i)cnt[i]=0;
	for(i=0;i<n;++i)++cnt[v[i]=s[i]];
	for(i=1;i<m;++i)cnt[i]+=cnt[i-1];
	for(i=n-1;i>=0;--i)sa[--cnt[v[i]]]=i;
	for(int d=1;;++d){
		for(hl=1<<(d-1),id=0,i=n-hl;i<n;++i)q[id++]=i;
		for(i=0;i<n;++i)if(sa[i]>=hl)q[id++]=sa[i]-hl;
		for(i=0;i<m;++i)cnt[i]=0;
		for(i=0;i<n;++i)++cnt[v[q[i]]];
		for(i=1;i<m;++i)cnt[i]+=cnt[i-1];
		for(i=n-1;i>=0;--i)sa[--cnt[v[q[i]]]]=q[i];
		for(m=i=0;i<n;m++,i=j+1){
			for(j=i;j<n-1&&cmp(sa[j],sa[j+1],hl,n);++j);
			for(k=i;k<=j;++k)tmpv[sa[k]]=m;
		}for(i=0;i<n;++i)v[i]=tmpv[i];
		if(m==n)break;
	}
}
int rk1[N],rk2[N],h1[N],h2[N];
inline void calch(char*s,int n,int*sa,int*rk,int*h){
	register int i,j;
	for(i=0;i<n;++i)rk[sa[i]]=i;
	for(i=0;i<n;++i)if(rk[i]){
		j=max(0,h[rk[i-1]]-1);while(i+j<n&&sa[rk[i]-1]+j<n&&s[i+j]==s[sa[rk[i]-1]+j])++j;h[rk[i]]=j;
	}
}
int pre[N],rh1[N][21],rh2[N][21];
inline void Initlen(int n){
	for(register int i=2;i<=n;++i)pre[i]=pre[i>>1]+1;
}
inline void Initrmq(int*source,int rm[][21],int n){
	register int i,j;
	for(i=0;i<n;++i)rm[i][0]=source[i];
	for(j=1;j<=20;++j)for(i=0;i+(1<<j)-1<n;++i)rm[i][j]=min(rm[i][j-1],rm[i+(1<<(j-1))][j-1]);
}
inline int askmin(int rm[][21],int l,int r){
	return min(rm[l][pre[r-l+1]],rm[r-(1<<pre[r-l+1])+1][pre[r-l+1]]);
}
inline int getlcp(int x,int y){
	static int rx,ry;rx=rk1[x],ry=rk1[y];if(rx>ry)swap(rx,ry);
	return x==y?len-x:askmin(rh1,rx+1,ry);
}
inline int getlcs(int x,int y){
	static int rx,ry;rx=rk2[len-1-x],ry=rk2[len-1-y];if(rx>ry)swap(rx,ry);
	return x==y?x+1:askmin(rh2,rx+1,ry);
}
int mins[N][21];
inline void Initmins(){
	register int i,j;
	for(i=0;i<len;++i)mins[i][0]=i;
	for(j=1;j<=20;++j)for(i=0;i+(1<<j)-1<len;++i)mins[i][j]=rk1[mins[i][j-1]]<rk1[mins[i+(1<<(j-1))][j-1]]?mins[i][j-1]:mins[i+(1<<(j-1))][j-1];
}
inline int getmins(int l,int r){
	int sl=mins[l][pre[r-l+1]],sr=mins[r-(1<<pre[r-l+1])+1][pre[r-l+1]];
	return rk1[sl]<rk1[sr]?sl:sr;
}

struct Ans{
	int ins,len,t;
	Ans(){}
	Ans(int _ins,int _len,int _t):ins(_ins),len(_len),t(_t){}
	bool operator==(const Ans&B)const{return ins==B.ins&&len==B.len&&t==B.t;}
	bool operator<(const Ans&B)const{
		if(t!=B.t)return t>B.t;
		if(ins==B.ins)return len<B.len;
		int rx=rk1[ins],ry=rk1[B.ins],lcp=getlcp(ins,B.ins);
		if(len>lcp&&B.len>lcp)return rx<ry;else return len<B.len;
	}
}res;
inline void upd(Ans&x,Ans y){if(y<x)x=y;}

int main(){
	//freopen("tt.in","r",stdin);
	int test=0;
	register int i,j;
	while(scanf("%s",s)&&s[0]!='#'){
		printf("Case %d: ",++test); 
		len=strlen(s);for(i=0;i<len;++i)ss[i]=s[len-1-i];
		calcsa(s,len,256,sa1),calcsa(ss,len,256,sa2);
		calch(s,len,sa1,rk1,h1),calch(ss,len,sa2,rk2,h2);
		Initlen(len);
		Initrmq(h1,rh1,len),Initrmq(h2,rh2,len);
		Initmins();
		res=Ans(0,0,0);
		int lcp,lcs,d;
		for(int l=1;l<=len/2;++l){
			for(i=0;(i+1)*l<len;++i){
				lcp=getlcp(i*l,(i+1)*l);
				lcs=getlcs(i*l,(i+1)*l);
				d=lcp+lcs-1;
				upd(res,Ans(getmins(i*l-lcs+1,(i+1)*l+lcp-(d/l+1)*l),(d/l+1)*l,d/l+1));
			}
		}
		if(res.t==1){
			int c=~0U>>1;for(i=0;i<len;++i)c=min(c,(int)s[i]);printf("%c\n",c);
		}
		else{for(j=0;j<res.len;++j)putchar(s[res.ins+j]);puts("");}	
	}
	return 0;
}

BZOJ3205:[Apio2013]机器人 斯坦纳树+单位边权多源最短路

思路:

就是一堆机器人合并起来,看起来天然的生成树模型吗.

令\(f[i][j][x][y]\)表示区间\([i,j]\)的机器人合并到位置\((x,y)\)使得最小推动次数,模仿斯坦纳树,我们有以下两种转移:

\[f[i][j][x][y]=f[i][j][x'][y']+1\]

\[f[i][j][x][y]=f[i][k][x][y]+f[k+1][j][x][y]\]

第二种转移直接暴力即可.

对于第一种转移,我们则可以利用一次最短路spfa求解.

至于如何构图呢?我们需要预处理出对于机器人目前在的每一个位置,我们将机器人推动一个方向后最终处于的位置.

我煞笔被坑了几次:首先机器人最终是可以停在转动器位置的.

如果我们每次暴力枚举,会T,因此只能利用记忆化搜索来做.

反正这个预处理坑了我好久.(太弱不解释

 

可是只有这些优化还是不能AC的!

我们每次暴力spfa的耗时还是过长了.

这怎么优化?

我们发现边权都是1,下面介绍一种神奇的方法:

我们维护两个队列,首先将所有的点按照距离从小到大的顺序加入第二个队列中,随后进行松弛,每次选择两个队列队头距离比较小的点取出进行松弛,并将松弛后的点加入第一个队列中,这样就起到了优先队列的作用,且不用带一个\(log\),这样做是线性的.

这样才能AC!

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
 
#define INF 0x1f1f1f1f
 
#define N 10
#define W 510
#define H 510
int n,w,h;
char s[W][H];
     
int f[N][N][W][H],tclock,vis[W][H][4];
 
int nowi,nowj;
struct Ins{
    short x,y;
    Ins(){}
    Ins(int _x,int _y):x(_x),y(_y){}
    bool operator<(const Ins&B)const{return f[nowi][nowj][x][y]<f[nowi][nowj][B.x][B.y];}
}vec[W][H][4];
 
const int dx[]={-1,0,1,0},dy[]={0,-1,0,1};
 
queue<Ins>q1,q2;
 
Ins seq[W*H],tmp[W*H];int siz,rk[W*H],t[200010],cnt[200010];
inline void add(int x,int c,int v){if(t[x]!=v)t[x]=v,cnt[x]=0;cnt[x]+=c;}
inline int ask(int x,int v){if(t[x]!=v)t[x]=v,cnt[x]=0;return cnt[x];}
 
int sig;
inline void Rdxsort(){
    ++sig;
    int Max=0;register int i;int x,y;
    for(i=1;i<=siz;++i)add(f[nowi][nowj][x=seq[i].x][y=seq[i].y],1,sig),Max=max(Max,f[nowi][nowj][x][y]);
    for(i=1;i<=Max;++i)add(i,ask(i-1,sig),sig);
    for(i=1;i<=siz;++i)rk[i]=ask(f[nowi][nowj][x=seq[i].x][y=seq[i].y],sig),add(f[nowi][nowj][x][y],-1,sig),tmp[rk[i]]=seq[i];
    for(i=1;i<=siz;++i)seq[i]=tmp[i];
}
 
bool inq[W][H];
inline void work(){
    register int i,j,k;
    for(i=1;i<=siz;++i)q2.push(seq[i]),inq[seq[i].x][seq[i].y]=1;
    Ins t,to;int d1,d2;
    while(!q1.empty()||!q2.empty()){
        if(q1.empty())t=q2.front(),q2.pop();
        else if(q2.empty())t=q1.front(),q1.pop();
        else{
            d1=f[nowi][nowj][q1.front().x][q1.front().y];
            d2=f[nowi][nowj][q2.front().x][q2.front().y];
            if(d1<d2)t=q1.front(),q1.pop();else t=q2.front(),q2.pop();
        }
        for(inq[t.x][t.y]=k=0;k<4;++k){
            if((to=vec[t.x][t.y][k]).x!=INF&&f[nowi][nowj][to.x][to.y]>f[nowi][nowj][t.x][t.y]+1){
                f[nowi][nowj][to.x][to.y]=f[nowi][nowj][t.x][t.y]+1;
                if(!inq[to.x][to.y])inq[to.x][to.y]=1,q1.push(to);
            }
        }
    }
}
 
Ins calc(int x,int y,int v){
    if(vis[x][y][v]==tclock)return Ins(-1,-1);vis[x][y][v]=tclock;
    if(!(vec[x][y][v].x==0&&vec[x][y][v].y==0))return vec[x][y][v];
    int nowv=v;if(s[x][y]=='A')nowv=(v+1)%4;if(s[x][y]=='C')nowv=(v+3)%4;
    int tx=x+dx[nowv],ty=y+dy[nowv];
    if(s[tx][ty]=='x')return vec[x][y][v]=Ins(x,y);
    return vec[x][y][v]=calc(tx,ty,nowv);
}
 
int main(){
    scanf("%d%d%d",&n,&h,&w);
    register int i,j,k,p,q;
    for(i=1;i<=w;++i)scanf("%s",s[i]+1);
    memset(f,0x1f,sizeof f);
    int id;
    for(i=1;i<=w;++i)for(j=1;j<=h;++j)if(s[i][j]>='1'&&s[i][j]<='9')id=s[i][j]-'0',f[id][id][i][j]=0;
 
    for(i=0;i<=w+1;++i)s[i][0]=s[i][h+1]='x';
    for(j=0;j<=h+1;++j)s[0][j]=s[w+1][j]='x';
 
    for(i=1;i<=w;++i)for(j=1;j<=h;++j)if(s[i][j]!='x')
        for(k=0;k<4;++k)
            ++tclock,vec[i][j][k]=calc(i,j,k);
 
    for(int l=1;l<=n;++l)for(i=1;i+l-1<=n;++i){
        j=i+l-1;
        for(k=i;k<j;++k)for(p=1;p<=w;++p)for(q=1;q<=h;++q)f[i][j][p][q]=min(f[i][j][p][q],f[i][k][p][q]+f[k+1][j][p][q]);
        siz=0;for(p=1;p<=w;++p)for(q=1;q<=h;++q)if(f[i][j][p][q]!=INF)seq[++siz]=Ins(p,q);
        nowi=i,nowj=j,Rdxsort();
        work();
    }
 
    int ans=INF;
    for(i=1;i<=w;++i)for(j=1;j<=h;++j)ans=min(ans,f[1][n][i][j]);
 
    if(ans==INF)puts("-1");else printf("%d\n",ans);
 
    return 0;
}

BZOJ2595: [Wc2008]游览计划 斯坦纳树

思路:

斯坦纳树裸题.

输出方案的话记录一下转移就好了.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
 
#define INF 0x1f1f1f1f
 
#define N 11
#define M 11
int n,m,d[N][M],lab[N][M],cnt;
 
int f[N][M][1<<11];
 
struct Lastans{
    int x,y,S;
    Lastans(){}
    Lastans(int _x,int _y,int _S):x(_x),y(_y),S(_S){}
}g[N][M][1<<11];
 
struct Ins{
    int x,y;
    Ins(){}
    Ins(int _x,int _y):x(_x),y(_y){}
};
queue<Ins>q;
 
const int dx[]={-1,1,0,0},dy[]={0,0,1,-1};
bool inq[N][M];
#define Inrange(x,l,r) ((x)>=(l)&&(x)<=(r))
inline void spfa(int S){
    int tx,ty,i,j;
    while(!q.empty()){
        Ins t=q.front();q.pop();inq[i=t.x][j=t.y]=0;
        for(int k=0;k<4;++k)if(Inrange(tx=i+dx[k],1,n)&&Inrange(ty=j+dy[k],1,m)){
            if(f[tx][ty][S]>f[i][j][S]+d[tx][ty]){
                f[tx][ty][S]=f[i][j][S]+d[tx][ty],g[tx][ty][S]=Lastans(i,j,S);
                if(!inq[tx][ty])inq[tx][ty]=1,q.push(Ins(tx,ty));
            }
        }
    }
}
 
bool vis[N][M];
inline void dfs(int x,int y,int S){
    if(!S)return;
    vis[x][y]=1;if((!d[x][y])&&(S==(1<<lab[x][y])))return;
    Lastans p=g[x][y][S];
    dfs(p.x,p.y,p.S);
    if(S!=p.S)dfs(x,y,S-p.S);
}
 
int main(){
    scanf("%d%d",&n,&m);
    register int i,j;for(i=1;i<=n;++i)for(j=1;j<=m;++j){scanf("%d",&d[i][j]);if(!d[i][j])lab[i][j]=cnt++;}
    memset(f,0x1f,sizeof f);
    for(i=1;i<=n;++i)for(j=1;j<=m;++j)if(!d[i][j])f[i][j][1<<lab[i][j]]=0;
    for(int S=1;S<(1<<cnt);++S){
        for(int mas=(S-1)&S;mas;mas=(mas-1)&S)
            for(i=1;i<=n;++i)
                for(j=1;j<=m;++j)
                    if(f[i][j][S]>f[i][j][mas]+f[i][j][S-mas]-d[i][j])
                        f[i][j][S]=f[i][j][mas]+f[i][j][S-mas]-d[i][j],g[i][j][S]=Lastans(i,j,mas);
        memset(inq,0,sizeof inq);
        for(i=1;i<=n;++i)for(j=1;j<=m;++j)if(f[i][j][S]!=INF)q.push(Ins(i,j)),inq[i][j]=1;
        spfa(S);
    }
 
    Lastans res;
    int ans=INF;for(i=1;i<=n;++i)for(j=1;j<=m;++j)if(ans>f[i][j][(1<<cnt)-1])ans=f[i][j][(1<<cnt)-1],res=Lastans(i,j,(1<<cnt)-1);
    printf("%d\n",ans);
    dfs(res.x,res.y,(1<<cnt)-1);
    for(i=1;i<=n;++i){
        for(j=1;j<=m;++j)if(!vis[i][j])putchar('_');else if(vis[i][j]&&d[i][j]==0)putchar('x');else putchar('o');puts("");
    }
    return 0;
}

BZOJ3611: [Heoi2014]大工程 LCA单调性+树形dp

思路:思路基本同上一道题.

#include<cstdio>
#include<cstring>
#include<cctype>
#include<climits>
#include<iostream>
#include<algorithm>
using namespace std;
 
#define INF 0x1f1f1f1f
 
namespace Fio{
    inline int getc(){
        static const int L=1<<15;static char buf[L],*S=buf,*T=buf;
        if(S==T){T=(S=buf)+fread(buf,1,L,stdin);if(S==T)return EOF;}
        return*S++;
    }
    template<typename T>inline void Get(T&x){
        int c;while(!isdigit(c=getc()));x=c-'0';while(isdigit(c=getc()))x=(x<<1)+(x<<3)+c-'0';
    }
}
#define N 1000010
int Head[N],Next[N<<1],End[N<<1],Len[N<<1];
inline void addedge(int a,int b,int x){static int q=1;End[q]=b,Next[q]=Head[a],Head[a]=q,Len[q++]=x;}
inline void make(int a,int b,int x){addedge(a,b,x),addedge(b,a,x);}
 
int dfn[N],id,dep[N],pa[N][21];
inline bool cmp(const int&x,const int&y){return dfn[x]<dfn[y];}
inline void dfs(int x,int fa){
    dfn[x]=++id;
    for(int j=Head[x];j;j=Next[j])if(End[j]!=fa)dep[End[j]]=dep[x]+1,pa[End[j]][0]=x,dfs(End[j],x);
}
int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;--i)if(dep[pa[x][i]]>=dep[y])x=pa[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;--i)if(pa[x][i]!=pa[y][i])x=pa[x][i],y=pa[y][i];
    return pa[x][0];
}
int getdis(int son,int Anc){
    //int r=0;for(int i=20;i>=0;--i)if(dep[pa[son][i]]>=dep[Anc])r+=w[son][i],son=pa[son][i];return r;
    return dep[son]-dep[Anc];
}
 
struct Array{
    int d[N],t[N],init;
    Array(){}
    Array(int _init):init(_init){}
    inline int ask(int x,int nowv){
        if(t[x]!=nowv)t[x]=nowv,d[x]=init;return d[x];
    }
    inline void modify(int x,int to,int nowv){
        t[x]=nowv,d[x]=to;
    }
    inline void maxi(int x,int to,int nowv){
        if(t[x]!=nowv)t[x]=nowv,d[x]=init;if(d[x]<to)d[x]=to;
    }
    inline void mini(int x,int to,int nowv){
        if(t[x]!=nowv)t[x]=nowv,d[x]=init;if(d[x]>to)d[x]=to;
    }
    inline void add(int x,int c,int nowv){
        if(t[x]!=nowv)t[x]=nowv,d[x]=init;d[x]+=c;
    }
};
 
int nowv;
struct Tree{
    Array h;int next[N],end[N],len[N],ind;
    inline void reset(){h.init=-1;ind=0;}
    inline void addedge(int a,int b){int q=ind++;end[q]=b,next[q]=h.ask(a,nowv),h.modify(a,q,nowv),len[q++]=getdis(b,a);}
}G;
 
int seq[N],siz,end;
 
int stk[N],top,Anc[N];
 
Array isimp(0),maxdep(-INF),mindep(INF),cnt(0);
long long tot;
int maxans,minans;
inline void mini(int&x,int y){if(x>y)x=y;}
inline void maxi(int&x,int y){if(x<y)x=y;}
void Solve(int x){
    for(int j=G.h.ask(x,nowv);j!=-1;j=G.next[j])Solve(G.end[j]),cnt.add(x,cnt.ask(G.end[j],nowv),nowv),tot+=(long long)G.len[j]*cnt.ask(G.end[j],nowv)*(siz-cnt.ask(G.end[j],nowv));
    if(isimp.ask(x,nowv))maxdep.modify(x,0,nowv),mindep.modify(x,0,nowv),cnt.add(x,1,nowv);
    for(int j=G.h.ask(x,nowv);j!=-1;j=G.next[j])
        mini(minans,mindep.ask(x,nowv)+mindep.ask(G.end[j],nowv)+G.len[j]),
        mindep.mini(x,mindep.ask(G.end[j],nowv)+G.len[j],nowv);
    for(int j=G.h.ask(x,nowv);j!=-1;j=G.next[j])
        maxi(maxans,maxdep.ask(x,nowv)+maxdep.ask(G.end[j],nowv)+G.len[j]),
        maxdep.maxi(x,maxdep.ask(G.end[j],nowv)+G.len[j],nowv);
}
int main(){
    #ifndef ONLINE_JUDGE
    freopen("tt.in","r",stdin);
    //freopen("tt.out","w",stdout);
    #endif
    using namespace Fio;
    int n;Get(n);
    register int i,j;int a,b;for(i=1;i<n;++i)Get(a),Get(b),make(a,b,1);
    dep[1]=1,dfs(1,-1);
    for(j=1;j<=20;++j)
        for(i=1;i<=n;++i)pa[i][j]=pa[pa[i][j-1]][j-1];
    int Q;Get(Q);
    while(Q--){
        Get(siz);for(i=1;i<=siz;++i)Get(seq[i]);end=siz;sort(seq+1,seq+siz+1,cmp);
        ++nowv;for(i=1;i<=siz;++i)isimp.modify(seq[i],1,nowv);
        for(top=0,i=1;i<=siz;++i){
            if(!top)stk[++top]=seq[i];
            else{
                int Lca=lca(stk[top],seq[i]);Anc[seq[i]]=Lca;
                while(dep[stk[top]]>dep[Lca]){if(dep[stk[top-1]]<=dep[Lca])Anc[stk[top]]=Lca;--top;}
                if(Lca!=stk[top])Anc[Lca]=stk[top],seq[++end]=stk[++top]=Lca;
                stk[++top]=seq[i];
            }
        }
        sort(seq+1,seq+end+1,cmp);for(G.reset(),i=2;i<=end;++i)G.addedge(Anc[seq[i]],seq[i]);
        tot=0,minans=INF,maxans=-INF,Solve(seq[1]);
        printf("%lld %d %d\n",tot,minans,maxans);
    }
    return 0;
}

BZOJ2286: [Sdoi2011]消耗战 LCA单调性+树形dp

思路:

首先对于一次询问进行一次朴素的树形dp极为容易.不过这样需要\(O(n)\)的时间复杂度,这样总的时间复杂度为\(O(qn)\),无法通过.

我们能否对于一次询问,将时间复杂度限定至仅与关键点数有关呢?

 

我们考虑构造一个树结构,仅仅将关键点连接起来,并适当添加部分非关键点,我们如果在这样减缩后的树上做dp,就能使得复杂度只与关键点数有关.

我们利用一个单调栈维护树上的一条深度单调递增的链,当我们要添加的点与栈顶的点的lca深度小于栈顶时,我们不断弹栈并在这个过程中顺便维护此时树的形态.

容易发现,对于每次添加,至多在新树上添加一个点,因此新树的点数只与关键点线性相关.

 

接下来在新树上作朴素的树形dp就行了.

 

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
 
namespace Fio{
    inline int getc(){
        static const int L=1<<15;static char buf[L],*S=buf,*T=buf;
        if(S==T){T=(S=buf)+fread(buf,1,L,stdin);if(S==T)return EOF;}
        return*S++;
    }
    template<typename T>inline void Get(T&x){
        int c;while(!isdigit(c=getc())&&c!='-');bool sign=c=='-';
        x=sign?0:c-'0';while(isdigit(c=getc()))x=(x<<1)+(x<<3)+c-'0';
        if(sign)x=-x;
    }
    char buf[5000010],*o=buf;
    template<typename T>inline void print(T x){
        static int stk[100];int top=0;if(x<0)*o++='-',x=-x;
        if(!x)*o++=48;else{for(;x;x/=10)stk[++top]=x%10;for(int i=top;i>=1;--i)*o++=48+stk[i];}
        *o++='\n';
    }
    inline void Final(){fwrite(buf,1,o-buf,stdout);}
}
 
#define N 250010
int head[N],next[N<<1],end[N<<1],len[N<<1];
inline void addedge(int a,int b,int x){
    static int q=1;end[q]=b,next[q]=head[a],len[q]=x,head[a]=q++;
}
inline void make(int a,int b,int x){addedge(a,b,x),addedge(b,a,x);}
 
int pa[N][21],w[N][21],dfn[N],id,dep[N];
void dfs(int x,int fa){
    dfn[x]=++id;
    for(int j=head[x];j;j=next[j])if(end[j]!=fa){
        dep[end[j]]=dep[x]+1,pa[end[j]][0]=x,w[end[j]][0]=len[j],dfs(end[j],x);
    }
}
int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;--i)if(dep[pa[x][i]]>=dep[y])x=pa[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;--i)if(pa[x][i]!=pa[y][i])x=pa[x][i],y=pa[y][i];
    return pa[x][0];
}
long long calc(int son,int Anc){
    int res=100010;for(int i=20;i>=0;--i)if(dep[pa[son][i]]>=dep[Anc])res=min(res,w[son][i]),son=pa[son][i];return res;
}
 
int seq[N],siz;
 
inline bool cmp(const int&a,const int&b){return dfn[a]<dfn[b];}
 
int stk[N],top,Anc[N];
 
struct Array{
    long long sum[N];int t[N];
    inline void modify(int x,int add,int v){
        if(t[x]!=v)t[x]=v,sum[x]=0;sum[x]+=add;
    }
    inline long long ask(int x,int v){
        if(t[x]!=v)t[x]=v,sum[x]=0;return sum[x];
    }
}f,IsImp;int nowv;
 
int main(){
    #ifndef ONLINE_JUDGE
    freopen("tt.in","r",stdin);
    #endif
    using namespace Fio;
    int n;Fio::Get(n);
    register int i,j;int a,b,x;for(i=1;i<n;++i)Get(a),Get(b),Get(x),make(a,b,x);
    dep[1]=1,dfs(1,-1);
    for(j=1;j<=20;++j)
        for(i=1;i<=n;++i)pa[i][j]=pa[pa[i][j-1]][j-1],w[i][j]=min(w[i][j-1],w[pa[i][j-1]][j-1]);
    int Q;Get(Q);
    while(Q--){
        Get(siz);for(i=1;i<=siz;++i)Get(seq[i]);++nowv;for(i=1;i<=siz;++i)IsImp.modify(seq[i],1,nowv);seq[++siz]=1;
        sort(seq+1,seq+siz+1,cmp);int end=siz;
        for(top=0,i=1;i<=siz;++i){
            if(!top)stk[++top]=seq[i];
            else{
                int Lca=lca(stk[top],seq[i]);Anc[seq[i]]=Lca;
                while(dep[stk[top]]>dep[Lca]){if(dep[stk[top-1]]<=dep[Lca])Anc[stk[top]]=Lca;--top;}
                if(stk[top]!=Lca)Anc[Lca]=stk[top],seq[++end]=stk[++top]=Lca;
                stk[++top]=seq[i];
            }
        }
        sort(seq+1,seq+end+1,cmp);
        //for(i=1;i<=siz;++i)printf("%d ",seq[i]);puts("");
        long long dis;
        for(i=end;i>=1;--i){
            dis=calc(seq[i],Anc[seq[i]]);
            if(IsImp.ask(seq[i],nowv))f.modify(Anc[seq[i]],dis,nowv);else f.modify(Anc[seq[i]],min(dis,f.ask(seq[i],nowv)),nowv);
        }
        print(f.ask(1,nowv));
    }
    return Final(),0;
}


BZOJ2961:共点圆 圆的反演+cdq分治

思路:

(现在我还没有AC这道题,先记录一下思路吧T^T)

昨天膜拜了xhr犇的论文,get了一系列新姿势.

总的来说,保证修改相互独立的数据结构问题可以用如下三种方法来水:

1.对时间分治

题目要求:修改独立,允许离线

我们利用对时间分治,将问题转化为先给出若干修改,然后进行若干询问的形式.这样我们就可以先对修改进行预处理,随后回答每个询问,这样就避免了动态修改.

2.二进制分组

题目要求:修改独立,强制在线

我们按照顺序进行操作,同时维护已经完成的修改的二进制分组,当在后面加入一个修改的时候,我们暴力重构为新的分组;当回答询问时,我们在之前的\(O(\log n)\)的分组中分别累加答案.这样,我们就通过一个\(\log\)的代价避免了动态修改.

3.整体二分

这个与这道题无关就先不说了.

 

那么来说这道题.

首先有两种基本思路:

[1]利用圆的反演转化为动态半平面交问题

我们以圆心为反演中心,合适长度为半径进行反演,那么每个圆都被反演成一条不过原点的直线(在圆内区域形成一个半平面).我们对每个询问点也进行反演,不难发现若询问点在所有圆的并集之内,则反演点应该在所有直线的半平面交之内.

那么现在问题转化为:支持两个操作,加入一个半平面,询问一个点在不在之前插入的半平面的半平面交之内.

{1}貌似可以拿平衡树维护,\(O(n\log n)\),我表示跪了.

{2}按照时间分治,在一次分治内,我们处理出前一半操作中插入的半平面的半平面交,随后去判断后一半操作中的询问点是否在这个半平面交中,若不在,则表示这个询问点不在所有的圆中.于是这样我们可以做到\(O(n\log ^2n)\).

(至于如何快速判断一个点在不在一个凸包中,我们可以任取凸包内的一个点,将凸包上的点按照关于这个点的极角序排序,对于询问点,我们二分得到询问点在凸包上的哪两个点之间,随后利用\(O(1)\)判断询问点在不在三角形中.这样我们就做到了\(O(\log n)\)询问.)

{3}利用二进制分组,维护\(O(\log n)\)个分组的半平面交,容易发现重构半平面交的复杂度为\(O(n\log ^2n)\),对于每一个询问的复杂度也为\(\log ^2n\),因此总复杂度为\(O(n\log ^2n)\).

[2]通过等式变换转化为另一个问题

由于题目保证圆通过原点,我们考虑点\((x_0,y_0)\)在圆心为\((ox,oy)\)的圆内部或边界上的条件:

\[(x_0-ox)^2+(y_0-oy)^2\leq{ox^2+oy^2}\]

\[x_0^2+y_0^2\leq{2x_0{ox}+2y_0{oy}}\]

这样相当于是每一个询问是给定一个半平面,然后问之前出现过的所有圆形成的点是否都在半平面之内.并且要支持加点.

{1}利用平衡树做动态凸包,\(O(n\log n)\),同样是给跪了.

{2}按时间分治,对于每一次分治,我们预处理出前一半操作中的点形成的凸包,然后去判断后面一半操作中的询问半平面是否完全包含凸包.

我们没有必要作凸包,只需\(O(n)\)维护上下凸壳即可.对于后面的询问,我们可以直接二分,也可以预先将所有询问排序随后线性扫描.

这样就可以做到\(O(n\log ^2n)\)或是\(O(n\log n)\).

{3}二进制分组,我们将之前的\(n\)次加点操作分为\(O(\log n)\)组,然后对于每一次询问在每一个维护的结构中二分.这个方法支持强制在线,不过时间复杂度为\(O(n\log ^2n)\).

 

我目前写的做法是[1]{2},不过貌似精度死得很惨.反演半径居然会影响答案.

现在用[2]{2}的方法AC了.时间复杂度\(O(n\log n)\).

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
using namespace std;
 
typedef double f2;
inline f2 sqr(const f2&x){return x*x;}
 
#define N 500010
struct Case{
    int qte,lab;f2 x,y;
    bool operator<(const Case&B)const{
        if(qte!=B.qte)return qte<B.qte;
        if(qte==0)return x<B.x||(x==B.x&&y<B.y);
        if(qte==1)return(-x/y)<(-B.x/B.y);
    }
}q[N],ql[N],qr[N],up[N],down[N];
int cntl,cntr,topup,topdown;
 
struct Point{
    f2 x,y;
    Point(){}
    Point(f2 _x,f2 _y):x(_x),y(_y){}
    Point operator-(const Point&B)const{return Point(B.x-x,B.y-y);}
};
f2 cross(const Point&A,const Point&B){return A.x*B.y-A.y*B.x;}
struct Line{
    f2 A,B,C;
};
inline Line Getline(const Case&c){
    Line l;
    l.A=2*c.x,l.B=2*c.y,l.C=sqr(c.x)+sqr(c.y);
    return l;
}
inline bool onleft(const Line&l,const Point&p){
    return l.A*p.x+l.B*p.y>=l.C;
}
bool OK[N];
 
inline void Solve(int l,int r){
    if(l==r)return;
    int mid=(l+r)>>1;
    register int i,j;
    for(cntl=cntr=0,i=l;i<=r;++i)if(q[i].lab<=mid)ql[++cntl]=q[i];else qr[++cntr]=q[i];
    int id=l;for(i=1;i<=cntl;++i)q[id++]=ql[i];for(i=1;i<=cntr;++i)q[id++]=qr[i];
    int cnt=0;for(i=l;i<=mid;++i)cnt+=(q[i].qte==0);
    if(cnt){
        for(topup=topdown=0,i=l;i<=mid&&q[i].qte==0;++i){
            while(topup>1&&(q[i].y-up[topup].y)*(up[topup].x-up[topup-1].x)>=(up[topup].y-up[topup-1].y)*(q[i].x-up[topup].x))--topup;
            up[++topup]=q[i];
            while(topdown>1&&(q[i].y-down[topdown].y)*(down[topdown].x-down[topdown-1].x)<=(down[topdown].y-down[topdown-1].y)*(q[i].x-down[topdown].x))--topdown;
            down[++topdown]=q[i];
        }
        int ins;
        for(ins=mid+1;ins<=r;++ins)if(q[ins].qte==1)break;
        if(ins<=r){
            i=ins;
            for(j=1;j<=topdown;++j){
                for(;i<=r&&(j==topdown||-q[i].x/q[i].y*(down[j+1].x-down[j].x)<=(down[j+1].y-down[j].y));++i)
                    if(!onleft(Getline(q[i]),Point(down[j].x,down[j].y)))OK[q[i].lab]=0;   
            }
            i=r;
            for(j=1;j<=topup;++j){
                for(;i>=ins&&(j==topup||-q[i].x/q[i].y*(up[j+1].x-up[j].x)>=up[j+1].y-up[j].y);--i)
                    if(!onleft(Getline(q[i]),Point(up[j].x,up[j].y)))OK[q[i].lab]=0;
            }
        }
    }
    Solve(l,mid),Solve(mid+1,r);
}
 
int main(){
    int n;scanf("%d",&n);
    register int i;for(i=1;i<=n;++i)scanf("%d%lf%lf",&q[i].qte,&q[i].x,&q[i].y),q[i].lab=i;
    for(i=1;i<=n;++i)if(q[i].qte==1)OK[i]=1;
    for(i=1;i<=n&&q[i].qte==1;++i)OK[i]=0;
    sort(q+1,q+n+1);
    Solve(1,n);
    for(i=1;i<=n;++i)if(q[i].qte==1)puts(OK[i]?"Yes":"No");
    return 0;
}

(另外此题真的非常卡精度T^T)