china-final F Mr. Panda and Fantastic Beasts (后缀自动机+字典序最小的最短路)

题目链接:

http://codeforces.com/gym/101194

 

题意:

给n个字符串s1,s2......sn,问只在第一个字符串中出现的最短子串,多个答案输出字典序最小的子串。

 

思路:

把n个串用'z'+1连接起来建立后缀自动机,把s2到sn的母串上所有位置都标记cnt=1,如果parent树的子树和求完后,某个状态的cnt==0,那么所有能到达该状态的串都只在s1中出现过,于是求从root到这些cnt==0的状态的字典序最小的最短路就好了。

Time:420ms

 

#include<bits/stdc++.h>
using namespace std;

const int N=600005;

int tot,root,last;
int pa[N],deep[N],ch[N][27],cnt[N];
int newnode(int _deep)
{
	tot++;
	memset(ch[tot],0,sizeof(ch[tot]));
	deep[tot]=_deep;
	cnt[tot]=0;
	return tot;
}
void init()
{
	tot=0;
	root=newnode(0);
	last=root;
}
void insert(int w)
{
	int np=newnode(deep[last]+1);
	int u=last;
	while(u&&!ch[u][w]) ch[u][w]=np,u=pa[u];
	if (!u) pa[np]=root;
	else
	{
		int v=ch[u][w];
		if (deep[u]+1==deep[v]) pa[np]=v;
		else
		{
			int nv=newnode(deep[u]+1);
			memcpy(ch[nv],ch[v],sizeof(ch[v]));
			pa[nv]=pa[v]; pa[v]=pa[np]=nv;
			while(u&&ch[u][w]==v) ch[u][w]=nv,u=pa[u];
		}
	}
	last=np;
}
int sum[N],stk[N];
void topsort()
{
	for (int i=0;i<=deep[last];i++) sum[i]=0;
	for (int i=1;i<=tot;i++) sum[deep[i]]++;
	for (int i=1;i<=deep[last];i++) sum[i]+=sum[i-1];
	for (int i=1;i<=tot;i++) stk[sum[deep[i]]--]=i;
	for (int i=tot;i>=2;i--) cnt[pa[stk[i]]]+=cnt[stk[i]];
}

bool vis[N];
bool fst[N];
char s[N];
char st[N];
int pre[N];
int pc[N];

int bfs(int S)
{
	queue<int>Q;
	vis[S]=1;
	Q.push(S);
	while(!Q.empty())
	{
		int u=Q.front(); Q.pop();
		for (int w=0;w<26;w++)
		{
			int v=ch[u][w];
			if (v&&fst[v]&&!vis[v])
			{
				pre[v]=u;
				pc[v]=w;
				if (cnt[v]==0) return v;
				vis[v]=1;
				Q.push(v);
			}
		}
	}
	return -1;
}
int main()
{
	int t; scanf("%d",&t);
	for (int cas=1;cas<=t;cas++)
	{
		init();
        int n; scanf("%d",&n);
        scanf("%s",st+1);
		for (int i=1;st[i];i++) insert(st[i]-'a');
		for (int i=2;i<=n;i++)
		{
			scanf("%s",s+1);
			insert(26); cnt[last]=1;
			for (int j=1;s[j];j++)
				insert(s[j]-'a'),cnt[last]=1;
		}
		topsort();
		for (int i=1;i<=tot;i++) vis[i]=fst[i]=0;
		int now=1;
		for (int i=1;st[i];i++)
		{
			now=ch[now][st[i]-'a'];
			int tmp=now;
			while(tmp&&!fst[tmp]) fst[tmp]=1,tmp=pa[tmp];
		}
		printf("Case #%d: ",cas);
		int ret=bfs(1);
		if (ret!=-1)
		{
			int cnt=0;
			for (int i=ret;i!=1;i=pre[i]) s[++cnt]=pc[i]+'a';
			for (int i=cnt;i>=1;i--) printf("%c",s[i]);
			printf("\n");
		}
		else printf("Impossible\n");
	}
}

 

后缀自动机求字符串s1以i结尾的子串和s2到sn连接起来的串的最长公共子串,这个子串再向左延伸一个字符就是以i结尾的最短的只在s1中出现的子串。求出答案后暴力找字典序最小的串,要是答案太多可能会超时,然而数据没有卡这个时间。

Time:358ms

#include<bits/stdc++.h>
using namespace std;

const int N=600005;

int tot,root,last;
int pa[N],deep[N],ch[N][27],cnt[N];
int newnode(int _deep)
{
	tot++;
	memset(ch[tot],0,sizeof(ch[tot]));
	deep[tot]=_deep;
	cnt[tot]=0;
	return tot;
}
void init()
{
	tot=0;
	root=newnode(0);
	last=root;
}
void insert(int w)
{
	int np=newnode(deep[last]+1);
	int u=last;
	while(u&&!ch[u][w]) ch[u][w]=np,u=pa[u];
	if (!u) pa[np]=root;
	else
	{
		int v=ch[u][w];
		if (deep[u]+1==deep[v]) pa[np]=v;
		else
		{
			int nv=newnode(deep[u]+1);
			memcpy(ch[nv],ch[v],sizeof(ch[v]));
			pa[nv]=pa[v]; pa[v]=pa[np]=nv;
			while(u&&ch[u][w]==v) ch[u][w]=nv,u=pa[u];
		}
	}
	last=np;
}
int sum[N],stk[N];
void topsort()
{
	for (int i=0;i<=deep[last];i++) sum[i]=0;
	for (int i=1;i<=tot;i++) sum[deep[i]]++;
	for (int i=1;i<=deep[last];i++) sum[i]+=sum[i-1];
	for (int i=1;i<=tot;i++) stk[sum[deep[i]]--]=i;
	for (int i=tot;i>=2;i--) cnt[pa[stk[i]]]+=cnt[stk[i]];
}


char s[N];
char st[N];
int f[N];
bool cmp(int p1,int p2,int len)
{
	for (int i=0;i<len;i++)
		if (st[p1+i]!=st[p2+i]) return st[p1+i]<st[p2+i];
	return 1;
}
int main()
{
    int t; scanf("%d",&t);
    for (int cas=1;cas<=t;cas++)
    {
        init();
        int n; scanf("%d",&n);
        scanf("%s",st+1);
        for (int i=2;i<=n;i++)
        {
            scanf("%s",s+1);
            for (int j=1;s[j];j++)
                insert(s[j]-'a');
            insert(26);
        }
        int now=root,step=0;
        int ans=N;
        for (int i=1;st[i];i++)
		{
			while(now&&!ch[now][st[i]-'a']) now=pa[now];
			if (!now) now=1,step=0;
			else step=deep[now]+1,now=ch[now][st[i]-'a'];
			f[i]=step;
			if (i-f[i]>0) ans=min(ans,f[i]);
		}
        printf("Case #%d: ",cas);
        if (ans==N) printf("Impossible\n");
        else
		{
			int id=0;
			for (int i=1;st[i];i++)
				if (i-f[i]>0&&f[i]==ans)
				{
					if (id==0||cmp(i-ans,id-ans,ans+1)) id=i;
				}
			for (int i=id-f[id];i<=id;i++) printf("%c",st[i]);
			printf("\n");
		}
    }
}

 

后缀数组+二分答案,每次判断当前前缀相同的块里是否有s1的后缀并且没有其它串的后缀

Time:483ms

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=300005;

