ACM巨全模板(下)

柯氏模板(下)

柯氏模板(上)
柯氏模板(中)
pdf下载
本模板博主还在完善ing…谢谢大家观看

计算几何:
1.三角形 (求面积))
2.多边形
3.三点求圆心和半径
4.扫描线 (矩形覆盖求面积) (矩形覆盖求周长)
5.凸包 (平面上最远点对)
6.求凸多边形的直径
7.求凸多边形的宽度
8.求凸多边形的最小面积外接矩形
9.半平面交
字符串:
1.字典树(多个字符串的前缀)
2.KMP(关键字搜索)
3.EXKMP(找到S中所有P的匹配)
4.马拉车(最长回文串)
5.寻找两个字符串的最长前后缀(KMP)
6.hash(进制hash,无错hash,多重hash,双hash)
7.后缀数组 (按字典序排字符串后缀)
8.前缀循环节(KMP的fail函数)
9.AC自动机 (n个kmp)
10.后缀自动机
小技巧:
1.关于int,double强转为string
2.输入输出挂
3.低精度加减乘除
4.一些组合数学公式
5.二维坐标的离散化
6.消除向下取整的方法
7.一些常用的数据结构 (STL)
8.Devc++的使用技巧
9.封装好的一维离散化
10.Ubuntu对拍程序
11.常数
12.Codeblocks使用技巧
13.java大数
叮嘱

计算几何

1.点

a) 判断点是否在先线段上
1)首先判断Q是否在直线 P 1 P 2 P_1P_2 P1P2上。
判断方法:用叉乘,Q P 1 P_1 P1 × P 1 P 2 P_1P_2 P1P2 = 0,即(Q - P 1 P_1 P1) *( P 1 − P 2 P_1 - P_2 P1P2) = 0;
2)考虑Q是否在 P 1 P 2 P_1P_2 P1P2的反向延迟线上,即Q在以 P 1 、 P 2 P_1、P_2 P1P2为对角顶点的矩形内。

#include
using namespace std;
 
struct point{
    double x;
    double y;
};
 
int Online(point P1,point P2,point Q){
    if((Q.x-P1.x)*(P2.y-P1.y)==((P2.x-P1.x)*(Q.y-P1.y))  //叉乘
        &&(min(P1.x,P2.x)<=Q.x&&Q.x<=max(P1.x,P2.x)) //保证Q点坐标在P1P2之间
        &&min(P1.y,P2.y)<=Q.y&&Q.y<=max(P1.y,P2.y))
        return 1;
    else
        return 0;
}
 
int main()
{
    point p1,p2,q;
    cin>>p1.x>>p1.y>>p2.x>>p2.y;  //输入线段
    cin>>q.x>>q.y;  //输入点
    if(Online(p1,p2,q))
       printf("YES\n");
    else
        printf("NO\n");
    return 0;
}

b) 点绕点旋转后的坐标:
在平面中,一个点绕任意点旋转θ度后的点的坐标:
假设对图片上任意点(x,y),绕一个坐标点(rx0,ry0)逆时针旋转a角度后的新的坐标设为(x0, y0),有公式:

x0= (x - rx0)*cos(a) - (y - ry0)*sin(a) + rx0 ;
y0= (x - rx0)*sin(a) + (y - ry0)*cos(a) + ry0 ;

2.三角形

ACM巨全模板(下)_第1张图片

2.多边形

a) . 凸多边形面积的求法:

把凸多边形分成n-2个小三角形然后求面积 ( 对非凸多边形仍然有效 )
代码实现:

3.三点求圆心及半径

#include 
using namespace std;  
#define maxn 100005  
#define eps 1e-6  
struct node1{  
    double x,y,r;  
}a[maxn];  
int n;  
double dis(node1 a,node1 b){  //线段ab的长度
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));  
}  
bool eq(double a,double b){ //比较是否相等 
    if(fabs(a-b)<=eps)  
        return 1;  
    return 0;  
}  
bool gongxian(node1 a,node1 b,node1 c){   //判断三点是否共线  
    if(eq((b.x-a.x)*(c.y-a.y),(c.x-a.x)*(b.y-a.y)))  
        return 1;  
    return 0;  
}  
node1 yuanxin(node1 a,node1 b,node1 c){  //求圆心坐标  
    double a1=b.x-a.x,b1=b.y-a.y,c1=(a1*a1+b1*b1)/2;  
    double a2=c.x-a.x,b2=c.y-a.y,c2=(a2*a2+b2*b2)/2;  
    double d=a1*b2-a2*b1;  
    node1 res;  
    res.x=a.x+(c1*b2-c2*b1)/d;  
    res.y=a.y+(a1*c2-a2*c1)/d;  
    return res;  
}  

int main(void){  
    int T;  
    scanf("%d",&T);  
    while(T--){  
    	int q;
        scanf("%d %d",&n,&q);  
        for(int i=0;i<n;i++)  
            scanf("%lf%lf",&a[i].x,&a[i].y);  
        while(q--){  
                int t1,t2,t3;  
                cin>>t1>>t2>>t3;  
                if(gongxian(a[t1],a[t2],a[t3]))  
                printf("在直线上\n"); 
                node1 c=yuanxin(a[t1],a[t2],a[t3]);  
                c.r=dis(c,a[t1]); 
                cout<<c.x<<" "<<c.y<<" "<<c.r<<endl; 
        }  
    }  
    return 0;  
}  

4.扫描线

a)矩形覆盖求面积

#define mem(a,x) memset(a,x,sizeof(a))
#include
using namespace std;
typedef long long ll;
const int N = 111;
struct Edge {
    double l,r;//这条线的左右端点的横坐标
    double h;//这条线的纵坐标
    int f;//这条线是矩形的上边还是下边
} e[N<<1];
bool cmp(Edge a,Edge b) {
    return a.h < b.h;
}
struct Node {
    int l,r;//横坐标的区间,是横坐标数组的下标
    int s;//该节点被覆盖的情况(是否完全覆盖)
    double len;//该区间被覆盖的总长度
} q[N*8];
double x[2*N];//横坐标
#define ls i<<1
#define rs i<<1|1
#define m(i) ((q[i].l + q[i].r)>>1)
void build(int i,int l,int r) {
    q[i].l = l,q[i].r = r;
    q[i].s = 0;
    q[i].len = 0;
    if (l == r) return;
    int mid = m(i);
    build(ls,l,mid);
    build(rs,mid+1,r);
}
void pushup(int i) {
    if (q[i].s) { //非零,已经被整段覆盖
        q[i].len = x[q[i].r+1] - x[q[i].l];
    } else if (q[i].l == q[i].r) { //这是一个点而不是线段
        q[i].len = 0;
    } else { //是一条没有整个区间被覆盖的线段,合并左右子的信息
        q[i].len = q[ls].len + q[rs].len;
    }
}
void update(int i,int l,int r,int xx) { //这里深刻体会为什么令下边为1,上边-1
    //下边插入边,上边删除边
    if (q[i].l == l&&q[i].r == r) {
        q[i].s += xx;
        pushup(i);//更新区间被覆盖de总长度
        return;
    }
    int mid = m(i);
    if (r <= mid) update(ls,l,r,xx);
    else if (l > mid) update(rs,l,r,xx);
    else {
        update(ls,l,mid,xx);
        update(rs,mid+1,r,xx);
    }
    pushup(i);
}
int main() {
    int n;
    int kas = 0;
    while (scanf("%d",&n) == 1&&n) {
        int tot = 0;
        for (int i = 0; i < n; ++i) {
            double x1,x2,y1,y2;
            scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);//输入一个矩形
            Edge &t1 = e[tot];
            Edge &t2 = e[1+tot];
            t1.l = t2.l = x1,t1.r = t2.r = x2;
            t1.h = y1;
            t1.f = 1;
            t2.h = y2;
            t2.f = -1;
            x[tot] = x1;
            x[tot+1] = x2;
            tot += 2;
        }
        sort(e,e+tot,cmp);//边按高度从小到大排序(自下而上扫描)
        sort(x,x+tot);
        //离散化横坐标
        int k = 1;
        for (int i = 1; i < tot; ++i) {
            if (x[i] != x[i-1]) { //去重
                x[k++] = x[i];
            }
        }
        build(1,0,k-1);//离散化后的区间是[0,k-1]
        double ans = 0.0;
        for (int i = 0; i < tot; ++i) {
            //因为线段树维护的是横坐标们的下标,所以对每条边求出其两个横坐标对应的下标
            int l = lower_bound(x,x+k,e[i].l) - x;//在横坐标数组里找到这条边的位置
            int r = lower_bound(x,x+k,e[i].r) - x - 1;
            update(1,l,r,e[i].f);//每扫到一条边就更新横向的覆盖len
            ans += (e[i+1].h - e[i].h)*q[1].len;//q[1]是整个区间,q[1].k=len是整个区间的有效长度
            //计算面积就是用区间横向的有效长度乘以两条边的高度差(面积是两条边里面的部分)
        }
        printf("Test case #%d\n",++kas);
        printf("Total explored area: %.2f\n\n",ans);
    }
    return 0;
}

b)矩形覆盖求周长

#define mem(a,x) memset(a,x,sizeof(a))
#include
using namespace std;
typedef long long ll;
const int N = 5007;
const int X = 20007;
const int inf = 1<<29;
struct Edge { //扫描线
    int l,r;//左右端点的横坐标
    int h;//这条线的高度,即纵坐标
    int f;//标记这条边是上边(-1)还是下边(1)
} e[N*2];
bool cmp(Edge a,Edge b) {

    return a.h < b.h;//高度从小到大排,扫描线自下而上扫
}
struct Node {
    int l,r;//该节点代表的线段的左右端点坐标
    int len;//这个区间被覆盖的长度
    int s;//表示这个区间被重复覆盖了几次
    bool lc,rc;//表示这个节点左右两个端点是否被覆盖(0表示没有被覆盖,1表示有被覆盖)
    int num;//这个区间有多少条线段(这个区间被多少条线段覆盖)
    //len用来计算横线 num用来计算竖线
} q[4*X];
#define ls i<<1
#define rs i<<1|1
#define m(i) ((q[i].l + q[i].r)>>1)
void pushup(int i) { //区间合并
    if (q[i].s) { //整个区间被覆盖
        q[i].len = q[i].r - q[i].l + 1;
        q[i].lc = q[i].rc = 1;
        q[i].num = 1;
    } else if (q[i].l == q[i].r) { //这是一个点而不是一条线段
        q[i].len = 0;
        q[i].lc = q[i].rc = 0;
        q[i].num = 0;
    } else { //是一条没有整个区间被覆盖的线段,合并左右子的信息
        q[i].len = q[ls].len + q[rs].len ;//长度之和
        q[i].lc = q[ls].lc;
        q[i].rc = q[rs].rc;//和左儿子共左端点,和右儿子共右端点
        q[i].num = q[ls].num + q[rs].num  - (q[ls].rc&q[rs].lc);
        //如果左子的右端点和右子的左端点都被覆盖了
    }
}
void build (int i,int l,int r) {
    q[i].l = l,q[i].r = r;
    q[i].s = q[i].len = 0;
    q[i].lc = q[i].rc = q[i].num = 0;
    if (l == r) return;
    int mid = m(i);
    build(ls,l,mid);
    build(rs,mid+1,r);
}
void update(int i,int l,int r,int xx) {
    if (l == q[i].l && q[i].r == r) {
        q[i].s += xx;
        pushup(i);
        return;
    }
    int mid = m(i);
    if (r <= mid) update(ls,l,r,xx);
    else if (l > mid) update(rs,l,r,xx);
    else {
        update(ls,l,mid,xx);
        update(rs,mid+1,r,xx);
    }
    pushup(i);
}
int main() {
    int n;
    while (cin>>n) {
        int x1,x2,y1,y2,mx = -inf,mn = inf;
        int tot = 0;
        for (int i = 0; i < n; ++i) {
            scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
            mx = max(mx,max(x1,x2));
            mn = min(mn,min(x1,x2));
            Edge & t1 = e[tot];
            Edge & t2 = e[tot+1];
            t1.l = t2.l = x1,t1.r = t2.r = x2;
            t1.h = y1;
            t1.f = 1;
            t2.h = y2;
            t2.f = -1;
            tot += 2;
        }
        sort(e,e+tot,cmp);
        //数据小可以不离散化
        int ans = 0;//计算周长
        int last = 0;//保存上一次的总区间的被覆盖的长度
        build(1,mn,mx-1);
        //每两条横线之间才会有竖线
        for (int i = 0; i < tot; ++i) {
            update(1,e[i].l,e[i].r-1,e[i].f);//根据扫描线更新
            //计算周长
            //横线:现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值
            ans += abs(q[1].len - last);
            //竖线:[下一条横线的高度-现在这条横线的高度]*2*num
            ans += (e[i+1].h - e[i].h)*2*q[1].num;
            last = q[1].len;//每次都要更新上一次总区间覆盖的长度
        }
        printf("%d\n",ans);
    }
    return 0;
}

扫描线模板:

#include  
using namespace std;  
  
double x[2002],y[2002];//最多100个矩形,所以最多只有200条横线或纵线  
double a[1002][4];//矩形实际坐标,分别是左下角横、纵,右下角横、纵
bool cover[2002][2002];  
const double EPS = 1e-7;  
  
int cmp(const void * b,const  void * a){//b>a 返回1  
    if(fabs(*(double* )b-*(double *)a)<EPS)return 0;  
    else if(*(double* )b-*(double *)a>0)return 1;  
    else return -1;  
}  
  
int main(){  
    int n,i,k,j,h1,h2,v1,v2;  
    while(scanf("%d",&n)&&n){  
        for(i=0,k=0;i<n;i++,k++){  
            scanf("%lf%lf%lf%lf",&a[i][0],&a[i][1],&a[i][2],&a[i][3]);  
            x[k]=a[i][0],y[k]=a[i][1],x[++k]=a[i][2],y[k]=a[i][3];  
        }  
        qsort(x,2*n,sizeof(x[0]),cmp);  
        qsort(y,2*n,sizeof(y[0]),cmp);//将横线,竖线排好序  
        memset(cover,0,sizeof(cover));//初始时无覆盖区  
        //对每个矩形,进行一次横纵线的扫描,覆盖范围  
        //扫描线条数作为下标,离散化  
        for(i=0;i<n;i++){  
            k=0;  
            while(fabs(x[k]-a[i][0])>EPS)k++;h1=k;  
            k=0;  
            while(fabs(y[k]-a[i][1])>EPS)k++;v1=k;  
            k=0;  
            while(fabs(x[k]-a[i][2])>EPS)k++;h2=k;  
            k=0;  
            while(fabs(y[k]-a[i][3])>EPS)k++;v2=k;  
            //标记该矩形覆盖的区域  
            for(j=h1;j<h2;j++)      //不考虑最右  
                for(k=v1;k<v2;k++)  //不考虑最下  
                    cover[j][k]=true;  
        }  
        double sum=0;  
        //这时再用扫描线真实的位置计算面积,由矩形左下角的点(判断是否覆盖)及四条扫描线确定。  
        for(i=0;i<2*n-1;i++)  
            for(j=0;j<2*n-1;j++)  
                sum+=(cover[i][j]*(x[i+1]-x[i])*(y[j+1]-y[j]));  
        printf("%d\n", (int)round(sum));  
    }  
}  

5.凸包

a)平面最远点对

#include 
#include 
#include 
#include 
using namespace std;
const int N=100010;
const double eps=1e-8;

struct vector {
	double x,y;
	vector(double x=0,double y=0):x(x),y(y) {};
};
#define point vector
vector operator + (vector a,vector b) {return vector(a.x+b.x,a.y+b.y);}
vector operator - (vector a,vector b) {return vector(a.x-b.x,a.y-b.y);}
vector operator * (vector a,double k) {return vector(a.x*k,a.y* k);}
vector operator / (vector a,double k) {return vector(a.x/k,a.y/k);}
double operator * (vector a,vector b) {return a.x*b.x+a.y*b.y;}
double operator ^ (vector a,vector b) {return a.x*b.y-b.x*a.y;}
vector d[N],hull[N];
bool operator < (point a,point b) {return a.x==b.x?a.y<b.y:a.x<b.x;}
 
int n,m;
double len(vector a) {return (a.x*a.x+a.y*a.y);}

void get_hull() {
	m=2;
	hull[1]=d[1],hull[2]=d[2];
	for(int i= 3;i<=n;++i) {
		while(m>1&&(((hull[m]-hull[m-1])^(d[i]-hull[m-1]))-eps<0)) --m;
		hull[++m]=d[i];
	}
	int k=m;
	for(int i=n-1;i>=1;--i) {
		while(m>k&&(((hull[m]-hull[m-1])^(d[i]-hull[m-1]))-eps<0)) --m;
		hull[++m]=d[i];

	}
}

int main() {
	cin>>n;
	for(int i=1; i<=n; ++i)
		cin>>d[i].x>>d[i].y;
	sort(d+1,d+n+1) ;
	get_hull();
	double ans=0;for(int i=1;i<m;++i )
		for(int j=1;j<=m;++j)
			ans=max(len(hull[i]-hull[j]),ans);
	cout<<(int)ans<<endl ;
   
  	return 0;
}

6.求凸多边形的直径:

多边形的直径是凸多边形边上所有点中距离最远的一对点的距离,他显然是某对对踵点的距离,枚举所有对踵点即可效率O(n)(对每一对对踵点O(1))