int a[N];
char s[N];
int pos,cut;
struct suffixarray
{
    int sa[N],rk[N],h[N];
    int cnt[N],sa2[N],rk2[N];
    int rlen[N];
    int n;
    bool cmp(int i,int j,int len)
    {
        if (rk2[i]==rk2[j]&&rk2[i+len]==rk2[j+len]) return 0;
        return 1;
    }
    void make(int *s,int len)
    {
        s[len+1]=-1;  //按情况修改
        n=len;
        int m=60000;   //按情况修改
        for (int i=0;i<=m;i++) cnt[i]=0;
        for (int i=1;i<=n;i++) cnt[s[i]]++;
        for (int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
        for (int i=n;i>=1;i--) sa[cnt[s[i]]--]=i;
        int k=1; rk[sa[1]]=1;
        for (int i=2;i<=n;i++)
        {
            if (s[sa[i]]!=s[sa[i-1]]) k++;
            rk[sa[i]]=k;
        }
        rk2[n+1]=0;
        for (int j=1;k<n&&j<=n;j*=2)
        {
            int tot=0;
            for (int i=n-j+1;i<=n;i++) sa2[++tot]=i;
            for (int i=1;i<=n;i++)
                if (sa[i]>j) sa2[++tot]=sa[i]-j;
            m=k;
            for (int i=1;i<=m;i++) cnt[i]=0;
            for (int i=1;i<=n;i++) cnt[rk[i]]++;
            for (int i=2;i<=m;i++) cnt[i]+=cnt[i-1];
            for (int i=n;i>=1;i--) sa[ cnt[rk[sa2[i]]]-- ]=sa2[i];
            for (int i=1;i<=n;i++) rk2[i]=rk[i];
            k=1; rk[sa[1]]=1;
            for (int i=2;i<=n;i++)
            {
                if (cmp(sa[i],sa[i-1],j)) k++;
                rk[sa[i]]=k;
            }
        }
        for (int i=1;i<=n;i++)
        {
            if (rk[i]==1)
            {
                h[rk[i]]=0;  //这里很容易错
                continue;
            }
            k=h[rk[i-1]];
            if (k>0) k--;
            int j=sa[rk[i]-1];
            while(s[i+k]==s[j+k]) k++;
            h[rk[i]]=k;
        }
    }
    void show()
	{
		printf("\n");
		for (int i=1;i<=n;i++)
        {
            for (int j=sa[i];j<=n;j++)
             printf("%c",char(a[j]));
            printf(" %d\n",h[i]);
        }
	}
	int idx;
	bool check(int len)
	{
		bool hv1=0,hv2=0;
		for (int i=1;i<=n;i++)
		{
			if (h[i]<len)
			{
				if (hv1&&!hv2)
				{
					idx=sa[i-1];
					return 1;
				}
				hv1=hv2=0;
				if (rlen[sa[i]]>=len)
				{
					if (sa[i]<cut) hv1=1;
					else hv2=1;
				}
			}
			else
			{
				if (sa[i]<cut) hv1=1;
				else hv2=1;
			}
		}
		return 0;
	}
	void solve(int *s)
	{
		int tmp=n;
		for (int i=n;i>=1;i--)
		{
			if (s[i]>'z') tmp=i,rlen[i]=0;
			else rlen[i]=tmp-i;
		}
		int l=1,r=cut;
		while(l<r)
		{
			int mid=(l+r)/2;
			if (!check(mid)) l=mid+1;
			else r=mid;
		}
		if (l==cut) printf("Impossible\n");
		else
		{
			check(l);
			for (int i=idx;i<idx+l;i++)
				printf("%c",char(s[i]));
			printf("\n");
		}
	}
}SA;
int main()
{
    int t; scanf("%d",&t);
    for (int cas=1;cas<=t;cas++)
    {
        int n; scanf("%d",&n);
        pos=0;
        for (int i=1;i<=n;i++)
		{
			scanf("%s",s+1);
			for (int j=1;s[j];j++)
				a[++pos]=s[j];
			a[++pos]='z'+i;
			if (i==1) cut=pos;
		}
        SA.make(a,pos);
        printf("Case #%d: ",cas);
        //SA.show();
        SA.solve(a);
    }
}

面向对象的计算几何模板

写了四天的模板,纯手打,有了板子过题实在是爽。

#include<bits/stdc++.h>
//#include<cstdio>
//#include<cmath>
//#include<algorithm>
using namespace std;
typedef long double lod;
typedef long long ll;
typedef long double ld;
const ld eps=1e-8;
const ld pi=acos(-1.0);
int sgn(ld x)
{
	if (x<-eps) return -1;
	if (x>eps) return 1;
	return 0;
}

struct P; //点,向量
struct LINE; //线段,射线,直线;
struct CIRCLE;
struct TRIANGLE;
struct POLYGON;

void kr(ld &x)
{
	double t; scanf("%lf",&t);
	x=t;
}
void kr(ll &x)
{
	scanf("%lld",&x);
}
struct P
{
	lod x,y;
	void read()
	{
		kr(x); kr(y);
	}
    P operator+(const P &t)const
    {
        return {x+t.x,y+t.y};
    }
    P operator-(const P &t)const
    {
        return {x-t.x,y-t.y};
    }
    P operator*(ld t)const
    {
    	return {x*t,y*t};
    }
	P operator/(ld t)const
    {
    	return {x/t,y/t};
    }
    lod operator*(const P &t)const
    {
        return x*t.y-y*t.x;
	} //叉积
    lod operator%(const P &t)const
    {
        return x*t.x+y*t.y;
    } //点积
    bool operator<(const P &t)const
    {
    	return sgn(x-t.x)<0||sgn(x-t.x)==0&&sgn(y-t.y)<0;
    }
    bool operator==(const P &t)const
    {
    	return sgn(x-t.x)==0&&sgn(y-t.y)==0;
    }
    ld ang()const
    {
    	return atan2(y,x);
    }
    ld length()const
    {
    	return sqrt(x*x+y*y);
    }
	P rotate(const P &t,ld sita)const
	{
		return {(x-t.x)*cos(sita)-(y-t.y)*sin(sita)+t.x,
				(x-t.x)*sin(sita)+(y-t.y)*cos(sita)+t.y};
	} //逆时针转sita
	ld btang(const P &t)const
	{
		return acos( (*this%t)/length()/t.length() );
	} //向量夹角
	P midvec(const P &t)const
	{
		return (*this)/length()+t/t.length();
	} //角平分向量
};

struct LINE
{
	P p1,p2;
	void read()
	{
		p1.read(); p2.read();
	}
	LINE midLINE()
	{
        P midp=(p1+p2)/2;
        P v=p2-p1;
        v=v.rotate({0,0},pi/2);
        return {midp,midp+v};
	} //中垂线
    bool have1(const P &p)const
    {
    	return sgn( (p-p1)*(p-p2) )==0&&sgn( (p-p1)%(p-p2) )<=0;
    } //线段上有点
	bool have2(const P &p)const
    {
    	return sgn( (p-p1)*(p-p2) )==0&&sgn( (p-p1)%(p2-p1) )>=0;
    } //射线上有点
	bool have3(const P &p)const
    {
    	return sgn( (p-p1)*(p-p2) )==0;
    } //直线上有点
    lod areawith(const P &p)const
    {
    	return abs( (p1-p)*(p2-p)/2 );
    } //线段和点围成面积
    P vecfrom(const P &p)const
    {
		P v=(p2-p1);
		v=v.rotate({0,0},pi/2);
		ld s1=(p1-p)*(p2-p);
		ld s2=v*(p2-p1);
		v=v*(s1/s2);
		return v;
    }//点到直线垂足的向量
    P footfrom(const P &p)const
    {
		P v=vecfrom(p);
		return p+v;
    } //点到直线垂足
    ld dis1from(const P &p)const
    {
    	P foot=footfrom(p);
		if (have1(foot)) return (foot-p).length();
		return min( (p1-p).length(),(p2-p).length());
    }//点到线段距离
    ld dis2from(const P &p)const
    {
    	P foot=footfrom(p);
		if (have2(foot)) return (foot-p).length();
		return (p1-p).length();
    }//点到射线距离
    ld dis3from(const P &p)const
    {
		return vecfrom(p).length();
    }//点到直线距离
    P symP(const P &p)const
    {
        P v=vecfrom(p);
        return p+v*2;
    } //点关于直线的对称点



	//1线段 2射线 3直线
	bool isct11(const LINE &L)const
	{
		P a1=p1,a2=p2;
		P b1=L.p1,b2=L.p2;
		if (sgn( max(a1.x,a2.x)-min(b1.x,b2.x) )<0||
			sgn( max(b1.x,b2.x)-min(a1.x,a2.x) )<0||
			sgn( max(a1.y,a2.y)-min(b1.y,b2.y) )<0||
			sgn( max(b1.y,b2.y)-min(a1.y,a2.y) )<0)
				return 0;
		lod tmp1=(a2-a1)*(b1-a1);
		lod tmp2=(a2-a1)*(b2-a1);
		if (sgn(tmp1)<0&&sgn(tmp2)<0||sgn(tmp1)>0&&sgn(tmp2)>0) return 0;
		tmp1=(b2-b1)*(a1-b1);
		tmp2=(b2-b1)*(a2-b1);
		if (sgn(tmp1)<0&&sgn(tmp2)<0||sgn(tmp1)>0&&sgn(tmp2)>0) return 0;
		return 1;
	}
	bool isct21(const LINE &L)const
	{
		P v=p2-p1;
		P a=p1;
		P b1=L.p1,b2=L.p2;
		lod tmp1=v*(b1-a);
		lod tmp2=v*(b2-a);
		if (sgn(tmp1)<0&&sgn(tmp2)<0||sgn(tmp1)>0&&sgn(tmp2)>0) return 0;
		if (tmp1>tmp2) swap(b1,b2);
		if (sgn( (b1-a)*(b2-a) )>0) return 1;
		if (sgn( (b1-a)*(b2-a) )<0) return 0;
		//最后排除共线但不相交的情况
		return L.have1(a)||have2(b1)||have2(b2);
	}
	bool isct31(const LINE &L)const
	{
		P v=p2-p1;
		P a=p1;
		lod tmp1=v*(L.p1-a);
		lod tmp2=v*(L.p2-a);
		if (sgn(tmp1)<0&&sgn(tmp2)<0||sgn(tmp1)>0&&sgn(tmp2)>0) return 0;
		return 1;
	}
	bool isct22(const LINE &L)const
	{
		if (have2(L.p1)||L.have2(p1)) return 1;
		P v=vecfrom(L.p1);
		if (sgn( v%(L.p2-L.p1) )<=0) return 0;
		v=L.vecfrom(p1);
		if (sgn( v%(p2-p1) )<=0) return 0;
		return 1;
	}
	bool isct32(const LINE &L)const
	{
		if (have3(L.p1)) return 1;
		P v=vecfrom(L.p1);
		if (sgn( v%(L.p2-L.p1) )<=0) return 0;
		return 1;
	}
	bool isct33(const LINE &L)const
	{
		if (have3(L.p1)) return 1;
		return sgn( (p2-p1)*(L.p2-L.p1) )!=0;
	}

	//前提是不重合且有交点,p1沿p2-p1方向到达L上的长度,负数表示反向
	//直线交多边形需要用到
	ld dis33(const LINE &L)const
	{
		return (L.p1-p1)*(L.p2-p1) / ( (p2-p1)*(L.p2-L.p1) )
				* (p2-p1).length();
	}
	P isctPoint(const LINE &L)const
	{
		ld len=dis33(L);
		P v=p2-p1;
		return p1+v*(len/v.length());
	}//直线交点坐标

};

struct CIRCLE
{
	P cent;
	lod r;
	void read()
	{
		cent.read(); kr(r);
	}
	ld area()const
	{
		return pi*r*r;
	}
	bool have(const P &p)const
	{
		return sgn( (p-cent).length()-r ) <=0;
	}//点在圆内
	P LeftcutPoint(const P &p)const
	{
		P v=p-cent;
        ld sita=acos(r/v.length());
        v=v.rotate({0,0},sita);
        v=v/v.length()*r;
        return cent+v;
	}//左切点
	P RightcutPoint(const P &p)const
	{
		P v=p-cent;
        ld sita=acos(r/v.length());
        v=v.rotate({0,0},-sita);
        v=v/v.length()*r;
        return cent+v;
	}//右切点
	bool isct3(const LINE &L)const
	{
		return sgn(L.dis3from(cent)-r)<=0;
	}//圆与直线相交
	P vecto(const LINE &L)const
	{
		P v=L.p2-L.p1;
		v=v.rotate({0,0},pi/2);
		if (sgn(v%(L.p1-cent))<0) v=v.rotate({0,0},pi);
		return v/v.length()*r;
	}//从圆心垂直射向直线的向量,长度为r
	P LeftisctPoint(const LINE &L)const
	{
		P v=vecto(L);
		ld d=L.dis3from(cent);
		ld sita=acos(d/r);
		return cent+v.rotate({0,0},sita);
	}//左交点
	P RightisctPoint(const LINE &L)const
	{
		P v=vecto(L);
		ld d=L.dis3from(cent);
		ld sita=acos(d/r);
		return cent+v.rotate({0,0},-sita);
	}//右交点
	bool separate(const CIRCLE &C)const
	{
		ld d=(cent-C.cent).length();
		return sgn(r+C.r-d)<=0;
	}//相离
	bool contain(const CIRCLE &C)const
	{
		if (sgn(r-C.r)<0) return 0;
		ld d=(cent-C.cent).length();
		return sgn( d+C.r-r)<=0;
	}//包含
	ld isctarea(const CIRCLE &C)const
	{
		if (separate(C)) return 0;
		if (contain(C)) return C.area();
		if (C.contain(*this)) return area();
		ld d=(cent-C.cent).length();
		ld ang1=acos( (r*r+d*d-C.r*C.r)/2/r/d );
		ld ang2=acos( (C.r*C.r+d*d-r*r)/2/C.r/d);
		return ang1*r*r+ang2*C.r*C.r
			   -r*r*sin(2*ang1)/2-C.r*C.r*sin(2*ang2)/2;
	}//圆相交面积,分两个三角形减,否则被codeforces卡精度
};


struct TRIANGLE
{
	P a[3];
	void read()
	{
		for (int i=0;i<3;i++) a[i].read();
	}
	ld area()const
	{
		ld ret=0;
		for (int i=0;i<3;i++)
			ret+=a[i]*a[(i+1)];
		return abs(ret);
	}
	P center1()const
	{
		return (a[0]+a[1]+a[2])/3;
	}//重心
	P center2()const
	{
		LINE L1={a[0],a[1]};
		LINE L2={a[1],a[2]};
		return L1.midLINE().isctPoint(L2.midLINE());
	}//外心
	P center3()const
	{
		P v0=(a[1]-a[0]).midvec(a[2]-a[0]);
		P v1=(a[0]-a[1]).midvec(a[2]-a[1]);
		LINE L1={a[0],a[0]+v0};
		LINE L2={a[1],a[1]+v1};
		return L1.isctPoint(L2);
	}//内心
	bool have(const P &p)const
	{
		lod tmp1=0;
		for (int i=0;i<3;i++)
			tmp1+=a[i]*a[(i+1)%3];
		tmp1=abs(tmp1);
		lod tmp2=0;
		for (int i=0;i<3;i++)
			tmp2+=abs( (a[i]-p)*(a[(i+1)%2]-p) );
		return sgn(tmp1-tmp2)==0;
	}//点在三角形内
	bool isctLINE1(const LINE &L)const
	{
		for (int i=0;i<3;i++)
		{
			LINE R={a[i],a[(i+1)%3]};
			if (L.isct11(R)) return 1;
		}
		return 0;
	}//与线段相交
	bool isctLINE2(const LINE &L)const
	{
		for (int i=0;i<3;i++)
		{
			LINE R={a[i],a[(i+1)%3]};
			if (L.isct21(R)) return 1;
		}
		return 0;
	}//与射线相交
	bool isctLINE3(const LINE &L)const
	{
		for (int i=0;i<3;i++)
		{
			LINE R={a[i],a[(i+1)%3]};
			if (L.isct31(R)) return 1;
		}
		return 0;
	}//与直线相交
	P FirstisctPoint(const LINE &L)const;//与有向直线第一个交点
	P SecondisctPoint(const LINE &L)const;//与有向直线第二个交点