double RotatingCaliper_diameter(Point poly[],int n){
    int p=0,q=n-1,fl,i;
    double ret=0;
    for(i=0;i<n;i++){
        if(poly[i].y>poly[q].y)    q=i;
        if(poly[i].y<poly[p].y)    p=i;
    }
    Point ymin=poly[p],ymax=poly[q];
    for(i=0;i<n*3;i++){
        if(ret<Length(poly[p%n]-poly[q%n]))    ret=Length(poly[p%n]-poly[q%n]);
        fl=dcmp(Cross(poly[(p+1)%n]-poly[p%n],poly[q%n]-poly[(q+1)%n]));
        if(!fl){
            if(ret<Length(poly[p%n]-poly[(q+1)%n]))    ret=Length(poly[p%n]-poly[(q+1)%n]);
            if(ret<Length(poly[q%n]-poly[(p+1)%n]))    ret=Length(poly[q%n]-poly[(p+1)%n]);
            p++,q++;
        }
        else{
            if(fl>0)    p++;
            else        q++;
        }
    }
    return ret;
}//用旋转卡壳求解凸多边形直径 


7.求凸多边形的宽度:

ACM巨全模板(下)_第2张图片

double RotatingCaliper_width(Point poly[],int n){
    int p=0,q=n-1,fl,i;
    double ret;
    for(i=0;i<n;i++){
        if(poly[i].y>poly[q].y)    q=i;
        if(poly[i].y<poly[p].y)    p=i;
    }
    ret=Length(poly[p]-poly[q]);
    for(i=0;i<n*3;i++){
        fl=dcmp(Cross(poly[(p+1)%n]-poly[p%n],poly[q%n]-poly[(q+1)%n]));
        if(!fl){
            if(ret>DistanceToLine(poly[p%n],poly[q%n],poly[(q+1)%n]))    ret=DistanceToLine(poly[p%n],poly[q%n],poly[(q+1)%n]);
            p++,q++;
        }
        else{
            if(fl>0){
                if(ret>DistanceToLine(poly[q%n],poly[p%n],poly[(p+1)%n]))    ret=DistanceToLine(poly[q%n],poly[p%n],poly[(p+1)%n]);
                p++;
            }
            else{
                if(ret>DistanceToLine(poly[p%n],poly[q%n],poly[(q+1)%n]))    ret=DistanceToLine(poly[p%n],poly[q%n],poly[(q+1)%n]);
                q++;
            }
        }
    }
    return ret;
}//用旋转卡壳求解凸多边形宽度 

Rotating Caliper Width

8.求凸多边形的最小面积外接矩形:

这样的矩形一定与凸多边形至少一边发生重合,于是这一条重合的边以及这条边的对边可以通过旋转卡壳来枚举,在枚举出这么一组有重合平行边之后,如何枚举另外一对平行边呢,在我们枚举出第一对有重合平行边并找到其对应的另一对边之后(这个另一对边方向与有重合平行边垂直,所以这对边其实可以存成多边形上的一对点)如果我们旋转这组有重合平行边得到另一组有重合平行边的话,新的一组的对应另一对边可以由旧的一组通过向相同的方向旋转得来,判定是否完成旋转的方法可以是向量叉积效率O(n)(对每一对对踵点O(1),另一对平行线的旋转也是单向的于是也是O(n))其实求最小周长外接矩形也是同理

void RC(Poi poly[],int n){
    int p=0,q=n-1,l=0,r=n-1;
    int fl,i,j;
    Vec nm,dr;
    for(i=0;i<n;i++){
        if(poly[i].y<poly[p].y)    p=i;
        if(poly[i].y>poly[q].y)    q=i;
        if(poly[i].x<poly[l].x)    l=i;
        if(poly[i].x>poly[r].x)    r=i;
    }
    an[0]=intersect_p(poly[p],Vec(1,0),poly[l],Vec(0,1)),an[1]=intersect_p(poly[p],Vec(1,0),poly[r],Vec(0,1));
    an[2]=intersect_p(poly[r],Vec(0,1),poly[q],Vec(1,0)),an[3]=intersect_p(poly[l],Vec(0,1),poly[q],Vec(1,0));
    ans=fabs(Area(an[0],an[1],an[2]));
    for(i=0;i<n*3;i++){
        fl=dcmp(Cross(poly[(p+1)%n]-poly[p%n],poly[q%n]-poly[(q+1)%n]));
        if(!fl){
            dr=poly[(p+1)%n]-poly[p%n],dr=dr/Len(dr);
            nm=Vec(dr.y,-dr.x);
            while(dcmp(Cross(poly[(l+1)%n]-poly[l%n],nm))>0)    l++;
            nm=Vec(0,0)-nm;
            while(dcmp(Cross(poly[(r+1)%n]-poly[r%n],nm))>0)    r++;
            cha[0]=intersect_p(poly[p%n],dr,poly[l%n],nm),cha[1]=intersect_p(poly[p%n],dr,poly[r%n],nm);
            cha[2]=intersect_p(poly[r%n],nm,poly[q%n],dr),cha[3]=intersect_p(poly[l%n],nm,poly[q%n],dr);
            if(fabs(Area(cha[0],cha[1],cha[2]))<ans){
                for(j=0;j<4;j++)    an[j]=cha[j];
                ans=fabs(Area(an[0],an[1],an[2]));
            }
        }
        else{
            if(fl>0){
                dr=poly[(p+1)%n]-poly[p%n],dr=dr/Len(dr);
                nm=Vec(dr.y,-dr.x);
                while(dcmp(Cross(poly[(l+1)%n]-poly[l%n],nm))>0)    l++;
                nm=Vec(0,0)-nm;
                while(dcmp(Cross(poly[(r+1)%n]-poly[r%n],nm))>0)    r++;
                cha[0]=intersect_p(poly[p%n],dr,poly[l%n],nm),cha[1]=intersect_p(poly[p%n],dr,poly[r%n],nm);
                cha[2]=intersect_p(poly[r%n],nm,poly[q%n],dr),cha[3]=intersect_p(poly[l%n],nm,poly[q%n],dr);
                if(fabs(Area(cha[0],cha[1],cha[2]))<ans){
                    for(j=0;j<4;j++)    an[j]=cha[j];
                    ans=fabs(Area(an[0],an[1],an[2]));
                }
                p++;
            }
            else{
                dr=poly[(q+1)%n]-poly[q%n],dr=dr/Len(dr);
                nm=Vec(-dr.y,dr.x);
                while(dcmp(Cross(poly[(l+1)%n]-poly[l%n],nm))>0)    l++;
                nm=Vec(0,0)-nm;
                while(dcmp(Cross(poly[(r+1)%n]-poly[r%n],nm))>0)    r++;
                cha[0]=intersect_p(poly[p%n],dr,poly[l%n],nm),cha[1]=intersect_p(poly[p%n],dr,poly[r%n],nm);
                cha[2]=intersect_p(poly[r%n],nm,poly[q%n],dr),cha[3]=intersect_p(poly[l%n],nm,poly[q%n],dr);
                if(fabs(Area(cha[0],cha[1],cha[2]))<ans){
                    for(j=0;j<4;j++)    an[j]=cha[j];
                    ans=fabs(Area(an[0],an[1],an[2]));
                }
                q++;
            }
        }
    }
}

9.半平面交

半平面交是什么?
我们知道一条直线可以把平面分为两部分,其中一半的平面就叫半平面。
那半平面交,就是多个半平面的相交部分。我们在学习线性规划时就有用过。
半平面交有什么用?
1.求解一个区域,可以看到给定图形的各个角落。(多边形的核)
2.求可以放进多边形的圆的最大半径。

给你n个凸多边形,求多边形的交的面积

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef double db;
const int M = 507;

inline int rd() {
    int x = 0;
    bool f = 1;
    char c = getchar();
    for (; !isdigit(c); c = getchar())
        if (c == '-') f = 0;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - 48;
    return f ? x : -x;
}

int n, m, tt;

struct pt {
    db x, y;
    pt(db xx = 0, db yy = 0) {
        x = xx;
        y = yy;
    }
} p[M], a[M];
pt operator+(pt x, pt y) { return pt(x.x + y.x, x.y + y.y); }
pt operator-(pt x, pt y) { return pt(x.x - y.x, x.y - y.y); }
pt operator*(pt x, db d) { return pt(x.x * d, x.y * d); }
pt operator/(pt x, db d) { return pt(x.x / d, x.y / d); }
db slop(pt x) { return x.y / x.x; }
db dot(pt x, pt y) { return x.x * y.x + x.y * y.y; }
db det(pt x, pt y) { return x.x * y.y - x.y * y.x; }
db len(pt x) { return sqrt(dot(x, x)); }
db dis(pt x, pt y) { return len(y - x); }
db area(pt x, pt y, pt z) { return det(y - x, z - x); }

struct line {
    pt P, v;
    line(pt PP = pt(), pt vv = pt()) {
        P = PP;
        v = vv;
    }
} l[M], s[M];

pt inter(line x, line y) {
    pt u = x.P - y.P;
    db t = det(u, y.v) / det(y.v, x.v);
    return x.P + x.v * t;
}
bool parallel(line x, line y) { return det(y.v, x.v) == 0; }
bool lineleft(line x, line y) {
    db tp = det(x.v, y.v);
    return (tp > 0) || ((tp == 0) && det(x.v, y.P - x.P) > 0);
}
bool ptright(pt x, line y) { return det(y.v, x - y.P) <= 0; }  ///<=

bool cmp(line x, line y) {                                //极角排序
    if (x.v.y == 0 && y.v.y == 0) return x.v.x < y.v.x;   // y都为0
    if (x.v.y <= 0 && y.v.y <= 0) return lineleft(x, y);  //同在上部
    if (x.v.y > 0 && y.v.y > 0) return lineleft(x, y);    //同在下部
    return x.v.y < y.v.y;                                 //一上一下
}

void hpi() {                      // half-plane intersection
    sort(l + 1, l + m + 1, cmp);  // sort
    int tp = 0, i;
    for (i = 1; i <= m; i++) {
        if (i == 1 || !parallel(l[i], l[i - 1])) tp++;  //平行特判
        l[tp] = l[i];
    }
    m = tp;
    int L = 1, R = 2;
    s[1] = l[1], s[2] = l[2];
    for (i = 3; i <= m; i++) {
        while (L < R && ptright(inter(s[R], s[R - 1]), l[i])) R--;
        while (L < R && ptright(inter(s[L], s[L + 1]), l[i])) L++;
        s[++R] = l[i];
    }
    while (L < R && ptright(inter(s[R], s[R - 1]), s[L]))
        R--;           //最后删除无用平面
    if (R - L <= 1) {  //若半平面交退化为点或线
        puts("0.000");
        return;
    }
    tp = 0;
    s[L - 1] = s[R];
    for (i = L; i <= R; i++)
        a[++tp] =
            inter(s[i], s[i - 1]);  //求出相邻两边的交点,转化为凸包的记录方法
    db ans = 0;
    for (i = 3; i <= tp; i++) ans += area(a[1], a[i - 1], a[i]) * 0.5;
    printf("%.3lf", ans);  //求面积
}

int main() {
    int i, x, y, z, st;
    tt = rd();
    n = m = 0;
    while (tt--) {
        z = rd();
        st = n + 1;
        while (z--) {
            x = rd(), y = rd();
            p[++n] = pt(x, y);
            if (n > st) l[++m] = line(p[n - 1], p[n] - p[n - 1]);
        }
        l[++m] = line(p[n], p[st] - p[n]);
    }

    hpi();

    return 0;
}

字符串:

1.字典树(多个字符串的前缀)

ACM巨全模板(下)_第3张图片
字典树的插入和查询复杂度都为O(n)

1.求该字符串是否是众多字符串中的子字符串。(小写字母abc)

/*
  trie tree的储存方式:将字母储存在边上,边的节点连接与它相连的字母 
  trie[rt][x]=tot:rt是上个节点编号,x是字母,tot是下个节点编号 
*/ 
#include
#define maxn 2000010
using namespace std;
int tot=1,n;
int trie[maxn][26];
//bool isw[maxn];查询整个单词用
void insert(char *s,int rt){
    for(int i=0;s[i];i++){
        int x=s[i]-'a';
        if(trie[rt][x]==0){//现在插入的字母在之前同一节点处未出现过 
            trie[rt][x]=++tot;//字母插入一个新的位置,否则不做处理 
        }
        rt=trie[rt][x];//为下个字母的插入做准备  
    }
    /*isw[rt]=true;标志该单词末位字母的尾结点,在查询整个单词时用到*/
}

bool find(char *s,int rt){
    for(int i=0;s[i];i++){
        int x=s[i]-'a';
        if(trie[rt][x]==0)return false;//以rt为头结点的x字母不存在,返回0 
        rt=trie[rt][x];//为查询下个字母做准备 
    }
    return true;
    //查询整个单词时,应该return isw[rt] 
}
char s[22];
int main(){
    tot=0;
    int rt=1;
    scanf("%d",&n);  //输入众多字符串的个数
    for(int i=1;i<=n;i++){
        cin>>s;  //输入字符串
        insert(s,rt); 
    }
    scanf("%d",&n);  //输入查询的个数
    for(int i=1;i<=n;i++){
        cin>>s;   //输入查询字符串
        if(find(s,rt))printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}
//数组模拟

2、查询前缀出现次数(小写字母abc)

/*
  trie tree的储存方式:将字母储存在边上,边的节点连接与它相连的字母 
  trie[rt][x]=tot:rt是上个节点编号,x是字母,tot是下个节点编号 
*/ 
#include
using namespace std;
const int maxn=400004;
int trie[maxn][26],len,root,tot,sum[maxn];
bool p;
int n,m; 
char s[11];
void insert(){
    len=strlen(s);
    root=0;
    for(int i=0;i<len;i++){
        int id=s[i]-'a';
        if(!trie[root][id]) //现在插入的字母在之前同一节点处未出现过
            trie[root][id]=++tot; //字母插入一个新的位置,否则不做处理 
        sum[trie[root][id]]++;//前缀保存 
        root=trie[root][id]; //为下个字母的插入做准备  
    }
}
int search(){
    root=0;
    len=strlen(s);
    for(int i=0;i<len;i++){
        int id=s[i]-'a';
        if(!trie[root][id]) return 0;
        root=trie[root][id]; //为查询下个字母做准备 
    } //root经过此循环后变成前缀最后一个字母所在位置
    return sum[root]; //把这个改成return 1 就是求字符串是否是一些字符串的前缀
}
int main(){
    scanf("%d",&n);  //输入众多字符串的个数
    for(int i=1;i<=n;i++){
        cin>>s;  //输入字符串
        insert();
    }
    scanf("%d",&m); //输入查询的个数
    for(int i=1;i<=m;i++){
        cin>>s;   //输入查询字符串
        printf("%d\n",search());
    }
}
//数组模拟

2.KMP(关键字搜索)

next数组:
ACM巨全模板(下)_第4张图片
复杂度O(m+n)
计算匹配串在模式串中第一次出现的位置:

#include 
using namespace std;
const int maxn=10010;
typedef long long ll;

int s[maxn*100],p[maxn],nexts[maxn];
int plen,slen;

void getNEXT(int *p){  //获取next数组
   nexts[0]=-1;
   int k=-1,j=0;
   while(j<plen){
       if(k==-1||p[j]==p[k]){
        k++;
        j++;
        if(p[j]!=p[k]) nexts[j]=k;
        else nexts[j]=nexts[k];
       }
       else k=nexts[k];
   }
}

int kmp(int*s,int*p){  //kmp主要的还是next数组
    int i=0,j=0;
    getNEXT(p);
    while(i<slen&&j<plen){  //两个指针
        if(j==-1||s[i]==p[j]){
            i++;
            j++;
        }
        else{
            j=nexts[j];
        }
    }
    if(j==plen)return i-j+1;
    else return -1;
}

int main(){
    int n;
    cin>>n;
    while(n--){
        cin>>slen>>plen;   //s[i]为主串,p[i]为子串
        for(int i=0;i<slen;i++) scanf("%d",&s[i]); //可以改成字符串
        for(int i=0;i<plen;i++) scanf("%d",&p[i]);  //可以改成字符串
         cout<<kmp(s,p)<<endl;
    }
}

3.EXKMP(找到S中所有P的匹配)

ACM巨全模板(下)_第5张图片
next[i]:T[i]…T[m - 1]与T的最长相同前缀长度;
extend[i]:S[i]…S[n - 1]与T的最长相同前缀长度。
本代码输出的是next和extend数组,当next数组与p字符串长度相同即是s中p的匹配,复杂度O(n+m)

#include 
using namespace std;
const int maxn=1000005;

char p[maxn],s[maxn];
int nexts[maxn],extend[maxn];//S[i]…S[n-1]与T的最长相同前缀的长度
void getNEXT(char*p,int&plen,int *nexts){  //获得next数组
    int a=0,k=0;
    nexts[0]=plen;
    for(int i=1;i<plen;i++){
        if(i>=k||i+nexts[i-a]>=k){
            if(i>=k) k=i;
            while(k<plen&&p[k]==p[k-i]) k++;
            nexts[i]=k-i;
            a=i;
        }
        else
            nexts[i]=nexts[i-a];
    }
}

void extendkmp(char*s,int&slen,char*p,int&plen,int *extend,int *next){
    int a=0,k=0;
    getNEXT(p,plen,next);
    for(int i=0;i<slen;i++){
        if(i>=k||i+nexts[i-a]>=k){
            if(i>=k) k=i;
            while(k<slen&&k-i<plen&&s[k]==p[k-i])
                k++;
            extend[i]=k-i;
            a=i;
        }
        else
            extend[i]=nexts[i-a];
    }
}

int main(){
    int slen,plen;
    while(cin>>s>>p){  //s为主串,p为子串
        slen=strlen(s);
        plen=strlen(p); 
        extendkmp(s,slen,p,plen,extend,nexts);
        cout<<"next: ";
        for(int i=0;i<plen;i++)
            cout<<nexts[i]<<" ";
        cout<<"\nxetend: ";
        for(int i=0;i<slen;i++)
            cout<<extend[i]<<" ";
        cout<<endl<<endl; //本代码输出的是next和extend数组,
        //当next数组与p字符串长度相同即是s中p的匹配
    }
}

4.马拉车(最长回文串)

复杂度O(n)
找出类似"abba"和“abcba”

#include 
using namespace std;
typedef long long ll;
const int maxn=110010;
char Ma[maxn*2];
int Mp[maxn*2];
void Manacher(char*s,int len){
    int l=0;           
    Ma[l++]='$';
    Ma[l++]='#';
    for(int i=0;i<len;i++){   //若是指定字符可以在这修改
            Ma[l++]=s[i];
            Ma[l++]='#';
    }
    Ma[l]=0;
    int mx=0,id=0;
    for(int i=0;i<l;i++){   //如果不要abcba类型的,可以加个if(Ma[i]=='#')
        Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1;
        while(Ma[i+Mp[i]]==Ma[i-Mp[i]]) Mp[i]++;
        if(i+Mp[i]>mx){
            mx=i+Mp[i];
            id=i;
        }
    }
}

char s[maxn];
int main(){
    int t;
    cin>>t;
    while(t--){
    scanf("%s",s);
        int len=strlen(s);
        Manacher(s,len);
        int ans=0;
        for(int i=0;i<2*len+2;i++)
            ans=max(ans,Mp[i]-1);
        printf("%d\n",ans);
    }
    return 0;
}

5.寻找两个字符串的最长前后缀(KMP)

复杂度O(n+m).

#include
using namespace std;
const int maxn=1e6+10;
 
int nexts[1000010];
void cal_nexts(const string &str,int nexts[],int len){
    nexts[0]=nexts[1]=0;  //初始化
    for(int i=1;i<len;++i){  
        int j=nexts[i];
        while(j&&str[i]!=str[j])   //匹配不了就跳回上一个
            j=nexts[j];  
        nexts[i+1]=(str[j]==str[i])?j+1:0;
    }
}
 
int kmp(const string &str,int slen,const string &ptr,int plen){
    cal_nexts(ptr,nexts,plen);   //给ptr建立next数组
    int j=0;
    for(int i=0;i<slen;i++){     
        while(j&&ptr[j]!=str[i])  //不能与就直接跳到next数组的位置
            j=nexts[j];
        if(ptr[j]==str[i])   //否则就下一个
            ++j;
    }
    return j;
}
 
string str,ptr;
 
int main(){
    cin>>str>>ptr;
        int plen=ptr.length(),slen=str.length();
        int len=kmp(str,slen,ptr,plen);
        ptr=ptr.substr(0, len);//获得相同的子串
    cout<<ptr<<endl;
    return 0;
}

6.hash

1.进制hash(单哈希/自然溢出法)

给出一个固定进制base,将一个串的每一个元素看做一个进制位上的数字,所以这个串就可以看做一个base进制的数,那么这个数就是这个串的哈希值;则我们通过比对每个串的的哈希值,即可判断两个串是否相同

#include
using namespace std;
typedef unsigned long long ull;
ull base=131;
ull a[10010];
char s[10010];
int n,ans=1;
int prime=233317; 
ull mod=212370440130137957ll;
ull hashe(char s[])
{
    int len=strlen(s);
    ull ans=0;
    for (int i=0;i<len;i++)
    ans=(ans*base+(ull)s[i])%mod+prime; //转换进制
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s);
        a[i]=hashe(s);
    }
} 