	bool isct(const TRIANGLE &TRI)const
	{
		for (int i=0;i<3;i++)
		{
			LINE L={a[i],a[(i+1)%3]};
			for (int j=0;j<3;j++)
			{
				LINE R={TRI.a[j],TRI.a[(j+1)%3]};
				if (L.isct11(R)) return 1;
			}
		}
		return 0;
	}//三角形相交
	bool contain(const TRIANGLE &TRI)const
	{
		for (int j=0;j<3;j++)
			if (!have(TRI.a[j])) return 0;
		return 1;
	}//当前三角形包含TRI
	bool separate(const TRIANGLE &TRI)const
	{
		return !isct(TRI)&&!contain(TRI)&&!TRI.contain(*this);
	}//互相分离
	ld isctarea(const TRIANGLE &TRI)const;
};




const int POLNUM=1005;
struct PL
{
	ld len;
	int v;
}stk[POLNUM];
int top;
bool cmplen(const PL &a,const PL &b)
{
	return a.len<b.len;
}
P cent;
bool cmpang(const P &p1,const P &p2)
{
	int tmp=sgn( (p1-cent).ang() - (p2-cent).ang() );
	if (tmp!=0) return tmp<0;
	return (p1-cent).length() < (p2-cent).length();
}
struct POLYGON
{
	int n;
	P a[POLNUM];
	void read(int k)
	{
		for (int i=1;i<=k;i++) a[i].read();
		n=k;
	}
	void ChangetoConvex()
	{
		for (int i=2;i<=n;i++)
			if (a[i].x<a[1].x||a[i].x==a[1].x&&a[i].y<a[1].y)
				swap(a[1],a[i]);
		cent=a[1];
		sort(a+2,a+n+1,cmpang);
		int top=2;
		for (int i=3;i<=n;i++)
		{
			while(top>=2&&
				sgn((a[top]-a[top-1])*(a[i]-a[top]))<=0 )
					top--;
			a[++top]=a[i];
		}
		n=top;
	}//变凸包!(逆时针)
	ld Clength()const
	{
		ld ret=0;
		for (int i=2;i<=n;i++) ret+=(a[i]-a[i-1]).length();
		if (n>2) ret+=(a[1]-a[n]).length(); //防止n==2重复计算
		return ret;
	}//周长
	bool have(const P p)
	{
		int k,d1,d2,wn=0;
		a[0]=a[n];
		for (int i=1;i<=n;i++)
		{
			LINE L={a[i-1],a[i]};
			if (L.have1(p)) return 1;
			k=sgn( (a[i]-a[i-1])*(p-a[i-1]) );
			d1=sgn( a[i-1].y-p.y );
			d2=sgn( a[i].y-p.y );
			if (k>0&&d1<=0&&d2>0) wn++;
			if (k<0&&d2<=0&&d1>0) wn--;
		}
		return wn!=0;
	}//点在多边形内
	ld cutlength(const LINE &L)
	{
		a[0]=a[n]; top=0;
		for (int i=1;i<=n;i++)
		{
			LINE R={a[i-1],a[i]};
			lod s1=sgn( (L.p2-L.p1)*(R.p1-L.p1) );
			lod s2=sgn( (L.p2-L.p1)*(R.p2-L.p1) );
			if (s1<0&&s2<0||s1==0&&s2==0||s1>0&&s2>0) continue;
			if (s1<s2) stk[++top]={L.dis33(R),(s1!=0&&s2!=0?2:1)};
			else stk[++top]={L.dis33(R),(s1!=0&&s2!=0?-2:-1)};
		}
		sort(stk+1,stk+top+1,cmplen);
		int cnt=0;
		ld ret=0;
		for (int i=1;i<=top;i++)
		{
			if (cnt) ret+=stk[i].len-stk[i-1].len;
			cnt+=stk[i].v;
		}
		return ret;
	}//直线和多边形的交线总长,两个多边形一顺一逆可求只被覆盖一次的长度,多个同方向可求直线与多个多边形的交线总长
	bool isct(const POLYGON &POL)const
	{
		for (int i=2;i<=n;i++)
			for (int j=2;j<=POL.n;j++)
			{
				LINE L1={a[i-1],a[i]};
				LINE L2={POL.a[j-1],POL.a[j]};
				if (L1.isct11(L2)) return 1;
			}
		return 0;
	}//多边形相交,当前是两链相交判断
	ld isctarea(const POLYGON &POL)const;
};
/////////////////////////////////////////
//hdu5572

P a,v,b;
CIRCLE c;

bool solve()
{

	LINE L={a,a+v};
	P foot=L.footfrom(c.cent);
	if (c.have(foot)&&L.have2(foot))
	{
		P p1=c.LeftisctPoint(L);
		P p2=c.RightisctPoint(L);
		P bc;
		if ((p1-a).length()<(p2-a).length()) bc=p1;
		else bc=p2;
		LINE R={c.cent,bc};
		P a2=R.symP(a);
		LINE L1={a,bc},L2={bc,a2};
		if (L1.have1(b)||L2.have2(b)) return 1;
		return 0;
	}
	else return L.have2(b);
}
int main()
{
	int t; scanf("%d",&t);
	for (int cas=1;cas<=t;cas++)
	{
		c.read();
		a.read(); v.read();
		b.read();
		printf("Case #%d: %s\n",cas,solve()?"Yes":"No");
	}
}

HDU 5957 环套树+线段树维护bfs序

题意:

n个点n条边的连通图,边长度都为1,没有重边自环,两种操作:

1.把与x距离小于等于k的所有点的权值增加d  (k<=2)

2.问离x距离小于等于k的所有点的权值和 (k<=2)

 

思路:

首先这是个环套树,找到环后从环上的每个点开始bfs,处理出每个点向子树走k步的bfs序区间,一层层维护就好了。

分类讨论比较麻烦。

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
ll lazy[4*N],sum[4*N];
int n;
void init()
{
    memset(lazy,0,sizeof(lazy));
    memset(sum,0,sizeof(sum));
}
void up(int x)
{
	sum[x]=sum[x*2]+sum[x*2+1];
}
void down(int x,int l,int r)
{
	if (lazy[x])
	{
		int mid=(l+r)/2;
		sum[x*2]+=(mid-l+1)*lazy[x];
		sum[x*2+1]+=(r-mid)*lazy[x];
		lazy[x*2]+=lazy[x];
		lazy[x*2+1]+=lazy[x];
		lazy[x]=0;
	}
}
void add(int i,int l,int r,int x,int y,int w)
{
	if (x<=l&&r<=y)
	{
		sum[i]+=w*(r-l+1);
		lazy[i]+=w;
		return;
	}
	down(i,l,r);
	int mid=(l+r)/2;
	if (x<=mid) add(i*2,l,mid,x,y,w);
	if (y>mid) add(i*2+1,mid+1,r,x,y,w);
	up(i);
}
ll get(int i,int l,int r,int x,int y)
{
	if (x<=l&&r<=y) return sum[i];
	down(i,l,r);
	int mid=(l+r)/2;
	ll ret=0;
	if (x<=mid) ret+=get(i*2,l,mid,x,y);
	if (y>mid) ret+=get(i*2+1,mid+1,r,x,y);
	return ret;
}

void ad(int l,int r,int w)
{
	if (l>r) return;
	add(1,1,n,l,r,w);
}
ll gt(int l,int r)
{
	if (l>r) return 0;
	return get(1,1,n,l,r);
}

bool vis[N],on[N];
int l[N],r[N];
vector<int>link[N];
int dfs1(int u,int fa)
{
	vis[u]=1;
	for (int i=0;i<link[u].size();i++)
	{
		int v=link[u][i];
		if (v==fa) continue;
		l[v]=u;
		if (vis[v]) return v;
		int tmp=dfs1(v,u);
		if (tmp) return tmp;
	}
	return 0;
}
int tot;
int q[N],bl[N][3],br[N][3],fa[N];
void bfs(int u)
{
	vis[u]=1;
	q[++tot]=u;
	bl[u][0]=tot; bl[u][1]=bl[u][2]=N;
	br[u][0]=tot; br[u][1]=br[u][2]=0;
	int head=tot;
	while(head<=tot)
	{
		int u=q[head]; head++;
		for (int i=0;i<link[u].size();i++)
		{
			int v=link[u][i];
			if (vis[v]||on[v]) continue;
			q[++tot]=v;
			bl[v][0]=tot; bl[v][1]=bl[v][2]=N;
			br[v][0]=tot; br[v][1]=br[v][2]=0;
			fa[v]=u;
			vis[v]=1;
		}
	}
}

void look(int n)
{
	for (int i=1;i<=n;i++)
	{
		printf("%d %lld\n",i,get(1,1,n,bl[i][0],br[i][0]));
	}
}
char s[10];
int main()
{
	int t; scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;i++)
		{
			int x,y; scanf("%d%d",&x,&y);
			link[x].push_back(y);
			link[y].push_back(x);
		}

		for (int i=1;i<=n;i++) vis[i]=0,on[i]=0;
		int root=dfs1(1,1);
		r[l[root]]=root; on[root]=1;
        for (int i=l[root];i!=root;i=l[i])  r[l[i]]=i,on[i]=1;

        tot=0;
        for (int i=1;i<=n;i++) vis[i]=0;
        bfs(root);
		for (int i=l[root];i!=root;i=l[i]) bfs(i);
        for (int k=1;k<=2;k++)
			for (int i=tot;i>=1;i--)
			{
				int v=q[i];
				if (on[v]) continue;
				int u=fa[v];
				bl[u][k]=min(bl[u][k],bl[v][k-1]);
				br[u][k]=max(br[u][k],br[v][k-1]);
			}