2.无错hash

其实原理很简单,就是我们要记录每一个已经诞生的哈希值,然后对于每一个新的哈希值,我们都可以来判断是否和已有的哈希值冲突,如果冲突,那么可以将这个新的哈希值不断加上一个大质数,直到不再冲突。

for(int i=1;i<=m;i++)//m个串
{
cin>>str;//下一行的check为bool型 
while(check[hash(str)])hash[i]+=19260817;
hash[i]+= hash(str) ;
}

3.多重hash

这其实就是你用不同的两种或多种方式哈希,然后分别比对每一种哈希值是否相同——显然是增加了空间和时间,但也确实增加了其正确性。

ACM巨全模板(下)_第6张图片

//哈希自动机,需要二维hash数组 
for伪代码排序,用来使哈希值单调(更好判断相/不同的数量) 
for(int i=1;i<=m;i++){
    check=1for(int j=1;j<=qwq;j++)//皮一下
        if(hash[j][i]==hash[j][i+1]){check=0;break;} 
    if(check)ans++;//此为判断相同个数 
} 

4.双hash

其实就是用两个不同的mod来算hash,哈希冲突的概率是降低了很多,不过常数大,容易被卡

#include
using namespace std;
#define N 10001
#define ull unsigned long long
#define inf 1<<30
#define ll int
#define base 233
ull mod1=212370440130137957ll;
ull mod2=inf;
ll n;
char a[N];
struct node{ll x,y;}f[N];
inline ull hash1(char s[]){
    ll ans=0,len=strlen(s);
    for(ll i=0;i<len;i++){
        ans=(base*ans+(ull)s[i])%mod1;
    }
    return ans;
}
inline ull hash2(char s[]){
    ll ans=0,len=strlen(s);
    for(ll i=0;i<len;i++){
        ans=(base*ans+(ull)s[i])%mod2;
    }
    return ans;
}
inline bool cmp1(node a,node b){return a.x<b.x;}
inline bool cmp2(node a,node b){return a.y<b.y;}
int main(){
    scanf("%d",&n);
    for(ll i=1;i<=n;i++){
        scanf("%s",a);
        f[i].x=hash1(a);
        f[i].y=hash2(a);
    }
    sort(f+1,f+n+1,cmp1);sort(f+1,f+n+1,cmp2);
    ll ans=1;
    for(ll i=1;i<n;i++){
        if(f[i].x!=f[i+1].x||f[i].y!=f[i+1].y)ans++;
    }
    printf("%d\n",ans);
    return 0;
}

7.后缀数组

后缀就是从字符串的某个位置i到字符串末尾的子串,我们定义以s的第i个字符为第一个元素的后缀为suff(i)
把s的每个后缀按照字典序排序,
后缀数组sa[i] 就表示排名为i的后缀的起始位置的下标
而它的映射数组rk[i]就表示起始位置的下标为i的后缀的排名
简单来说,sa表示排名为i的是啥,rk表示第i个的排名是啥

两个优化:

  • 1.倍增
    1)先按照每个后缀的第一个字符排序。
    2)再把相邻的两个关键字合并到一起,就相当于根据每一个后缀的前两个字符进行排序。第二关键字的补零。(2倍数排序) 直到没有相同为止。

这样排序的速度稳定在 (log n)
如下图:
ACM巨全模板(下)_第7张图片

  • 2.基数排序
    用一波基数排序优化一下。在这里我们可以注意到,每一次排序都是排两位数,所以基数排序可以将它优化到O(n)级别,总复杂度就是(n log n)。

构造最长公共前缀——Height

同样先是定义一些变量
Heigth[i] : 表示Suffix[SA[i]]和Suffix[SA[i - 1]]的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀
H[i] : 等于Height[Rank[i]],也就是后缀Suffix[i]和它前一名的后缀的最长公共前缀
而两个排名不相邻的最长公共前缀定义为排名在它们之间的Height的最小值。
ACM巨全模板(下)_第8张图片
高效地得到Height数组
想要快速的得到Height就需要用到一个关于H数组的性质。
H[i] ≥ H[i - 1] - 1!
如果上面这个性质是对的,那我们可以按照H[1]、H[2]……H[Len] 的顺序进行计算,那么复杂度就降为O(N)了!
让我们尝试一下证明这个性质 : 设Suffix[k]是排在Suffix[i - 1]前一名的后缀,则它们的最长公共前缀是H[i - 1]。都去掉第一个字符,就变成Suffix[k + 1]和Suffix[i]。如果H[i - 1] = 0或1,那么H[i] ≥ 0显然成立。 否则,H[i] ≥ H[i - 1] - 1(去掉了原来的第一个,其他前缀一样相等), 所以Suffix[i]和在它前一名的后缀的最长公共前缀至少是H[i - 1] - 1。
仔细想想还是比较好理解的。H求出来,那Height就相应的求出来了,这样结合SA,Rank和Height我们就可以做很多关于字符串的题了!

例题:
读入一个长度为n的由大小写英文字母或数字组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。位置编号为1到n。

按字典序排字符串后缀

#include
#include
#include
using namespace std;
const int maxn=1000050;
//以s的第i个字符为第一个元素的后缀为suff(i)
char s[maxn];//存字符串
int y[maxn];//y[i]表示第二关键字排名为i的数,第一关键字的位置
int x[maxn];//x[i]是第i个元素的第一关键字
int c[maxn]; //桶
int sa[maxn];//后缀数组sa[i]就表示排名为i的后缀的起始位置的下标
int ranks[maxn]; //它的映射数组ranks[i]就表示起始位置的下标为i的后缀的排名
int height[maxn]; //表示Suffix[SA[i]]和Suffix[SA[i - 1]]的最长公共前缀,也就是排名相邻的两个后缀的最长公共前
int wt[30]; //快写
int n,m;

inline void putout(int x){ 
    if(!x) {
        putchar(48);
        return;
    }
    int l=0;
    while(x) wt[++l]=x%10,x/=10;
    while(l) putchar(wt[l--]+48);
}

inline void get_SA() {
    for (int i=1; i<=n; ++i) ++c[x[i]=s[i]];
    //c数组是桶
    //x[i]是第i个元素的第一关键字
    for (int i=2; i<=m; ++i) c[i]+=c[i-1];
    //做c的前缀和,我们就可以得出每个关键字最多是在第几名
    for (int i=n; i>=1; --i) sa[c[x[i]]--]=i;
    for (int k=1;k<=n;k<<=1){  //排序(倍增)
        int num=0;
        for (int i=n-k+1; i<=n; ++i) y[++num]=i;
        //y[i]表示第二关键字排名为i的数,第一关键字的位置
        //第n-k+1到第n位是没有第二关键字的 所以排名在最前面
        for (int i=1; i<=n; ++i) if(sa[i]>k) y[++num]=sa[i]-k;
        //排名为i的数 在数组中是否在第k位以后
        //如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进y就行了
        //所以i枚举的是第二关键字的排名,第二关键字靠前的先入队
        for (int i=1; i<=m; ++i) c[i]=0;
        //初始化c桶
        for (int i=1; i<=n; ++i) ++c[x[i]];
        //因为上一次循环已经算出了这次的第一关键字 所以直接加就行了
        for (int i=2; i<=m; ++i) c[i]+=c[i-1]; //第一关键字排名为1~i的数有多少个
        for (int i=n; i>=1; --i) sa[c[x[y[i]]]--]=y[i],y[i]=0;
        //因为y的顺序是按照第二关键字的顺序来排的
        //第二关键字靠后的,在同一个第一关键字桶中排名越靠后
        //基数排序
        swap(x,y);
        //这里不用想太多,因为要生成新的x时要用到旧的,就把旧的复制下来,没别的意思
        x[sa[1]]=1;
        num=1;
        for (int i=2; i<=n; ++i)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? num : ++num;
        //因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字
        if (num==n) break;
        m=num;
        //这里就不用那个122了,因为都有新的编号了
        }
        for (int i=1; i<=n; ++i) putout(sa[i]),putchar(' ');
    }
inline void get_height() {
    int k=0;
    for (int i=1; i<=n; ++i) ranks[sa[i]]=i;
        for (int i=1; i<=n; ++i) {
        if (ranks[i]==1) continue;//第一名height为0
        if (k) --k;//h[i]>=h[i-1]-1;
        int j=sa[ranks[i]-1];
        while (j+k<=n && i+k<=n && s[i+k]==s[j+k]) ++k;
        height[ranks[i]]=k;//h[i]=height[ranks[i]];
    }
    putchar(10);
    for (int i=1; i<=n; ++i) putout(height[i]),putchar(' ');
}
int main() {
    gets(s+1);
    n=strlen(s+1);
    m=122;
    //因为这个题不读入n和m所以要自己设
    //n表示原字符串长度,m表示字符个数,ascll('z')=122
    //我们第一次读入字符直接不用转化,按原来的ascll码来就可以了
    //因为转化数字和大小写字母还得分类讨论,怪麻烦的
    get_SA();
//get_height();
}

一个串中两个串的最大公共前缀是多少?
这不就是Height吗?用rmq预处理,再O(1)查询。

8.前缀循环节(KMP的fail函数)

对于具有n个字符的给定字符串的每个前缀s (每个字符具有介于97和126,包含在内),我们想知道前缀是否是周期字符串。也就是说,对于每个i ( 2 ≤ i ≤ n ) (2≤i≤n) (2in)我们想知道最大的k>1 (如果有) 这样的前缀s的长度i可以是写为a k,这是连接k次,对于某些字符串a的缀。当然,我们还想知道周期k。
str :aabaabaabaab
fail:-1 0 1 0 1 2 3 4 5 6 7 8 9
output:
2 2
6 2
9 3
12 4
fail[i]>=0&&i%(i-fail[i])==0 判断前缀是否循环
k=i/(i-fail[i]) 循环的次数

#include 
using namespace std;
const int N=1e6+5;
char str[N];
int fail[N];
int len;

void get_fail(char *P) {
    int i=0,j=-1;fail[i]=j;
    while(i<len){
        if (j==-1||P[j]==P[i]){
            fail[++i]=++j;
        }
        else  j=fail[j];
    }
}
 
int main(void){
    int cas = 0;
    while (scanf ("%d",&len)==1){
        if (!len) break;
        scanf ("%s",str);
        get_fail(str);
        for (int i=0;i<=len;++i) {
            printf ("%d ",fail[i]);
        } 
        puts("");
        printf("Test case #%d\n",++cas);
        for (int i=1;i<=len;++i){
            if (fail[i]>=0&&i%(i-fail[i])==0){
                int k=i/(i-fail[i]);
                if(k>1){
                    printf ("%d %d\n",i,k);
                }
            }
        }
        puts ("");
    }
    return 0;
}

9.AC自动机

#include 
using namespace std;
const int N = 10000 * 50 + 10;
struct AC_automaton {
    int tr[N][26], cnt; //TRIE
    int e[N];           //标记字符串结尾
    int fail[N];        //fail指针

    void init() {
        memset(tr, 0, sizeof(tr));
        memset(e, 0, sizeof(e));
        memset(fail, 0, sizeof(fail));
        cnt = 0;
    }
    void insert(char *s) {
        //插入模式串
        int p = 0;
        for (int i = 0; s[i]; i++) {
            int k = s[i] - 'a';
            if (!tr[p][k])
                tr[p][k] = ++cnt;
            p = tr[p][k];
        }
        e[p]++;
    }
    void build() {
        queue<int> q;
        memset(fail, 0, sizeof(fail));
        for (int i = 0; i < 26; i++)
            if (tr[0][i])
                q.push(tr[0][i]);
        //首字符入队
        //不直接将0入队是为了避免指向自己
        while (!q.empty()) {
            int k = q.front();
            q.pop(); //当前结点
            for (int i = 0; i < 26; i++) {
                if (tr[k][i]) {
                    fail[tr[k][i]] = tr[fail[k]][i]; //构建当前的fail指针
                    q.push(tr[k][i]);                //入队
                } else
                    tr[k][i] = tr[fail[k]][i];
                //匹配到空字符,则索引到父节点fail指针对应的字符,以供后续指针的构建
                //类似并差集的路径压缩,把不存在的tr[k][i]全部指向tr[fail[k]][i]
                //这句话在后面匹配主串的时候也能帮助跳转
            }
        }
    }
    int query(char *t) {
        int p = 0, res = 0;
        for (int i = 0; t[i]; i++) {
            p = tr[p][t[i] - 'a'];
            for (int j = p; j && ~e[j]; j = fail[j]) {
                res += e[j], e[j] = -1;
            }
        }
        return res;
    }
};
AC_automaton AC;
char txt[1000000 + 10];
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        AC.init();
        int n;
        scanf("%d", &n);
        char str[60];
        while (n--) {
            scanf("%s", str);
            AC.insert(str);
        }
        AC.build();
        scanf("%s", txt);
        printf("%d\n", AC.query(txt));
    }
    return 0;
}

10.后缀自动机

ACM巨全模板(下)_第9张图片

#include
#include
#define maxc 28
using namespace std;
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
typedef long long ll;
int len[maxn * 2], //最长子串的长度(该节点字串数量=len[x]-len[link[x]])
link[maxn * 2],   //后缀链接(最短串前部减少一个字符所到达的状态)
cnt[maxn * 2],    //被后缀连接的数
nex[maxn * 2][maxc],  //状态转移(尾部加一个字符的下一个状态)(图)
idx, //节点编号
last;    //最后节点
ll epos[maxn * 2]; // enpos数(该状态子串出现数量)
char str[maxn];
ll a[maxn];		//长度为i的子串出现最大次数