		init();
        int m; scanf("%d",&m);
		while(m--)
		{
			//look(n);
			scanf("%s",s);
			if (s[0]=='M')
			{
				int u,k,w; scanf("%d%d%d",&u,&k,&w);
				if (k==0) ad(bl[u][0],br[u][0],w);
				if (k==1)
				{
					ad(bl[u][0],br[u][0],w);
					ad(bl[u][1],br[u][1],w);//
					if (on[u])
					{
						int lv=l[u],rv=r[u];
						ad(bl[lv][0],br[lv][0],w);
						ad(bl[rv][0],br[rv][0],w);
					}
					else
					{
						int v=fa[u];
						ad(bl[v][0],br[v][0],w);
					}
				}
				if (k==2)
				{
					ad(bl[u][0],br[u][0],w);
					ad(bl[u][1],br[u][1],w);
					ad(bl[u][2],br[u][2],w);
					if (on[u])
					{
						int lv=l[u];
						ad(bl[lv][0],br[lv][0],w);
						ad(bl[lv][1],br[lv][1],w);
						int rv=r[u];
						ad(bl[rv][0],br[rv][0],w);
						ad(bl[rv][1],br[rv][1],w);
						lv=l[lv];
						if (lv!=rv)
							ad(bl[lv][0],br[lv][0],w);
						else lv=r[lv];
						rv=r[rv];
						if (rv!=lv)
							ad(bl[rv][0],br[rv][0],w);
					}
					else
					{
						int v=fa[u];
						ad(bl[v][0],br[v][0],w);
						ad(bl[v][1],br[v][1],w);
						ad(bl[u][0],br[u][0],-w);
						if (on[v])
						{
							int lv=l[v],rv=r[v];
							ad(bl[lv][0],br[lv][0],w);
							ad(bl[rv][0],br[rv][0],w);
						}
						else
						{
							v=fa[v];
							ad(bl[v][0],br[v][0],w);
						}
					}
				}
			}
			if (s[0]=='Q')
			{
				int u,k,w; scanf("%d%d",&u,&k);
				ll ret=0;
				if (k==0) ret+=gt(bl[u][0],br[u][0]);
				if (k==1)
				{
					ret+=gt(bl[u][0],br[u][0]);
					ret+=gt(bl[u][1],br[u][1]);
					if (on[u])
					{
						int lv=l[u],rv=r[u];
						ret+=gt(bl[lv][0],br[lv][0]);
						ret+=gt(bl[rv][0],br[rv][0]);
					}
					else
					{
						int v=fa[u];
						ret+=gt(bl[v][0],br[v][0]);
					}
				}
				if (k==2)
				{
					ret+=gt(bl[u][0],br[u][0]);
					ret+=gt(bl[u][1],br[u][1]);
					ret+=gt(bl[u][2],br[u][2]);
					if (on[u])
					{
						int lv=l[u];
						ret+=gt(bl[lv][0],br[lv][0]);
						ret+=gt(bl[lv][1],br[lv][1]);
						int rv=r[u];
						ret+=gt(bl[rv][0],br[rv][0]);
						ret+=gt(bl[rv][1],br[rv][1]);
						lv=l[lv];
						if (lv!=rv)
							ret+=gt(bl[lv][0],br[lv][0]);
						else lv=r[lv];
						rv=r[rv];
						if (rv!=lv)
							ret+=gt(bl[rv][0],br[rv][0]);
					}
					else
					{
						int v=fa[u];
						ret+=gt(bl[v][0],br[v][0]);
						ret+=gt(bl[v][1],br[v][1]);
						ret-=gt(bl[u][0],br[u][0]);
						if (on[v])
						{
							int lv=l[v],rv=r[v];
							ret+=gt(bl[lv][0],br[lv][0]);
							ret+=gt(bl[rv][0],br[rv][0]);
						}
						else
						{
							v=fa[v];
							ret+=gt(bl[v][0],br[v][0]);
						}
					}
				}
				printf("%lld\n",ret);
			}

		}

		for (int i=1;i<=n;i++) link[i].clear();
	}
}
/*
1
8
1 2
2 3
3 4
1 4
2 5
3 6
5 7
5 8
12
MODIFY 8 1 5
MODIFY 8 2 2
QUERY 5 0
QUERY 5 1
QUERY 5 2
MODIFY 7 2 2
QUERY 7 2
MODIFY 3 1 5
MODIFY 2 2 2
QUERY 6 1
MODIFY 4 1 -2
QUERY 2 2
*/

 

HDU5956 The Elder 下凸壳的维护

题意:

一棵树,1为根。每个点只能走向祖先,时间为距离的平方,另外走到祖先后会停留P单位时间。求每个点到达根的最小时间的最大值。

 

思路:

显然dp方程为:f[i]=(d[i]-d[j])^2+P+f[j]  ( j 是 i 的祖先)

转化成向量点积形式:f[i]=d[i]^2+P+(1 , 2*d[i] ) * ( f[j]+d[j]^2 , -d[j] )

注意到深度越大,向量(1 , 2*d[i] )会逆时针旋转

于是维护点集i的所有祖先的  ( f[j]+d[j]^2 , -d[j] ) 的下凸壳,就能求出f[i]的最小值

注意到向量( f[j]+d[j]^2 , -d[j] )只会添加在下凸壳右边,于是只要用个队列来保存就可以了

查询的时候只要一直扔掉左边的点,找到最大值为止,然后退出当前dfs的时候加回来就可以了

如果一个个保存删掉的点,再一个个加回来的话,在菊花图的情况下复杂度就会变成O(n^2),事实上目前网上的题解都是这么做的,估计出题人也没意识到复杂度的问题,因为这看起来很像O(n)。

我的做法是每次dfs记录当前队列的左端点,和右端点,操作完后,事实上只是替换了数组里的tail位置的元素,把head和tail还有这个元素事先保存下来,最后恢复head,tail和这个元素就可以了,这样复杂度就是完美的O(n)了。

也不知道这种做法叫什么,毕竟自己乱想出来的,就叫回溯队列好了233

后来一想这个做法还是n方。。。因为每次head和tail的删除操作都可能是O(n)的,所以要二分查找head和tail分别应该删到哪,这样算法复杂度就是O(nlogn)了

这里如果不用斜率,而把两边的dx都乘到对面去的话是有问题的,因为f[j]+d[j]^2的规模是1e14,2*d[i]的规模是1e7,乘起来理论上会爆,然而数据好像还是没卡掉这种做法

 

代码1:O(n^2)做法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
struct P
{
	ll x,y;
	ll operator*(const P &b)const
	{
		return x*b.x+y*b.y;
	}
}stk[N];
int n,p;
int head,tail;
vector<P>link[N];
ll f[N],d[N];
long double slope(P a,P b)
{
	long double tmp=1.0;
	return tmp*(b.y-a.y)/(b.x-a.x);
}
void dfs(int u,int fa,int deep)
{
	int phead=head,ptail=tail;
	d[u]=deep;
	if (u==1) f[u]=-p;
	else
	{
		P tmp={1,2*d[u]};
		while(tail-head+1>=2&&stk[head]*tmp>=stk[head+1]*tmp) head++;
		f[u]=d[u]*d[u]+p+stk[head]*tmp;
	}
	P tmp={f[u]+d[u]*d[u],-d[u]};
	while(tail-head+1>=2&&
		slope(stk[tail-1],stk[tail])>=slope(stk[tail],tmp)) tail--;
	P mem=stk[tail+1];
	stk[++tail]=tmp;

	for (int i=0;i<link[u].size();i++)
	{
		int v=link[u][i].x;
		if (v==fa) continue;
		dfs(v,u,deep+link[u][i].y);
	}
	tail--;
	stk[tail+1]=mem;
	head=phead; tail=ptail;
}
int main()
{
	int t; scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&p);
		for (int i=1;i<n;i++)
		{
			ll x,y,z;
			scanf("%d%d%lld",&x,&y,&z);
			if (z<0) n/=0;
			link[x].push_back({y,z});
		}
		head=1; tail=0;
		dfs(1,1,0);
		ll ans=0;
		for (int i=1;i<=n;i++) ans=max(ans,f[i]);
		printf("%lld\n",ans);

		for (int i=1;i<=n;i++) link[i].clear();
	}
}

 

代码2:O(nlogn)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
struct P
{
    ll x,y;
    ll operator*(const P &b)const
    {
        return x*b.x+y*b.y;
    }
}stk[N];
int n,p;
int head,tail;
vector<P>link[N];
ll f[N],d[N];
long double slope(P a,P b)
{
    long double tmp=1.0;
    return tmp*(b.y-a.y)/(b.x-a.x);
}
int gethead(P v)
{
	int l=head,r=tail;
	while(l<r)
	{
		int mid=(l+r)/2;
		if (stk[mid]*v>=stk[mid+1]*v) l=mid+1;
		else r=mid;
	}
	return l;
}
int gettail(P v)
{
	int l=head,r=tail;
	while(l<r)
	{
		int mid=(l+r)/2;
		if (slope(stk[mid],stk[mid+1])<slope(stk[mid+1],v)) l=mid+1;
		else r=mid;
	}
	return l;
}
void dfs(int u,int fa,int deep)
{
    int phead=head,ptail=tail;
    d[u]=deep;
    if (u==1) f[u]=-p;
    else
    {
        P tmp={1,2*d[u]};
        head=gethead(tmp);

        //while(tail-head+1>=2&&stk[head]*tmp>=stk[head+1]*tmp) head++;
        f[u]=d[u]*d[u]+p+stk[head]*tmp;
    }
    P tmp={f[u]+d[u]*d[u],-d[u]};
    tail=gettail(tmp);
//    while(tail-head+1>=2&&
//        slope(stk[tail-1],stk[tail])>=slope(stk[tail],tmp)) tail--;
    P mem=stk[tail+1];
    stk[++tail]=tmp;

    for (int i=0;i<link[u].size();i++)
    {
        int v=link[u][i].x;
        if (v==fa) continue;
        dfs(v,u,deep+link[u][i].y);
    }
    tail--;
    stk[tail+1]=mem;
    head=phead; tail=ptail;
}
int main()
{
    int t; scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&p);
        for (int i=1;i<n;i++)
        {
            ll x,y,z;
            scanf("%d%d%lld",&x,&y,&z);
            link[x].push_back({y,z});
        }
        head=1; tail=0;
        dfs(1,1,0);
        ll ans=0;
        for (int i=1;i<=n;i++) ans=max(ans,f[i]);
        printf("%lld\n",ans);

        for (int i=1;i<=n;i++) link[i].clear();
    }
}

FZOJ 2274 二分+有源汇上下界最小流

http://acm.fzu.edu.cn/problem.php?pid=2274

二分差值,求出最小的存在上下界可行流的答案,再跑一次上下界最小流。

这题恶心的地方在于建图从班级流向寝室会TLE,从寝室流向班级就会快很多。

因为只有三层边,dinic里增广次数只有几次,每次dinic复杂度几乎是O(n+m)的。

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

const int N=200005,INF=0x3f3f3f3f;
const int mxN=200005,mxM=500005;
int n,m,k;
int du[N];
int ra[N],rb[N],rc[N],rd[N];
int in[N];
int S,T,SS,TT;
int inflow;

struct graph
{
	int S,T;
    int base[mxN],vec[2*mxM],pre[2*mxM],tot;
    int c[2*mxM];
    int d[mxN],q[mxN];
    bool vis[mxN];
    void init()
    {
    	memset(base,0,sizeof(base));
        tot=1;
    }
    void link(int x,int y,int z)
    {
        vec[++tot]=y; pre[tot]=base[x]; base[x]=tot; c[tot]=z;
        vec[++tot]=x; pre[tot]=base[y]; base[y]=tot; c[tot]=0;
    }
    bool bfs()
    {
        int head=0,tail=0;
        memset(d,-1,sizeof(d));
        d[S]=0;
        q[++tail]=S;
        while(head<tail)
        {
            head++;
            int u=q[head];
            for (int now=base[u];now;now=pre[now])
            {
                int v=vec[now];
                if (d[v]==-1&&c[now]>0)
                {
                    d[v]=d[u]+1;
                    q[++tail]=v;
                    if (v==T) return 1;
                }
            }
        }
        return 0;
    }
    int dfs(int u,int flow)
    {
        int r=0;
        if (u==T) return flow;
        for (int now=base[u];now&&r<flow;now=pre[now])
        {
            int v=vec[now];
            if (c[now]>0&&d[v]==d[u]+1)
            {
                int x=min(c[now],flow-r);
                x=dfs(v,x);
                r+=x;
                c[now]-=x;
                c[now^1]+=x;
            }
        }
        if (!r)d[u]=-2;
        return r;
    }
    int dinic()
    {
        int ans=0;
        while(bfs())
            ans+=dfs(S,INF);
        return ans;
    }
}G;

bool pwork(int dif)
{
	G.init();
	memset(in,0,sizeof(in));

	for (int i=1;i<=k;i++)
		G.link(n+rd[i],rc[i],1);

	S=0,T=n+m+3;
	SS=T-2; TT=T-1;
	for (int i=1;i<=n;i++)
	{
		int l=(du[i]-dif+1)/2;
		int r=(du[i]+dif)/2;
		if (l<0) l=0;
		if (r>du[i]) r=du[i];
		if (l>r) return 0;
		G.link(i,TT,r-l);
		in[i]-=l;
		in[TT]+=l;
	}

	for (int i=1;i<=m;i++)
	{
		int l=du[n+i]-rb[i];
		int r=ra[i];
		if (r>du[n+i]) r=du[n+i];
		if (l<0) l=0;
		G.link(SS,n+i,r-l);
		in[SS]-=l;
		in[n+i]+=l;
	}

	inflow=0;

	for (int i=1;i<T;i++)
	{
		if (in[i]>0) G.link(S,i,in[i]),inflow+=in[i];
		if (in[i]<0) G.link(i,T,-in[i]);
	}
	return 1;
}
int maxflow;
bool check(int dif)
{
	if (!pwork(dif)) return 0;
	G.S=S;  G.T=T;
    G.link(TT,SS,INF);
    int tmp=G.dinic();
    return tmp==inflow;
}
int get(int dif)
{
	if (!pwork(dif)) return 0;
	G.S=S;  G.T=T;
	G.dinic();
    G.link(TT,SS,INF);
    return G.dinic();
}

int main()
{
	int t; scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		memset(du,0,sizeof(du));
		for (int i=1;i<=m;i++)
			scanf("%d%d",&ra[i],&rb[i]);
		scanf("%d",&k);
		for (int i=1;i<=k;i++)
		{
			scanf("%d%d",&rc[i],&rd[i]);
			du[rc[i]]++;
			du[n+rd[i]]++;
		}

        int l=0,r=0;
        for (int i=1;i<=n;i++) r=max(r,du[i]);
        while(l<r)
		{
			int mid=(l+r)/2;
			if (!check(mid)) l=mid+1;
			else r=mid;
		}
		printf("%d %d\n",r,get(r));
	}
	return 0;
}

ZSTUOJ 4239 巴比伦花园

好题。

 

先对于每个位置,求出向右延伸的最大长度l[i]。

求法:把所有h[i]都减去i*k,然后就是求  以坐标(i,-i*k)为左下角的最长矩形 ,  可以用单调栈来做。

 

求完l[i],问题就转化成了n条线段,q次询问,问区间内最长线段。

 

这些线段有个性质,就是右端点是相等或者递增的(也就是不下降)。像这样:

 

11111111

01111111

00111111

00011111

0000011111

0000001111

000000011111

假设询问[x,y],于是就可以二分找到第一个达到y的线段,然后就是在x和它之间求个区间最大值就好了。

 

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=100005;

int n,q;
ll k;
ll h[N];
int l[N];
ll stk[N],top;
int r[N];
int id[N];

struct ST
{
    int f[N][17]; //10w
    void make(int *a,int n)
    {
        for (int i=1;i<=n;i++) f[i][0]=a[i];
        for (int j=1;j<17;j++)
            for (int i=1;i<=n+1-(1<<j);i++)
                f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
    }
    int get(int l,int r)
    {
        int k=log(r-l+1)/log(2);
        return max(f[l][k],f[r+1-(1<<k)][k]);
    }
}L;

int main()
{
	int t; scanf("%d",&t);
	while(t--)
	{
		scanf("%d%lld%d",&n,&k,&q);
		for (int i=1;i<=n;i++)
		{
			scanf("%lld",&h[i]);
			h[i]-=i*k;
		}
		top=0; id[0]=n+1;
		for (int i=n;i>=1;i--)
		{
			int pos=upper_bound(stk+1,stk+top+1,-i*k)-stk;
			pos--;
			l[i]=id[pos]-i;
			while(top&&h[i]<=stk[top]) top--;
			stk[++top]=h[i]; id[top]=i;
		}
		L.make(l,n);
		for (int i=1;i<=n;i++) r[i]=i+l[i]-1;
		while(q--)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			if (x+l[x]>y) printf("%d\n",y-x+1);
			else
			{
				int pos=lower_bound(r+x+1,r+n+1,y)-r;
				int ret=min(r[pos],y)-pos+1;
				pos--;
				ret=max(ret,L.get(x,pos));
				printf("%d\n",ret);
			}
		}
	}
}
/*
100
10 2 1000
5 3 9 8 100 2 10 6 8 29
1 1

*/

 