void Iint() {	//初始化
	last = idx = 1; //1表示root起始点 空集
	link[1] = len[1] = 0;
}
//SAM建图
void Extend(int c) {     //插入字符,为字符ascll码值
	int x = ++idx; //创建一个新节点x;
	len[x] = len[last] + 1; //  长度等于最后一个节点+1
	epos[x] = 1;  //接受节点子串除后缀连接还需加一
	int p;  //第一个有C转移的节点;
	for (p = last; p && !nex[p][c]; p = link[p])nex[p][c] = x;//沿着后缀连接 将所有没有字符c转移的节点直接指向新节点
	if (!p)link[x] = 1, cnt[1]++;  //全部都没有c的转移 直接将新节点后缀连接到起点
	else {
		int q = nex[p][c];    //p通过c转移到的节点
		if (len[p] + 1 == len[q])    //pq是连续的
			link[x] = q, cnt[q]++; //将新节点后缀连接指向q即可,q节点的被后缀连接数+1
		else {
			int nq = ++idx;   //不连续 需要复制一份q节点
			len[nq] = len[p] + 1;   //令nq与p连续
			link[nq] = link[q];   //因后面link[q]改变此处不加cnt
			memcpy(nex[nq], nex[q], sizeof(nex[q]));  //复制q的信息给nq
			for (; p&&nex[p][c] == q; p = link[p])
				nex[p][c] = nq;    //沿着后缀连接 将所有通过c转移为q的改为nq
			link[q] = link[x] = nq; //将x和q后缀连接改为nq
 			cnt[nq] += 2; //  nq增加两个后缀连接
		}
	}
	last = x;  //更新最后处理的节点
}
void GetNpos() {	//求npos数,即该节点字串出现次数
	queue<int>q;
	for (int i = 1; i <= idx; i++)
		if (!cnt[i])q.push(i);   //将所有没被后缀连接指向的节点入队
	while (!q.empty()) {
		int x = q.front();
		q.pop();
		epos[link[x]] += epos[x]; //子串数量等于所有后缀连接指向该节点的子串数量和+是否为接受节点
		if (--cnt[link[x]] == 0)q.push(link[x]);   //当所有后缀连接指向该节点的处理完毕后再入队
	}
}
void GetSubNum() {	//求不相同字串数量
	ll ans = 0;
	for (int i = 2; i <= idx; i++)ans += len[i] - len[link[i]];	//一状态子串数量等于len[i]-len[link[i]]
	printf("%lld\n",ans);
}
void GetSubMax() {	//求出所有长度为k的子串中出现次数最多的子串出现次数
	GetNpos();
	int n = strlen(str);
	for (int i = 1; i <= idx; i++)a[len[i]] = max(a[len[i]], epos[i]);	//长度≤k的子串中出现次数最多的子串出现次数的最小值
	for (int i = n - 1; i >= 1; i--)a[i] = max(a[i], a[i + 1]);		//求一遍后缀最大值就是答案
	for (int i = 1; i <= n; i++)printf("%lld\n", a[i]);

}
int main() {
	//freopen("c:/input.txt","r",stdin);
	return 0;
}

小技巧:

1.关于int,double强转为string

直接看代码,又暴力,又愉快。

	int a=520;
    float b=5.20;
    string str="dong";
    string res=str+to_string(a);
    cout<<res<<endl; //dong520
    res=str+to_string(b);
    res.resize(8); //控制字符串的大小
    cout<<res<<endl; //dong5.20

2.输入输出挂

直接a=read()读入,out(a)输出;

template <typename T>
void read(T &x)
{
    x = 0;
    int f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    x *= f;
    return;
}
template <typename T>
void write(T x)
{
    if (!x)
    {
        putchar('0');
        return;
    }
    char F[40];
    T tmp = x > 0 ? x : -x;
    if (x < 0)
        putchar('-');
    int cnt = 0;
    while (tmp > 0)
    {
        F[cnt++] = tmp % 10 + '0';
        tmp /= 10;
    }
    while (cnt > 0)
        putchar(F[--cnt]);
}

4.一些组合数学公式

ACM巨全模板(下)_第10张图片

5.二维坐标的离散化

坐标(3,2000),(10005,31),(10006,5)
离散为(1,5),(3,3),(4,1)。

#include
using namespace std;
const int maxn=200;//这个就是离散完的图的大小
const int maximum=100000;
struct Node
{
    int x,y;
}node[maxn];
int x[maxn],y[maxn];
int  nx[maxn], ny[maxn];
int m[maxn*2+5][maxn*2+5];
int maxx=-1,maxy=-1;
void discrete(int n)
{
    int tot1 = 0,tot2 = 0;
    //x y数组下标从1开始
    //一般1是图的最小边界,最大边界maximum看题意自己定吧。
    x[0]=1,x[n+1]=maximum;
    y[0]=1,y[n+1]=maximum;
    sort(x,x+n+2);//数组长n+2
    sort(y,y+n+2);
    int len1=unique(x,x+n+2)-x; //排序去重
    int len2=unique(y,y+n+2)-y; //排序去重
    
    //离散x轴
    for(int i=0;i<len1;i++){
        if(i&&x[i]!=x[i-1]+1)nx[tot1++]=x[i]-1,nx[tot1++]=x[i];
        else nx[tot1++]=x[i];
    }

    //离散y轴
    for(int i=0;i<len2;i++){
        if(i&&y[i]!=y[i-1]+1) ny[tot2++]=y[i]-1,ny[tot2++]=y[i]; //中间有有空行记得减为一行空行
        else ny[tot2++]=y[i];
    }

    //用映射关系将需离散的点放入离散图中
    for(int i=0;i<n;i++){
        int newx=lower_bound(nx, nx+tot1,node[i].x)-nx;
        int newy=lower_bound(ny, ny+tot2,node[i].y)-ny;
        //cout<
        maxx=max(maxx,newx); //找最大上边界
        maxy=max(maxy,newy); //找最大左边界
        m[newx][newy]=1;
    }
}

int main(){
    int A,B,N;
    scanf("%d %d %d",&A,&B,&N); //输入长度宽度和点的个数
    for(int i=0;i<N;i++) {
        scanf("%d %d",&node[i].x,&node[i].y); //输入点的坐标
        x[i+1]=node[i].x;
        y[i+1]=node[i].y;
    }
    discrete(N);
    for(int i=0;i<=maxx;i++){  //输出离散化后的图
        for(int j=0;j<=maxy;j++){
            printf("%d ",m[i][j]);
        }printf("\n");
    }
}

6.消除向下取整的方法

a)

ACM巨全模板(下)_第11张图片

7.一些常用的数据结构 (STL)

a)bitset
C++的 bitset 在 bitset 头文件中,它是一种类似数组的结构,它的每一个元素只能是0或1,每个元素仅用1bit空间。

bitset<4> bitset1;  //无参构造,长度为4,默认每一位为0
bitset<8> bitset2(12);  //长度为8,二进制保存,前面用0补充
string s = "100101";
bitset<10> bitset3(s);  //长度为10,前面用0补充 
char s2[] = "10101";
bitset<13> bitset4(s2);  //长度为13,前面用0补充
cout << bitset1 << endl;  //0000
cout << bitset2 << endl;  //00001100
cout << bitset3 << endl;  //0000100101
cout << bitset4 << endl;  //0000000010101

用字符串构造时,字符串只能包含 ‘0’ 或 ‘1’ ,否则会抛出异常。
构造时,需在<>中表明bitset 的大小(即size)。
在进行有参构造时,若参数的二进制表示比bitset的size小,则在前面用0补充(如上面的栗子);若比bitsize大,参数为整数时取后面部分,参数为字符串时取前面部分.
bitset对于二进制有位操作符
此外,可以通过 [ ] 访问元素(类似数组),注意最低位下标为0

bitset<8> foo ("10011011");
    cout << foo.count() << endl;  //5  (count函数用来求bitset中1的位数,foo中共有5个1
    cout << foo.size() << endl;   //8  (size函数用来求bitset的大小,一共有8位
    cout << foo.test(0) << endl;  //true  (test函数用来查下标处的元素是0还是1,并返回false或true,此处foo[0]为1,返回true
    cout << foo.test(2) << endl;  //false  (同理,foo[2]为0,返回false
    cout << foo.any() << endl;  //true  (any函数检查bitset中是否有1
    cout << foo.none() << endl;  //false  (none函数检查bitset中是否没有1
    cout << foo.all() << endl;  //false  (all函数检查bitset中是全部为1
bitset<8> foo ("10011011");

    cout << foo.flip(2) << endl;  //10011111  (flip函数传参数时,用于将参数位取反,本行代码将foo下标2处"反转",即0变1,1变0
    cout << foo.flip() << endl;   //01100000  (flip函数不指定参数时,将bitset每一位全部取反

    cout << foo.set() << endl;    //11111111  (set函数不指定参数时,将bitset的每一位全部置为1
    cout << foo.set(3,0) << endl;  //11110111  (set函数指定两位参数时,将第一参数位的元素置为第二参数的值,本行对foo的操作相当于foo[3]=0
    cout << foo.set(3) << endl;    //11111111  (set函数只有一个参数时,将参数下标处置为1

    cout << foo.reset(4) << endl;  //11101111  (reset函数传一个参数时将参数下标处置为0
    cout << foo.reset() << endl;   //00000000  (reset函数不传参数时将bitset的每一位全部置为0
bitset<8> foo ("10011011");

    string s = foo.to_string();  //将bitset转换成string类型
    unsigned long a = foo.to_ulong();  //将bitset转换成unsigned long类型
    unsigned long long b = foo.to_ullong();  //将bitset转换成unsigned long long类型

    cout << s << endl;  //10011011
    cout << a << endl;  //155
    cout << b << endl;  //155

数学公式的推导:
Σ(|l-x|+|r-x|)最小,那么x就是所有l,r的中位数了

8.Devc++的使用技巧

1,改变字体:
工具–编辑器选项–显示
推荐:Consolas
2.改变背景和高亮
工具–编辑器选项–语法
推荐:Twilight
3.自动保存
工具–编辑器选项–自动保存
4.代码补全
工具–编辑器选项–代码补全(打开)
5.中文
tool–Environment Operation–Language

工具栏右键可关闭工具栏,也可设置快捷键

调试打开:
工具–编译器选项–代码生成/优化–连接器–产生调试信息(YES)(打开项目管理)

调试:
先编译(F9)–在调试(F5)–设置断点–在黑框输入样例–添加查看(可查看变量)–下一步

快捷键:
ctrl+shift+A 补全
ctrl+R 一键替换
ACM巨全模板(下)_第12张图片
F9 编译
F10 运行
F11 编译运行
ctrl+F11 全屏
ctrl+N 创建文件
ctrl+W 删除文件
ctrl+C 强制结束
ctrl+Z 终止运行
ctrl+S 保存
ctrl+M 分屏
ctrl+E 复制行
ctrl+D 删除行

9.封装好的一维离散化

int discretization(int *origin_data, int *discrete_data, int size_data) { //started index is 1
    for (int i = 1; i <= size_data; ++i)
        discrete_data[i] = origin_data[i];
    sort(discrete_data + 1, discrete_data + 1 + size_data);
    int size_discreted = unique(discrete_data + 1, discrete_data + 1 + size_data) - (discrete_data + 1);
    for (int i = 1; i <= size_data; ++i)
        origin_data[i] = lower_bound(discrete_data + 1, discrete_data + 1 + size_discreted, origin_data[i]) - discrete_data;
    return size_discreted;
}

10.Ubuntu对拍程序

cpp

freopen("in","r",stdin);
freopen("1.out","w",stdout);
freopen("in","r",stdin);
freopen("2.out","w",stdout);

对拍程序

#include
using namespace std;
int main(){
	int c=0;
	do{
		if(c)printf("#%d AC\n",c);
		++c;
		system("./make");
		system("./1");
		system("./2");
	}while(!system("diff 1.out 2.out"));
	printf("#%d WA\n",c);
	return 0;
}

数据生成器

#include
using namespace std;
int main(){
	freopen("in","w",stdout);
	srand(time(0));
	int n=srand()%10000;
	while(n==1)n=srand()%10000;
	int m=srand()%50000;
	printf("%d %d\n",n,m);
	for(int i=1;i<=m;i++){
		int a=srand()%n+1;
		int b=srand()%n+1;
		while(a==b)b=srand()%n+1;
		printf("%d %d\n",a,b);
	}
	return 0;
}

11.常数

const ull hash1 = 1610612741;
//const ull hash2 = 1610612747;
const double _PI = 3.14159265358979323846;       // PI
const double _1_PI = 0.31830988618379067154;     // 1/PI
const double _2_SQRTPI = 1.12837916709551257390; // 2/sqrt(PI)

12.codeblocks 的使用技巧

字体:
setting→editor→general settings→choose→Consolas
颜色:
setting→editor→syntax highlighting
色调85 饱和度123 亮度205.(豆沙绿)
风格:
更改风格为java括号在行末
1.Setting->Editor->Source Formatter->style->Bracket style 修改成Java
2.Setting->Editor->Source Formatter->padding ->Insert space padding around operators 打勾
快捷键:
Ctrl+R可以替换;
Ctrl+Shift+C 注释掉当前行或者选中快 Ctrl+Shift+x 解除注释
编译运行F9
补全:
进Settings里的Editor:
在general settings →Code-completion中,
将Automatically launch when typed # letter中的4改成2,这样打两个字母就会有提示了。
在Code-completion中:
• 将Keyword sets to additionally include中1到9都勾上
将Delay for auto-kick-in when typing [.::->]拉到 200ms,这样快点出来提示
• 选中Case-sensitive match,防止一些无关的东西干扰,如果你想它帮你纠正大小写,那就去掉勾
设置快捷键:
进Settings里的Editor:
在Keyboard short-cuts中将Edit->Code complete的快捷键
save改为ctrl+s
ACM巨全模板(下)_第13张图片
改为shift+ f。

codeblocks如果无法调试:(必须创建项目)
有些下载的codeblocks没有自带MinGW,没法编译,当你自己下载一个MinGW,你安装好了,可能不会带有gdb.exe文件。
1.点击Settings,选择Debugger选项
2.点击Default,选择Executable path,选择一个gdb.exe的文件,这个文件就在安装MinGW的文件夹的bin里面
3.如果没有这个gdb.exe文件,你就在这个网站下面下载一个,http://www.equation.com/servlet/equation.cmd?fa=gdb
4.下载好的gdb.exe复制到你的安装MinGW的文件夹的bin里面,就可以进行调试了
5.有些编译环境是有中文这些的不能编译,要选择新建的项目才可以编译

ACM巨全模板(下)_第14张图片
在这里插入图片描述
这个再按watchs即可查看各个数据的值。
数组也能看。

13.java大数

构造器描述 
BigDecimal(int)       创建一个具有参数所指定整数值的对象。 
BigDecimal(double) 创建一个具有参数所指定双精度值的对象。 
BigDecimal(long)    创建一个具有参数所指定长整数值的对象。 
BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。


方法描述 
add(BigDecimal)        BigDecimal对象中的值相加,然后返回这个对象。 
subtract(BigDecimal) BigDecimal对象中的值相减,然后返回这个对象。 
multiply(BigDecimal)  BigDecimal对象中的值相乘,然后返回这个对象。 
divide(BigDecimal)     BigDecimal对象中的值相除,然后返回这个对象。 
toString()                将BigDecimal对象的数值转换成字符串。 
doubleValue()          将BigDecimal对象中的值以双精度数返回。 
floatValue()             将BigDecimal对象中的值以单精度数返回。 
longValue()             将BigDecimal对象中的值以长整数返回。 
intValue()               将BigDecimal对象中的值以整数返回。
//1753 求A+B
import java.math.*;
import java.util.*;

public class Main
{
    public static void main(String args[]){

        BigDecimal c = new BigDecimal("0");
        BigDecimal d = new BigDecimal("0");
        BigDecimal e = new BigDecimal("0");
        BigDecimal f = new BigDecimal("0");
        Scanner cin = new Scanner(System.in);
        int n = cin.nextInt();
        while(n!=0){//等价于!=EOF
            n--;
            BigDecimal a = cin.nextBigDecimal();
            BigDecimal b = cin.nextBigDecimal();
            c = a.add(b);
            System.out.println(c.stripTrailingZeros().toPlainString());
            d = a.divide(b); //可以是小数负数,但是不能是无限小数
            System.out.println(d.stripTrailingZeros().toPlainString());
            e = a.subtract(b);
            System.out.println(e.stripTrailingZeros().toPlainString());
            f = a.multiply(b);
            System.out.println(f.stripTrailingZeros().toPlainString());
        }
    }

}
//1715 求斐波那契数列
import java.math.*;
import java.util.*;

public class Main
{
    public static void main(String args[]){

        BigDecimal a[] = new BigDecimal[1005];
        Scanner cin = new Scanner(System.in);
        while(cin.hasNext()){//等价于!=EOF

            a[1] = a[2] = new BigDecimal("1");
            for (int i =3; i< 1005; i++){
                a[i] = a[i-1].add(a[i-2]);
            }
            int N = cin.nextInt();
            for (int j = 0; j< N;j++){
                int pi = cin.nextInt();
                System.out.println(a[pi]);
            }
        }
    }

//1042 求阶乘
import java.math.*;
import java.util.*;

    public class Main
    {
        public static void main(String args[]){

            Scanner cin = new Scanner(System.in);
            while (cin.hasNext()){
                // 计算阶乘
                BigInteger c = new BigInteger("1");
                int n = cin.nextInt();
                for(int i = 1;i <= n; i++){
                    BigInteger s = BigInteger.valueOf(i);
                    c = c.multiply(s);
                }
                System.out.println(c);
            }
        }
    }


//1063 求R的n次方,注意输出格式的要求
import java.math.*;
import java.util.*;

    public class Main
    {
        public static void main(String args[]){

            Scanner cin = new Scanner(System.in);
            while (cin.hasNext()){
                // 计算n方
                BigDecimal r = cin.nextBigDecimal();
                int n = cin.nextInt();
                BigDecimal rn = new BigDecimal("1.0");
                rn = r.pow(n).stripTrailingZeros();//去掉字符串最后面的0以及来消除BigDecimal用科学计数形式来表示结果
                String tmp = rn.toPlainString();
                //去掉前导0
                if(tmp.startsWith("0"))
                    tmp=tmp.substring(1);

                System.out.println(tmp);
            }
        }
    }//1715 求斐波那契数列
import java.math.*;
import java.util.*;

public class Main
{
    public static void main(String args[]){

        BigDecimal a[] = new BigDecimal[1005];
        Scanner cin = new Scanner(System.in);
        while(cin.hasNext()){//等价于!=EOF

            a[1] = a[2] = new BigDecimal("1");
            for (int i =3; i< 1005; i++){
                a[i] = a[i-1].add(a[i-2]);
            }
            int N = cin.nextInt();
            for (int j = 0; j< N;j++){
                int pi = cin.nextInt();
                System.out.println(a[pi]);
            }
        }
    }

叮嘱:

1.遇到第k大记得考虑二分

注意:
1.版权归本作者李炜柯所有;2.未经原作者允许不得转载本文内容,否则将视为侵权;3.转载或者引用本文内容请注明来源及原作者;
文章由多篇博客整理而成,如有侵权,请联系本作者qq:1315602340

希望此篇模板能帮助到大家

你可能感兴趣的:(总结)