poj 3693 后缀数组+ST

枚举长度和起点,枚举起点的时候每次+长度。这样枚举的复杂度是O(nlogn)。

难点就在如何字典序最小。 可以求一下区间的rk的最小值来找这个字典序最小的位置。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=100005;
char s[N],s2[N];
int len;

struct ST
{
	int f[N][17];
	void make(int *a,int n)
	{
		for (int i=1;i<=n;i++) f[i][0]=a[i];
		for (int j=1;j<17;j++)
			for (int i=1;i<=n+1-(1<<j);i++)
				f[i][j]=min(f[i][j-1],f[i+(1<<j-1)][j-1]);
	}
	int get(int l,int r)
	{
		int k=log(r-l+1)/log(2);
		return min(f[l][k],f[r+1-(1<<k)][k]);
	}
}HL,HR,RK;
struct suffixarray
{
	int sa[N],rk[N],h[N];
	int cnt[N],sa2[N],rk2[N];
	int n;
	bool cmp(int i,int j,int len)
	{
		if (rk2[i]==rk2[j]&&rk2[i+len]==rk2[j+len]) return 0;
		return 1;
	}
	void make(char *s,int len)
	{
		n=len;
		int m=255;
		for (int i=1;i<=m;i++) cnt[i]=0;
		for (int i=1;i<=n;i++) cnt[s[i]]++;
		for (int i=2;i<=m;i++) cnt[i]+=cnt[i-1];
		for (int i=n;i>=1;i--) sa[cnt[s[i]]--]=i;
		int k=1; rk[sa[1]]=1;
		for (int i=2;i<=n;i++)
		{
			if (s[sa[i]]!=s[sa[i-1]]) k++;
			rk[sa[i]]=k;
		}
		rk2[n+1]=0;
		for (int j=1;k<n&&j<=n;j*=2)
		{
			int tot=0;
			for (int i=n-j+1;i<=n;i++) sa2[++tot]=i;
			for (int i=1;i<=n;i++)
				if (sa[i]>j) sa2[++tot]=sa[i]-j;
			m=k;
			for (int i=1;i<=m;i++) cnt[i]=0;
			for (int i=1;i<=n;i++) cnt[rk[i]]++;
			for (int i=2;i<=m;i++) cnt[i]+=cnt[i-1];
			for (int i=n;i>=1;i--) sa[ cnt[rk[sa2[i]]]-- ]=sa2[i];
			for (int i=1;i<=n;i++) rk2[i]=rk[i];
			k=1; rk[sa[1]]=1;
			for (int i=2;i<=n;i++)
			{
				if (cmp(sa[i],sa[i-1],j)) k++;
				rk[sa[i]]=k;
			}
		}
		for (int i=1;i<=n;i++)
		{
			k=h[rk[i-1]];
			if (k>0) k--;
			int j=sa[rk[i]-1];
			while(s[i+k]==s[j+k]) k++;
			h[rk[i]]=k;
		}
	}
	int lcp(ST &H,int i,int j)
	{
		int l=rk[i],r=rk[j];
		if (l>r) swap(l,r);
		return H.get(l+1,r);
	}
}SAL,SAR;
int k,idx,sl;
void solve()
{
	k=1;
	idx=1;
	sl=1;
	for (int i=2;i<=len;i++)
		if (s[i]<s[idx]) idx=i;

	HL.make(SAL.h,len);
	HR.make(SAR.h,len);
	RK.make(SAL.rk,len);
	for (int j=1;j<=len/2;j++)
	{
		for (int i=1;i<=len-j;i+=j)
		{
			if (i+j*k-1>len) break;  // 卡常数
			if (s[i]!=s[i+j]) continue;  // 卡常数
			int r=i+j+SAL.lcp(HL,i,i+j)-1;
			int l=i-SAR.lcp(HR,len+1-i,len+1-(i+j))+1;
			if ((r-l+1)/j<k) continue;
			int re=(r-l+1)%j;
			int id=RK.get(l,l+re); // 这个区间都会出现重复k次的子串,在这个区间内找字典序最小来更新答案
			if ((r-l+1)/j>k||(r-l+1)/j==k&&id<idx)
				k=(r-l+1)/j,idx=id,sl=j;
			i=max(i,r+1-j);  // 卡常数
		}
	}
	s[SAL.sa[idx]+k*sl]=0;
	printf("%s",s+SAL.sa[idx]);
	printf("\n");
}
int main()
{
	int cas=0;
	while(scanf("%s",s+1),s[1]!='#')
	{
		printf("Case %d: ",++cas);
		len=strlen(s+1);
		for (int i=1;i<=len;i++) s2[len+1-i]=s[i];
		SAL.make(s,len);
		SAR.make(s2,len);
		solve();
	}
}

hdu 5354 cdq分治+并查集

题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=5354

 

题意:问删除每个点后能否构成二分图。

 

做法:

   考虑分治,solve(l,r)是用来求区间l到r的答案,那么可以把l到mid的点都加入并查集,如果存在奇环了,那么mid+1到r的答都

是0,如果没有奇环,继续solve(mid+1,r)。 然后用相同的做法处理l到mid的答案。

   并查集需要还原,所以用启发式合并,用一个栈来记录需要还原的数据。这样复杂度是nlognlogn。

 

 

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int base[N],vec[2*N],pre[2*N],tot;
void link(int x,int y)
{
	vec[++tot]=y; pre[tot]=base[x]; base[x]=tot;
}
int fa[N],sz[N],len[N];
int top,stk[N];
bool check(int l,int r,int x,int y)
{
	for (int u=l;u<=r;u++)
	{
		for (int now=base[u];now;now=pre[now])
		{
			int v=vec[now];
			if (v>=x&&v<=y) continue;
			int su=0,sv=0,fu=u,fv=v;
			while(fu!=fa[fu]) su^=len[fu],fu=fa[fu];
			while(fv!=fa[fv]) sv^=len[fv],fv=fa[fv];
			if (fu==fv)
			{
				if (su==sv) return 0;
				continue;
			}
			if (sz[fu]<sz[fv]) swap(fu,fv);
			stk[++top]=fv;
			fa[fv]=fu;
			sz[fu]+=sz[fv];
			len[fv]=su^sv^1;
		}
	}
	return 1;
}
void load(int u)
{
	sz[fa[u]]-=sz[u];
	fa[u]=u;
}
int ans[N];
void solve(int l,int r)
{
	if (l==r) { ans[l]=1; return; }
	int pretop=top;
	int mid=(l+r)/2;
	if (!check(l,mid,mid+1,r))
		for (int i=mid+1;i<=r;i++) ans[i]=0;
	else
		solve(mid+1,r);
	while(top>pretop) load(stk[top]),top--;

	if (!check(mid+1,r,l,mid))
		for (int i=l;i<=mid;i++) ans[i]=0;
	else
		solve(l,mid);
	while(top>pretop) load(stk[top]),top--;
}
int main()
{
	int t; scanf("%d",&t);
	int n,m;
	while(t--)
	{
		scanf("%d%d",&n,&m);
		while(m--)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			link(x,y); link(y,x);
		}
		for (int i=1;i<=n;i++) fa[i]=i,sz[i]=1;
		solve(1,n);
		for (int i=1;i<=n;i++) printf("%d",ans[i]);
		printf("\n");
		tot=0;
		for (int i=1;i<=n;i++) base[i]=0;
	}
}

  

  

HDU 5052 树链剖分

题意:给你一棵树,每个点有点权,问从x走到y,先后取两个数a和b,b-a的最大值。

 

做法:树链剖分一下,然后线段树把路径区间都合并起来。

 

比赛的时候居然没想出来。。。(现在想想不是很简单嘛。。。)

 

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=50005;
int a[N];
int base[N],vec[2*N],pre[2*N],tot;
int id[N],rd[N],son[N],deep[N],size[N],fa[N],top[N];
int T;
int n;
void link(int x,int y)
{
	vec[++tot]=y; pre[tot]=base[x]; base[x]=tot;
}

struct seg
{
	int ma,mi,fl,fr;
	seg operator+(const seg &t)const
	{
		if (ma==-1) return t;
		if (t.ma==-1) return *this;
		int maa=max(ma,t.ma);
		int mii=min(mi,t.mi);
		int ffl=max(fl,t.fl);
		ffl=max(ffl,ma-t.mi);
		int ffr=max(fr,t.fr);
		ffr=max(ffr,t.ma-mi);
		return {maa,mii,ffl,ffr};
	}
	void operator+=(const int t)
	{
		ma+=t; mi+=t;
	}
}s[4*N];
int lazy[4*N];
void build(int i,int l,int r)
{
	lazy[i]=0;
	if (l==r)
	{
		s[i]={a[rd[l]],a[rd[l]],0};
		return;
	}
	int mid=(l+r)/2;
	build(i*2,l,mid);
	build(i*2+1,mid+1,r);
	s[i]=s[i*2]+s[i*2+1];
}
void pd(int i)
{
	if (lazy[i])
	{
		lazy[i*2]+=lazy[i];
		lazy[i*2+1]+=lazy[i];
		s[i*2]+=lazy[i];
		s[i*2+1]+=lazy[i];
		lazy[i]=0;
	}
}
void add(int i,int l,int r,int x,int y,int z)
{
	if (x<=l&&r<=y)
	{
		s[i]+=z;
		lazy[i]+=z;
		return;
	}
	pd(i);
	int mid=(l+r)/2;
	if (x<=mid) add(i*2,l,mid,x,y,z);
	if (y>mid) add(i*2+1,mid+1,r,x,y,z);
	s[i]=s[i*2]+s[i*2+1];
}
seg get(int i,int l,int r,int x,int y)
{
	if (x<=l&&r<=y) return s[i];
	pd(i);
	int mid=(l+r)/2;
	seg tmp={-1,-1,-1,-1};
	if (x<=mid) tmp=tmp+get(i*2,l,mid,x,y);
	if (y>mid) tmp=tmp+get(i*2+1,mid+1,r,x,y);
	return tmp;
}


void dfs1(int u,int d,int p)
{
	fa[u]=p;
	deep[u]=d;
	son[u]=-1;
	size[u]=1;
	for (int now=base[u];now;now=pre[now])
	{
		int v=vec[now];
		if (v==p) continue;
		dfs1(v,d+1,u);
		size[u]+=size[v];
		if (son[u]==-1||size[v]>size[son[u]]) son[u]=v;
	}
}
void dfs2(int u,int p)
{
	top[u]=p;
	id[u]=++T;
	rd[T]=u;
	if (son[u]!=-1) dfs2(son[u],p);
	for (int now=base[u];now;now=pre[now])
	{
		int v=vec[now];
		if (v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}

int lca(int x,int y)
{
	int f1=top[x],f2=top[y];
	while(f1!=f2)
	{
		if (deep[f1]<deep[f2]) swap(f1,f2),swap(x,y);
		x=fa[f1],f1=top[x];
	}
	if (deep[x]<deep[y]) swap(x,y);
	return y;
}

seg getseg(int x,int LCA,int v)
{
	seg tmp={-1,-1,-1,-1};
	while(x!=LCA&&deep[top[x]]>deep[LCA])
	{
		tmp=get(1,1,n,id[top[x]],id[x])+tmp;
		add(1,1,n,id[top[x]],id[x],v);
		x=fa[top[x]];
	}
	tmp=get(1,1,n,id[LCA],id[x])+tmp;
	add(1,1,n,id[LCA],id[x],v);
	return tmp;
}
int solve(int x,int y,int v)
{
	int LCA=lca(x,y);

	seg tmp1=getseg(x,LCA,v);
	add(1,1,n,id[LCA],id[LCA],-v);
	seg tmp2=getseg(y,LCA,v);

	int ans=max(tmp1.fl,tmp2.fr);
	ans=max(ans,tmp2.ma-tmp1.mi);
	return ans;
}

int main()
{
	int t; scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;i++) scanf("%d",&a[i]);
		for (int i=1;i<n;i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			link(x,y);
			link(y,x);
		}

		dfs1(1,1,-1);
		dfs2(1,-1);
		build(1,1,n);

		int q;
		scanf("%d",&q);
		while(q--)
		{
			int x,y,v;
			scanf("%d%d%d",&x,&y,&v);
			printf("%d\n",solve(x,y,v));
		}
		tot=T=0;
		for (int i=1;i<=n;i++) base[i]=0;
	}
}

hdu 3842 (平衡树维护凸壳)

怎么网上题解都是cdq分治做的啊。。。

 

题意什么的最近都懒得写了。。看这里http://blog.csdn.net/sdj222555/article/details/40919903

 

n方暴力很好想,f[i]= f[j] + (d[i]-d[j]-1) * G[j] + R[j] - P[i] 。

 

写成向量点积形式 f[i]= ( d[i] , 1 ) * ( G[j] , f[j]+R[j]-(d[j]+1])*G[j] )  - p[i] 。

 

把所有的 j 都 扔到平衡树里维护个上凸壳 就好了。

 

注意到 d[i] 是递增的,于是也不需要二分查找了。

 

貌似还是cdq写起来简单点?不过平衡树好想啊。

 

#include<cstdio>
#include<map>
#include<algorithm>
#define X first
#define Y second
using namespace std;
typedef long long ll;
typedef map<ll,ll>::iterator itr;
struct P
{
	ll x,y;
	P operator-(const P &t)const
	{
		return {x-t.x,y-t.y};
	}
	ll operator^(P t)
	{
		return x*t.y-y*t.x;
	}
};
ll dot(itr it,ll x,ll y)
{
	return it->X*x+it->Y*y;
}
ll cross(itr l,P p2,itr r)
{
	P p1={l->X,l->Y},p3={r->X,r->Y};
	return (p2-p1)^(p3-p2);
}
struct hull
{
	map<ll,ll>s;
	itr mid,l,r,p1,p2;
	void init() { s.clear(); }
	itr pre(itr it)
	{
		if (it==s.end()) return it;
		if (it==s.begin()) return s.end();
		return --it;
	}
	itr suc(itr it)
	{
		if (it==s.end()) return it;
		return ++it;
	}
	bool inside(P p)
	{
		if (s.empty()) return 0;
		r=s.lower_bound(p.x);
		if (r==s.end()) return 0;
		if (r->X==p.x) return p.y<=r->Y;
		if (r==s.begin()) return 0;
		l=r; l--;
		return cross(l,p,r)>=0;
	}
	void add(P p)
	{
		if (inside(p)) return;
		s[p.x]=p.y;
		mid=s.find(p.x);
		p1=suc(mid); p2=suc(p1);
		while(p1!=s.end()&&p2!=s.end()&&cross(mid,{p1->X,p1->Y},p2)>=0)
			s.erase(p1),p1=p2,p2=suc(p2);
		p1=pre(mid); p2=pre(p1);
		while(p1!=s.end()&&p2!=s.end()&&cross(mid,{p1->X,p1->Y},p2)<=0)
			s.erase(p1),p1=p2,p2=pre(p2);
	}
	ll get(ll x,ll y)
	{
		l=s.begin();
		r=suc(l);
		while(r!=s.end()&&dot(l,x,y)<=dot(r,x,y))
			s.erase(l),l=r,r=suc(r);
		return dot(l,x,y);
	}
}G;

const int N=100005;
int n,C,D;
struct mac
{
	int d,P,R,G;
}a[N];
bool cmp(mac a,mac b){ return a.d<b.d;}
ll f[N];

int main()
{
	int cas=0;
	while(scanf("%d%d%d",&n,&C,&D),n+C+D)
	{
		for (int i=1;i<=n;i++)
			scanf("%d%d%d%d",&a[i].d,&a[i].P,&a[i].R,&a[i].G);
		sort(a+1,a+n+1,cmp);
		a[0]={0,0,0,0};
		f[0]=C;
		n++;
		a[n]={D+1,0,0,0};
		G.init();
		G.add({a[0].G,f[0]});
		for (int i=1;i<=n;i++)
		{
			f[i]=G.get(a[i].d,1)-a[i].P;
			if (f[i]>=0) G.add({a[i].G,f[i]+a[i].R-1ll*(a[i].d+1)*a[i].G});
		}
		printf("Case %d: %lld\n",++cas,f[n]);
	}
}