lp5104 红包发红包

在\(\lbrack 0,1 \rbrack \)中任取一个数,期望是\(\frac{1}{2}\)
同理,在\(\lbrack 0,w \rbrack \)中任取一个数,期望是\(\frac{w}{2}\)
故而,取\(k\)个数的期望是\(\frac{w}{2^k}\)
费马小定理加快速幂即可。

#include<iostream>
#include<cstdio>
const long long MOD = 1000000007;

inline long long pw(int A,long long X){
	long long RT=1,BS=A;
	while(X){
		if(X&1){
			RT*=BS;
			RT%=MOD;
		}
		BS*=BS;
		BS%=MOD;
		X>>=1;
	}
	return RT;
}

inline long long inv(int X){
	return pw(X,MOD-2);
}

long long w,n,k;

void init(){
	scanf("%lld%lld%lld",&w,&n,&k);
	printf("%lld\n",(w*(inv(pw(2,k))))%MOD);
}

int main(){
	init();
	return 0;
}

lp3391 【模板】文艺平衡树(Splay)

虽然说是模板题,但实际上时候在考察Splay一个很巧妙的运用,也就是用Splay模拟区间。
首先我们发现二叉排序树一个很巧妙的性质:对于一个点\(a\)和它的一个孩子\(b\),若令\(b\)是\(a\)的\(D\)方向的孩子,
那么区间\((a,b)\)内的所有数都在以\(b\)的\(D\ xor\ 1\)方向上的孩子为根的子树中。
然后我们考虑翻转操作。我们毫不惊讶地发现,当我们将一棵子树上的所有节点都左右调换以后,这棵子树所代表的区间就完成了翻转。
对于一个节点来说,它的位置就已经表示了它的大小。
但是,如果对于每一次翻转我们都翻转整棵子树的话,复杂度是不可接受的。我们考虑一种类线段树的操作方法,也就是使用\(Lazy Tag\)来维护翻转。
而,除了翻转操作之外,会改变树的结构的只有旋转操作。于是我们只需要在旋转之前下放\(x\)和\(fa\)的延迟标记即可。
另外尤其要注意不要旋转过头。

#include<iostream>
#include<cstdio>
class Splay{
	private:
		class Node{
			public:
				int v;
				int sz;
				int fa;
				int sn[2];
				int lzy;
		};
		Node tr[200005];
		int rt;
		inline void prnt(int X){
			if(!X){
				return;
			}
			pshd(X);
			prnt(tr[X].sn[0]);
			printf("%d ",tr[X].v);
			prnt(tr[X].sn[1]);
		}
		inline void debugPrnt(int X){
			if(!X){
				return;
			}
			pshd(X);
			debugPrnt(tr[X].sn[0]);
			printf("[%d,%d]{%d}:%d ",tr[X].sn[0],tr[X].sn[1],tr[X].fa,tr[X].v);
			debugPrnt(tr[X].sn[1]);
		}
		inline void pshd(int X){
			if(tr[X].lzy){
				tr[X].lzy^=1;
				tr[tr[X].sn[0]].lzy^=1,tr[tr[X].sn[1]].lzy^=1;
				std::swap(tr[X].sn[0],tr[X].sn[1]);
			}
		}
		inline void updt(int X){
			tr[X].sz=tr[tr[X].sn[0]].sz+tr[tr[X].sn[1]].sz+1;
		}
		inline int fndD(int X){
			return tr[tr[X].fa].sn[0]==X?0:1;
		}
		inline void splayOne(int X){
			int D=fndD(X),D2=fndD(tr[X].fa);
			pshd(tr[X].fa),pshd(X);
			tr[tr[X].sn[D^1]].fa=tr[X].fa,tr[tr[X].fa].sn[D]=tr[X].sn[D^1];
			tr[X].sn[D^1]=tr[X].fa,tr[X].fa=tr[tr[X].fa].fa;
			tr[tr[X].fa].sn[D2]=X,tr[tr[X].sn[D^1]].fa=X;
			updt(tr[X].sn[D^1]),updt(X);
		}
		inline void splayRnw(int X){
			while(tr[X].fa){
				splayOne(X);
			}
			rt=X; 
		}
		inline int fnd(int X){
			int P=rt,FP=0;
			while(P){
				pshd(P);
				FP=P;
				if(tr[tr[P].sn[0]].sz+1<X){
					X-=(tr[tr[P].sn[0]].sz+1);
					P=tr[P].sn[1];
				}else if(tr[tr[P].sn[0]].sz<X){
					return P;
				}else{
					P=tr[P].sn[0];
				}
			}
			return FP;
		}
	public:
		inline void build(int N){
			for(int i=1;i<=N;++i){
				tr[i].sn[0]=i-1;
				tr[i].v=i;
				tr[i].fa=i+1;
				tr[i].sz=1;
			}
			tr[N].fa=0;
			tr[1].sn[0]=N+1,tr[N].sn[1]=N+2;
			tr[N+1].v=-2147483647,tr[N+2].v=2147483647;
			tr[N+1].fa=1,tr[N+2].fa=N;
			tr[N+1].sz=1,tr[N+2].sz=1;
			rt=N;
			splayRnw(N+1);
			splayRnw(N+2);
		}
		inline void flp(int L,int R){
			//printf("[%d](%d,%d)\n",rt,fnd(L),fnd(R+2));
			splayRnw(fnd(L));
			splayRnw(fnd(R+2));
			tr[tr[tr[rt].sn[0]].sn[1]].lzy^=1;
		}
		inline void splayPrnt(int N){
			splayRnw(N+1);
			splayRnw(N+2);
			prnt(tr[tr[rt].sn[0]].sn[1]);
			puts("");
		}
		inline void debug(){
			printf("%d ->",rt);
			debugPrnt(rt);
			puts("");
		}
};
int n,q;
Splay T;
void init(){
	int n,q;
	scanf("%d%d",&n,&q);
	int l,r;
	T.build(n);
	for(int i=1;i<=q;++i){
		scanf("%d%d",&l,&r);
		T.flp(l,r);
	}
	T.splayPrnt(n);
}
int main(){
	init();
	return 0;
}

平衡树-Splay

我们将一棵以\(Splay\)方式维护的二叉搜索树封装为一个对象。
对于这个对象,我们定义它的一类私有对象\(Node\),代表二叉搜索树上的一个节点。
对于这个节点,在通常情况下,我们需要维护的信息包括:
\(sn\),这是一个有两个元素的数组。其中第\(0\)项表示左子节点,第\(1\)表示右子节点。
\(fa\),表示它的父节点。
\(v\),表示它的值。
\(nm\),表示值为\(v\)的数的个数。
\(sz\),表示所有子节点中的数的个数的和。

对于一棵二叉搜索树上的一个节点,我们定义它的旋转、单旋和双旋操作。
定义一个函数\(fndD(X)\),表示一个节点相对于它的父节点是\(fndD(X)\)子节点
那么,一个节点\(X\)的旋转操作为:
将它的\(fndD(X)\ xor\ 1\)子节点作为它的父节点的\(fndD(X)\)子节点,并将\(X\)作为它的祖父节点的\(fndD(X_fa)\)节点,然后将它原来的父节点变成它的\(fndD(X)\ xor \ 1\)节点。
定义一个节点的单旋操作为,将这个节点向上旋转两次。
定义一个节点的双旋操作为,将这个节点的父节点向上旋转一次,然后再将这个节点向上旋转一次。
同时,我们定义一个将一个节点一直依据下述方法单旋/双旋到根的操作为一个伸展操作:
对于一个相对于其父亲的位置与其父亲相对于其祖父的位置相同的节点,对它采用双旋操作。
对于一个相对于其父亲的位置与其父亲相对于其祖父的位置不同的节点,对它采用单旋操作。


下面我们用一种被称为势能分析法的复杂度分析方式来分析它的复杂度。

我们定义关于一个节点\(X\)的势能函数\(\phi(X)\),定义为它的子树大小取对数。我们定义关于整棵二叉搜索树\(T\)的一个势能函数\(\Phi (T)\),定义为它的所有节点的\(\phi\)值之和。

对于第\(i\)次操作,我们定义一个函数\(T\)和一个摊还代价辅助函数\(A\),其中\(T(i)\)表示第\(i\)次操作消耗的实际时间。\(A(i)\)由下述式子定义:
$$A_{i}=T_{i}+\Phi_{i}-\Phi_{i-1}$$
那么,我们可以得到公式:
$$A_{sum}=\sum_{i=1}^{q}A_{i}=\sum_{i=1}^{q}T_{i}+\Phi_{q}-\Phi_{0}$$
故而,我们只需要求出对于每一次操作的\(T\)与\(\Delta\Phi\)即可。

考虑到,每一个操作事实上都可以视为一次\(Splay\)操作,所以我们只需要考虑各类\(Splay\)操作造成的影响即可。
故而,我们需要计算的便是旋转、单旋和双旋对的操作复杂度以及它们对\(\Phi(T)\)的影响。

首先我们分析旋转操作。
容易知道,对于一次关于节点\(X\)的旋转操作,会改变\(\phi\)值的有且仅有两个节点:\(X\)和它的父亲\(Y\)。
我们定义\(X\)旋转后的节点为\(X’\),\(Y\)旋转后的节点为\(Y’\),很容易可以得到一个式子:
$$\Delta\Phi = \phi_{X’} – \phi_{X} + \phi_{Y’} – \phi_{Y}$$
考虑到旋转操作后,\(X’\)位于\(Y\)的位置,并且节点的数量没有增减、对父节点也没有影响,所以得到\(\phi_{X’}=\phi_{Y}\)
考虑\(Y’\)是\(X’\)的子节点,我们可以得到
$$ \phi_{X’} > \phi_{Y’}$$
将上述两式代入式中,可得:
$$\Delta\Phi < \phi_{X’} – \phi_{X}$$
于是可以证明,一次旋转的均摊复杂度上界是\(O(\phi_{X’}-\phi_{X})\)

然后我们分析单旋操作。
依然是定义一个关于节点\(X\)的旋转操作,定义它的父亲为\(Y\),祖父为\(Z\),旋转后分别为\(X’,Y’,Z’\)
很容易可以得到一个式子:
$$\Delta\Phi = \phi_{X’} – \phi_{X} + \phi_{Y’} – \phi_{Y} + \phi_{Z’} – \phi_{Z}$$
同理于旋转操作,我们可以得到:
$$\phi_{X’} = \phi_{Z}$$
故而:
$$\Delta\Phi = – \phi_{X} + \phi_{Y’} – \phi_{Y} + \phi_{Z’}$$
同理于旋转操作,我们可以得到:
$$\phi_{Y’}<\phi_{X’},\phi_{X}<\phi_{Y}$$ 故而:$$\Delta\Phi < \phi_{X’} + \phi_{Z’} – 2\phi_{X}$$$$\Delta\Phi < 2(\phi_{X’}-\phi_{X}) – \phi_{X’} – \phi_{Z’}$$又,$$\phi_{X’} + \phi_{Z’} = log_{2}{|X’||Z’|} > 2$$
所以,
$$\Delta\Phi < 2(\phi_{X’}-\phi_{X}) – 2$$

故而单旋操作的复杂度为\(O(\phi_{X’}-\phi_{X})\)

双旋操作的复杂度同理。

令总共进行了\(n\)次旋转操作,对于总体的复杂度上界,我们有:
$$A_{sum}<\sum_{i=1}^{n}\phi_{i}-\phi_{i-1}$$
故而得到最终的摊还复杂度:\(O(\phi_{n})\)


然而,上述的势能分析法仍然不够直观。下面我们考虑一种基于伸展操作的性质的另一种分析法。
引理一: 每一次伸展操作,必然会使得从这个节点到根的一条链的长度折半。
首先我们考虑单旋和双旋操作的使用情况。
我们称应当使用双旋的情况为一字型,应当使用单旋的情况为之字形,很容易可以发现,原来已有的一条链总是会被拆成两个部分。
而,详细地考虑各种情况,我们发现,不存在一种链,使得这种折半失效。
故而,我们得到了引理一。

下面考虑引理一是怎样在实际情况中发挥作用的。
我们说一个节点的深度可接受,指的是它的深度是\(log_2n\)的常数倍。
考虑对某一个节点进行操作。如果这个节点的深度可接受,那么操作它的复杂度也是可接受的。
如果这个节点的深度不可接受,根据引理一,总是可以在\(log_2n\)次操作以内将其的深度变为可接受的。
而每一次操作最多只会令这个链的深度加一,因此\(log_2n\)次操作的复杂度可以均摊到那些令这个链深度加一的操作上,从而使操作的均摊浮渣度仍然是可接受的。

注意:
1.三目运算符优先级比逗号高。
2.函数名不要搞混。
3.注意区分节点大小和子树大小。
4.当寻找前驱的时候,当前值与当前点的值相等应当向左走;当寻找后继时,当前值与当前点的值相等应当向右走。

#include<iostream>
#include<cstdio>
#include<cstring>
#define MAXQ 1000005
inline int Max(int A,int B){
    return A>B?A:B;
}
inline int Min(int A,int B){
    return A<B?A:B;
}
class Splay{
	private:
		class Node{
    		public:
    			int sn[2];
    			int fa;
    			int v;
    			int nm;
    			int sz;
    			inline void set(int FA,int LS,int RS,int V){
    			    fa=FA,v=V,sn[0]=LS,sn[1]=RS,nm=1,sz=1;
                }
                inline void init(){
                	sn[0]=sn[1]=fa=v=nm=sz=0;
				}
		};
		int INF;
		Node tr[MAXQ];
		int st[100005],tp,cnt,rt;
		inline int fndD(int X){
			return tr[tr[X].fa].sn[0]==X?0:1;
		}
		inline void szRnw(int X){
		    tr[X].sz=tr[tr[X].sn[0]].sz+tr[tr[X].sn[1]].sz+tr[X].nm;
        }
		inline void splayOne(int X){
			int D=fndD(X),D2=fndD(tr[X].fa);
			tr[tr[X].sn[D^1]].fa=tr[X].fa;tr[tr[X].fa].sn[D]=tr[X].sn[D^1];
			tr[X].sn[D^1]=tr[X].fa;tr[X].fa=tr[tr[X].sn[D^1]].fa;
			tr[tr[X].sn[D^1]].fa=X;tr[tr[X].fa].sn[D2]=X;
			szRnw(tr[X].sn[D^1]);szRnw(X);
			if(!tr[X].fa){
				rt=X;
			}
		}
		inline void splayTwo(int X){
		    int D=fndD(X),D2=fndD(tr[X].fa);
		    tr[X].fa?
				(tr[tr[X].fa].fa?
					(D==D2?
						(splayOne(tr[X].fa),splayOne(X),0)
						:(splayOne(X),splayOne(X),0))
					:(splayOne(X),0))
				:0;
		}
		inline void splayRnw(int X){
			while(tr[X].fa){
			    splayTwo(X);
            }
		}
		inline int nwlc(int FA,int LS,int RS,int V,int D){
			if(!cnt){
				rt=1;
			}
			int P=tp?st[tp--]:++cnt;
		    tr[FA].sn[D]=P;
		    tr[P].set(FA,LS,RS,V);
		    return P;
        }
        inline int fnd(int V){
            int P=rt,FP=0;
            while(P){
                FP=P;
                if(tr[P].v==V){
                    splayRnw(P);
                    return P;
                }
                P=tr[P].sn[tr[P].v>V?0:1];
            }
            return FP;
        }
        inline int fndMn(int X){
            int P=X,FP=tr[X].fa;
            while(P){
                FP=P;
                P=tr[P].sn[0];
            }
            return FP;
        }
        inline int fndMx(int X){
        	int P=X,FP=tr[X].fa;
        	while(P){
        		FP=P;
        		P=tr[P].sn[1];
			}
			return FP;
		}
		inline void clr(int X,int D){
			if(D==2){
				tr[X].init();
				st[++tp]=X;
				return;
			}
			rt=tr[X].sn[D],tr[tr[X].sn[D]].fa=0;
			tr[X].init();
			st[++tp]=X;
		}
		inline void init(int P){
			if(!tr[P].sn[0]||!tr[P].sn[1]){
				tr[P].sn[0]?(clr(P,0)):(tr[P].sn[1]?clr(P,1):clr(P,2));
			}else{
				int RS=fndMn(tr[P].sn[1]);
				tr[RS].sn[0]=tr[P].sn[0],tr[tr[P].sn[0]].fa=RS;
				clr(P,1);
				splayRnw(RS);
			}
			if(!tr[rt].v&&!tr[rt].sz&&!tr[rt].fa&&!tr[rt].nm){
				splayInit();
			}
		}
		inline void prnt(int X){
			if(!X){
				return;
			}
			prnt(tr[X].sn[0]);
			printf("(%d)%d:[%d,%d][%d] ",X,tr[X].v,tr[X].sn[0],tr[X].sn[1],tr[X].fa);
			prnt(tr[X].sn[1]);
		}
	public:
	    inline void psh(int V){
	        int P=rt,FP=0,D=0;
	        while(P){
	            FP=P;
	            if(tr[P].v==V){
	                ++tr[P].nm;
	                splayRnw(P);
	                return;
                }
                D=tr[P].v>V?0:1;
	            P=tr[P].sn[tr[P].v>V?0:1];
            }
            splayRnw(nwlc(FP,0,0,V,D));
        }
        inline void del(int V){
            int P=fnd(V);
            splayRnw(P);
            tr[P].nm>1?(--tr[P].nm):(init(P),0);
        }
        inline int fndPre(int V){
            int P=rt,RT=-INF;
            while(P){
                RT=tr[P].v<V?Max(RT,tr[P].v):RT;
                P=tr[P].sn[tr[P].v<V?1:0];
            }
            return RT;
        }
        inline int fndNxt(int V){
            int P=rt,RT=INF;
            while(P){
                RT=tr[P].v>V?Min(RT,tr[P].v):RT;
                P=tr[P].sn[tr[P].v>V?0:1];
            }
            return RT;
        }
        inline int rnk(int V){
            int P=rt,RT=0;
            while(P){
                if(tr[P].v>V){
                    P=tr[P].sn[0];
                }else if(tr[P].v==V){
                    RT+=tr[tr[P].sn[0]].sz;
                    splayRnw(P);
                    return RT+1;
                }else{
                    RT+=tr[tr[P].sn[0]].sz+tr[P].nm;
                    P=tr[P].sn[1]; 
                }
            }
            return RT+1;
        }
        inline int aRnk(int V){
            int P=rt;
            while(P){
                if(tr[tr[P].sn[0]].sz+tr[P].nm<V){
                    V-=(tr[tr[P].sn[0]].sz+tr[P].nm);
                    P=tr[P].sn[1];
                }else if(tr[tr[P].sn[0]].sz<V){
                    return tr[P].v;
                }else{
                    P=tr[P].sn[0];
                }
            }
            return P;
        }
        inline void splayInit(){
			INF=2147483647,tp=0,cnt=0,rt=0;
			memset(tr,0,sizeof(tr));
		}
		inline void splayPrnt(){
			prnt(rt);
			puts("");
		}
}; 
Splay T;
void init(){
	int n,x,op;
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		//T.splayPrnt();
		scanf("%d%d",&op,&x);
		switch(op){
			case 1:{
				T.psh(x);
				break;
			}
			case 2:{
				T.del(x);
				break;
			}
			case 3:{
				printf("%d\n",T.rnk(x));
				break;
			}
			case 4:{
				printf("%d\n",T.aRnk(x));
				break;
			}
			case 5:{
				printf("%d\n",T.fndPre(x));
				break;
			}
			case 6:{
				printf("%d\n",T.fndNxt(x));
				break;
			}
		}
	}
}
int main(){
//	freopen("Splay.out","w",stdout);
	T.splayInit();
	init();
	return 0;
}

博客已经恢复访问

在昨日晚上被短暂的墙以后,我申请了搬瓦工的更换IP服务,现在已经可以正常访问。
用亲身经历警告各位,千万不要在服务器上搭建任何梯子。

大江南北一片赤红。
博客恢复正常访问。

lp5077 Tweetuzki 爱等差数列

令首项为\(x\),长度为\(l\)由等差数列求和式可得:
$$s=\frac{l(l-1)}{2}+l*x$$
故,题意等价于求:
使得式子:
$$2s=l(l-1+2x)$$
成立的最小\(x\)
即求使得式子成立的最大\(l\)
又,易知:
$$l<\sqrt{2s},s\le10^{12}$$
故,
$$l<10^{6}$$
显然这个复杂度是可以接受的。

#include<iostream>
#include<cstdio>
#include<cmath>
long long s;
inline void init(){
	scanf("%lld",&s);
	long long ans=0,len;
	for(long long i=1;i*i<=2*s;++i){
		if((2*s%i==0)&&(2*s/i-i+1>0)&&((s-(i*(i-1)/2))%i==0)){
			len=i;ans=((2*s)/i-i+1)>>1;
		}
	}
	printf("%lld %lld",ans,ans+len-1);
}

int main(){
	init();
	return 0;
} 

lp5079 Tweetuzki 爱伊图

看似是一道暴力模拟题,实际上是一个技巧性模拟题。
我们注意到,对于除了2和5以外的所有的数,它们的每一列的#数量都是不同的,故而我们可以用1代表”#”,0代表”.”,然后跑一遍求和即可。
对于2和5,我们特殊处理,暴力扫描即可。

#include<iostream>
#include<cstdio>
#include<algorithm>

char lst[12][100005];
int lst2[100005][12];
int cnt[100005];
int r,c;

inline bool cmp(int A,int B){
	return A>B;
}

void init(){
	scanf("%d%d",&r,&c);
	for(int i=1;i<=r;++i){
		for(int j=1;j<=c;++j){
			std::cin>>lst[i][j];
		}
	}
	for(int i=1;i<=r;++i){
		for(int j=1;j<=c;++j){
			lst2[j][i]=lst[i][j]=='#'?1:0;
		}
	}
	for(int i=1;i<=c;++i){
		std::sort(lst2[i]+1,lst2[i]+1+r,cmp);
	}
	for(int i=1;i<=c;++i){
		for(int j=1;j<=r;++j){
			cnt[i]+=lst2[i][j];
		}
	}
	int cnt1=0,cnt2=0,cnt3=0;
	for(int i=1;i<=c;++i){
		if(!cnt[i]){
			cnt1=cnt2=cnt3=0;
		}else{
			cnt1=cnt[i],cnt2=cnt[i+1],cnt3=cnt[i+2];
			if(cnt1==5&&!cnt2){
				printf("1");
			}else{
				if(cnt1==4&&cnt2==3&&cnt3==4){
					bool bo=0;
					for(int j=1;j<=r;++j){
						if(lst[j][i]=='.'&&lst[j][i+1]=='.'&&lst[j][i+2]=='#'){
							bo=1;
						}else if(lst[j][i]=='#'&&lst[j][i+1]=='.'&&lst[j][i+2]=='.'){
							if(bo){
								printf("2");
							}else{
								printf("5");
							}
							break;
						}
					}
				}else if(cnt1==3&&cnt2==3&&cnt3==5){
					printf("3");
				}else if(cnt1==3&&cnt2==1&&cnt3==5){
					printf("4");
				}else if(cnt1==5&&cnt2==3&&cnt3==4){
					printf("6");
				}else if(cnt1==1&&cnt2==1&&cnt3==5){
					printf("7");
				}else if(cnt1==5&&cnt2==3&&cnt3==5){
					printf("8");
				}else if(cnt1==4&&cnt2==3&&cnt3==5){
					printf("9");
				}else if(cnt1==5&&cnt2==2&&cnt3==5){
					printf("0");
				}
				i+=2; 
			}
		}
	}
}

int main(){
	init();
	return 0;
}

lp2540 NOIP2015 斗地主增强版

首先,我们注意到花色对于斗地主不会造成任何影响,所以我们可以忽视掉花色这个信息。
然后,我们将剩余的信息,依据数码的从小到大储存。
然后我们可以注意到,对于一种出牌方式,调整一些牌组的出牌顺序对答案并不会造成影响。
并且,散牌似乎具有贪心的性质。看起来如果能出三带二那么出三带一就不会更优,如果能出四带二那出三带二就不会更优。
对于顺子的情况是一个例外,因为如果能出顺子的话,可能存在一种情况,使得不出顺子比出顺子更优;亦有可能将一个顺子出一部分会更优。故而对于顺子应该爆搜判定。
总结起来就是:爆搜出顺子,贪心出散牌。
但是仔细考虑,我们可以发现,这种贪心是一种想当然的对正确答案的近似。这事实上仅因为四不可带一这一性质。
故而,对于4 4 1 2的情况,用上述的贪心得出的答案应当是3,然而事实上只需2即可。
但是,前面仍然有一个结论是正确的——也就是,出牌顺序不对答案造成影响。又考虑到,数码的顺序其实仅对顺子而言有意义。
所以我们可以仅储存,数量为k的数码有多少种,从而可以用5^13的空间复杂度预处理每一种散牌的状态。
但是如果这么做的话会发现连样例二都过不了。这事实上是因为,暴力搜索散牌的时间复杂度是完全不可接受的。
考虑到状态数极少,可以记忆化搜索来完成预处理。
用f[i][j][k][l]表示,有i个1,j个2,k个3,l个4时的最小解决花费即可。
几个坑点:
1.出顺子的时候应当是-=,+=而非直接赋值。
2.预先判双王以后应当计算花费。
3.出顺子的时候,在遍历到长度没达到要求的区间的时候也应当先将其清零然后在遍历之后加回去。
4.注意四带二的各种拆法:理论上应当有\(C_{4}^{2}+C_{3}^{2}\)种,少一种都不行,例如:两组三张和两组四张可以把两组三张拆开来打。
真可以说是丧心病狂的模拟题。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define dfs(X) X.srch()
//所有数+10之后mod13,13小王,14大王。 
inline int Max(int A,int B){
    return A>B?A:B;
}
inline int Min(int A,int B){
    return A<B?A:B;
}
struct statue;
int n,ans=0x3f3f3f3f,f[14][14][9][7];
int CNT[4]={0,0,0,0};
struct statue{
    int cst;
    int a[15];
    inline void init(){
        for(int i=0;i<15;++i){
            a[i]=0;
        }
    }
    inline statue(int cstin){
        cst=cstin;
    }
    inline void srch(){
        /*
        for(int i=0;i<15;++i){
            printf("%d ",a[i]);
        }
        printf("\n");
        */
        statue nw(cst+1);
        for(int i=0;i<15;++i){
            nw.a[i]=a[i];
        }
        for(int i=0;i<12;++i){
            if(a[i]>=3){
                nw.a[i]-=3;
                for(int j=i+1;j<12;++j){
                    if(a[j]<3){
                        break;
                    }
                    nw.a[j]-=3;
                    dfs(nw);
                }
                for(int j=i+1;j<12;++j){
                    if(a[j]<3){
                        break;
                    }
                    nw.a[j]+=3;
                }
                nw.a[i]+=3;
            }
            if(a[i]>=2){
                nw.a[i]-=2;
                for(int j=i+1;j<12;++j){
                    if(a[j]<2){
                        break;
                    }
                    nw.a[j]-=2;
                    if(j-i<2){
                        continue;
                    }
                    dfs(nw);
                }
                for(int j=i+1;j<12;++j){
                    if(a[j]<2){
                        break;
                    }
                    nw.a[j]+=2;
                }
                nw.a[i]+=2;
            }
            if(a[i]>=1){
                --nw.a[i];
                for(int j=i+1;j<12;++j){
                    if(a[j]<1){
                        break;
                    }
                    --nw.a[j];
                    if(j-i<4){
                        continue;
                    }
                    dfs(nw);
                }
                for(int j=i+1;j<12;++j){
                    if(a[j]<1){
                        break;
                    }
                    ++nw.a[j];
                }
                ++nw.a[i];
            }
        }
        CNT[0]=CNT[1]=CNT[2]=CNT[3]=0;
        for(int i=0;i<15;++i){
        	++CNT[a[i]-1];
		}
		ans=Min(ans,cst+f[CNT[0]][CNT[1]][CNT[2]][CNT[3]]);
//		printf("%d:%d %d %d %d\n",cst,CNT[0],CNT[1],CNT[2],CNT[3]);
    }
};
int II=0,JJ=0,KK=0,LL=0;
inline int rnw(int A,int B,int C,int D){
	if(A<0||B<0||C<0||D<0||A>13||B>13||C>8||D>6){
		return 0;
	}
    f[A][B][C][D]=Min(f[A][B][C][D],f[II][JJ][KK][LL]+1);
    return 0;
}
inline void prpr(){
    memset(f,0x3f,sizeof(f));
    f[0][0][0][0]=0;
    for(int i=0;i<=13;++i){
        for(int j=0;j<=13;++j){
            for(int k=0;k<=8;++k){
                for(int l=0;l<=6;++l){
//                	printf("%d %d %d %d\n",i,j,k,l);
                    II=i,JJ=j,KK=k,LL=l;
                    rnw(i,j,k,l+1);
                    rnw(i+2,j,k,l+1);
                    rnw(i,j+1,k,l+1);
                    rnw(i-1,j,k+1,l+1),rnw(i+1,j-1,k+1,l+1),rnw(i,j-1,k,l+2),rnw(i+1,j,k-1,l+2),rnw(i-1,j+1,k-1,l+2),rnw(i,j-2,k+2,k+1);
                    rnw(i,j+2,k,l+1);
                    rnw(i,j,k,l+2);
                    rnw(i-1,j+1,k+1,l+1),rnw(i-1,j-1,k+1,l+2),rnw(i-2,j,k+2,l+1);
                    
                    rnw(i,j,k+1,l);
                    rnw(i+1,j,k+1,l);
                    rnw(i,j+1,k+1,l);
                    
                    rnw(i,j+1,k,l);
                    
                    rnw(i+1,j,k,l);
                }
            }
        }
    }
}
void init(){
    ans=0x3f3f3f3f;
    int x,xx;
    statue s(0);
    s.init();
    for(int i=1;i<=n;++i){
        scanf("%d%d",&x,&xx);
        ++s.a[(!x)?(12+xx):((x+10)%13)];
    }
    if(s.a[13]&&s.a[14]){
        s.a[13]=s.a[14]=0;
        ++s.cst;
        dfs(s);
        --s.cst;
        s.a[13]=s.a[14]=1;
    }
    dfs(s);
    printf("%d\n",ans);
}
int main(){
	prpr();
    int T;
    scanf("%d%d",&T,&n);
    while(T--){
        init();
    }
    return 0;
}

 

lp2661 NOIP2015 信息传递

找图上最小环。删掉所有非环边即可。

#include<iostream>
#include<cstdio> 
struct ee{
    int v;
    int nxt;
}e[200005];
int h[200005],et=0;
bool vis[200005];
int n,in[200005],a[200005];
inline void add(int u,int v){
    e[++et]=(ee){v,h[u]};
    h[u]=et;
}
inline int fnd(int s){
    int x=s,nxt=a[x],rt=1;
    vis[x]=1;
    while(!vis[nxt]){
        ++rt;
        x=nxt;
        nxt=a[nxt];
        vis[x]=1;
    }
    return rt;
}
void init(){
    scanf("%d",&n);
    int x;
    for(int i=1;i<=n;++i){
        scanf("%d",&x);
        add(i,x);
        ++in[x];
        a[i]=x;
    }
    int nw,nwxt;
    for(int i=1;i<=n;++i){
        nw=i;
        while(!in[nw]){
            --in[a[nw]];
            nwxt=nw;
            nw=a[nw];
            a[nwxt]=0;
        }
    }
    int ans=0x3f3f3f3f;
    for(int i=1;i<=n;++i){
        if(!vis[i]&&a[i]){
            ans=std::min(ans,fnd(i));
        }
    }
    printf("%d\n",ans);
}

int main(){
    init();
    return 0;
}

 

二叉搜索树

二叉搜索树。
二叉搜索树是一棵二叉树。对于这棵树上的任意一个节点,右子树中的所有节点都大于它;左子树中的所有节点都小于它。
基于这个性质,我们可以以\(O(h)\)的时间的复杂度,来完成下述操作:
1.插入一个元素。
2.删除其中任意一个元素。
3.查找其中的一个元素。
4.查找第\(k\)大数。
5.询问某数的排名。
6.以常数更小的方式来查找一个数的前驱/后继。
7.以\(O(n)\)输出有序的整棵树。
8.查找将一个数插入树后的前驱/后继。
9.查找一个节点的子树大小。
下面我们一一讨论下述操作的实现。
操作6可能是最容易理解的一种操作。对于一个节点,如果要寻找它的后继,那么它的后继一定在如下两个位置之一:它的右子树,或者它的父亲。
如果它有右子树,那么它的后继一定在它的右子树中。这是因为,对于其他所有点,要么比它和它的右子树中的所有节点更大,要么比它们更小。这由二叉搜索树的基本性质可以轻松证明。
然后,它的后继一定是它的右子树中的最小点,这是因为右子树中的所有点都大于它。显然,这个最小点是右子树中最左的节点。我们称其为「后继右左定理」。
而上述的内容对于求一个有左子树的节点的前驱显然仍然是成立的。
如果它没有右子树,那么对于它的所有祖先,存在两种情况。
一是不存在任意一个祖先,使得这个祖先的父亲在这个祖先的右边,这种情况下,因为它没有右子树,所以它一定是整棵树中最大的节点,这样它就没有后继了;
另外一种是至少存在一个祖先,使得这个祖先的父亲在这个祖先的左边。
即,以这个祖先为根的整棵子树都是这个祖先的父亲的左子树,则此时很显然该节点是整棵子树中最右的节点。那么它显然是这个祖先的父亲的前驱。这可以根据「前驱左右定理」得出。
这样,我们就可以对任意的一个节点找到它的(如果存在)前驱和/或后继。
在有了操作6之后,我们就可以考虑操作1。如果插入的数比已有的任何数都大/都小,那么就一定可以插入到最右/左节点的右/左儿子。
如果不是这样的话,那么,它的大小一定在原来的一对大小相邻的数之间。
而综合「前驱左右定理」和「后继右左定理」,可以得出,一对大小相邻的节点之中,有且仅有一方的可以通过在子树中寻找来找到另一方。
这也就意味着,较大者的左子树和较小者的右子树中有且仅有一个是空的。那么,我们就一定可以把一个新的节点插入到一个空节点。
然后我们考虑操作3。它可以通过递归的二分操作来实现。假设我们当前在以\(x\)为根节点的子树中查找指定节点,
那么我们就可以根据二叉搜索树的性质,判断这个节点是\(x\),或位于\(x\)的某一棵子树中。
于是我们就可以递归查找任意一个数。
同理,操作8也是可以用相似的方法来实现。当这个数递归查找到一个无法继续查找的节点\(x\),
那么\(x\)一定是需要查找的数的前驱/后继,然后我们就可以通过操作6来查找这个数的后继/前驱。
接下来我们考虑操作4。操作4本质上也是一个树上二分的过程。我们首先计算出所有子树的大小,然后依据子树大小的单调性来递归查找第\(k\)大数。
于是我们考虑操作5,操作5可以在实现操作3的过程中实现。当每一次我们递归查找,我们同样也可以确定目标排名所在的一个区间,并且不断缩小这个区间。
事实上,每一次向下递归查找的过程,都确定了一部分的数一定大于或者小于目标数。当找到目标节点的时候,目标节点的区间就可以根据已有信息和它的左右子树(如果存在)来确定。
操作7就显得很显然了。通过中序遍历,我们可以以小-中-大的顺序来遍历整棵二叉树,也就将二叉树展开为一个有序序列。
它体现了二叉搜索树的性质的一个有趣的性质——对于任意一棵子树,它总是对应着有序序列上的一个连续区间。
这是因为,这棵子树之外的所有点,要么大于这棵子树中的所有点,要么小于这棵子树中的所有点。
另一方面,我们也可以从顶向下来理解这个过程。
对于根节点,它的左子树中的所有节点(如果存在)在序列中一定位于它左侧,而右子树(如果存在)中的所有节点在序列中一定位于它的右侧。
那么,递归地考虑每个子树,根据二叉搜索树的基本性质,它们都对应着一个有序的序列。所以它们总是对应着有序序列上的一个连续区间。
通过这种理解方式,我们也可以相应的理解(乃至证明)上诉的所有操作和性质。
下面是所有操作中最复杂的操作2,事实上操作2是唯一一个可能改变已有结构的操作。
对于需要删除的节点,存在三种情况。
情况一:它是一个叶节点。对于这种情况,我们只需要单纯地删除它,然后自底向顶更新子树大小。
情况二:它只有一个子节点。对于这种情况,我们只需要单纯地删除它,然后将它的子树接在它的父节点,然后自顶向上更新子树大小。
情况三:它既有左节点又有右节点。这种情况可能会比较复杂。首先我们知道,这个节点必然有前驱/后继。不妨将它的其他部分放在它的后继上。(前驱同理。)
这时候分两类讨论。
如果它的后继不是它的右子节点,那么可以将它的后继接在它的父节点,然后将它的右子树的其他部分作为它的后继的右子树。然后将它的左子树作为它的后继的左子树。
否则,我们将它的左节点放在它的后继的左节点处,然后将它的后继接在它的父节点。
对于操作9,我们只需要在每次更新的时候自顶向上计算贡献,存储好即可直接写出。
二叉搜索树是平衡树的基础,由于没有相关模板题,所以我也不太确定我的二叉搜索树有没有写挂。请各位多多指教。
 

#include<iostream>
#include<cstdio>
int st[100005],tp=0;
struct data{
    int v;int fa;int ls;int rs;int sz;int cnt;
}tr[100005];
int rot=0,trt=0;
inline int nwlc(){
    return tp?st[tp--]:++trt;
}
inline void init(int X){
    tr[X].cnt=tr[X].fa=tr[X].ls=tr[X].rs=tr[X].sz=tr[X].v=0;
    st[++tp]=X;
}
inline void rnw(int X){
    int P=X;
    while(P){
        tr[P].sz=tr[tr[P].ls].sz+tr[tr[P].rs].sz+tr[P].cnt;
        P=tr[P].fa;
    }
}
inline int nwpt(int V,int FA,int LS,int RS,int SZ){
    if(!rot){
        int P=1;trt=1;tp=0;rot=1;
        tr[P]=(data){V,0,0,0,1,1};
        return P;
    }
    int P=nwlc();
    tr[FA].v>V?tr[FA].ls=P:tr[FA].rs=P;
    tr[P]=(data){V,FA,LS,RS,SZ,1};
    return P;
}
inline int fndNm(int X){
    int P=rot,FP=0;
    while(P){
        FP=P;
        if(tr[P].v==X){
            FP=P;
            break;
        }
        P=X<tr[P].v?tr[P].ls:tr[P].rs;
    }
    return FP;
}
inline int fndMx(int X){
    int P=X,FP;
    while(P){
        FP=P;
        P=tr[P].rs;
    }
    return FP;
}
inline int fndMn(int X){
    int P=X,FP;
    while(P){
        FP=P;
        P=tr[P].ls;
    }
    return FP;
}
inline int fndPre(int X){
    if(tr[X].ls){
        return fndMx(tr[X].ls);
    }else{
        int P=X,NP;
        while(P){
            NP=P;
            P=tr[P].fa;
            if(tr[P].rs==NP){
                break;
            }
        }
        return P;
    }
}
inline int fndNxt(int X){
    if(tr[X].rs){
        return fndMn(tr[X].rs);
    }else{
        int P=X,NP;
        while(P){
            NP=P;
            P=tr[P].fa;
            if(tr[P].ls==NP){
                break;
            }
        }
        return P;
    }
}
inline int fndPP(int X){
    int MX=fndMx(rot);
    if(X<tr[MX].v){
        return MX;
    }
	int P=rot,FP=0;
	while(P){
		FP=P;
		if(tr[P].v==X){
			FP=P;
			break;
		}else{
			if(tr[P].v>X&&tr[tr[P].ls].v<X){
				FP=P;
				break;
			}else if(tr[P].v<X&&tr[tr[P].rs].v>X){
				FP=tr[P].rs;
				break;
			}
		}
		P=X<tr[P].v?tr[P].ls:tr[P].rs;
	}
	return fndPre(FP);
}
inline int fndNN(int X){
    int MN=fndMn(rot);
    if(X<tr[MN].v){
        return MN;
    }
	int P=rot,FP=0;
	while(P){
		FP=P;
		if(tr[P].v==X){
			FP=P;
			break;
		}else{
			if(tr[P].v>X&&tr[tr[P].ls].v<X){
				FP=tr[P].ls;
				break;
			}else if(tr[P].v<X&&tr[tr[P].rs].v>X){
				FP=P;
				break;
			}
		}
		P=X<tr[P].v?tr[P].ls:tr[P].rs;
	}
	return fndNxt(FP);
}
inline void trIns(int X){
    int P=rot,FP;
    while(P){
        FP=P;
        if(tr[P].v==X){
            ++tr[P].cnt;
            rnw(P);
            return;
        }
        P=(tr[P].v>X)?tr[P].ls:tr[P].rs;
    }
    rnw(nwpt(X,FP,0,0,1));
}
inline void cnnctP(int FA,int X,int SN){
    if(!SN){
        return;
    }
    tr[SN].fa=FA;
    tr[FA].ls==X?(tr[FA].ls=SN):(tr[FA].rs=SN);
}
inline void delP(int FA,int X,int SN){
    (tr[X].cnt>1)?--tr[X].cnt,cnnctP(FA,X,SN):cnnctP(FA,X,SN),init(X);
    rnw(SN?SN:FA);
}
inline void trDel(int X){
    if(!tr[X].ls&&!tr[X].rs){
        delP(tr[X].fa,X,0);
    }else if(tr[X].ls&&!tr[X].rs){
        delP(tr[X].fa,X,tr[X].ls);
    }else if(tr[X].rs&&!tr[X].ls){
        delP(tr[X].fa,X,tr[X].rs);
    }else{
        if(tr[X].rs==fndNxt(X)){
            tr[tr[X].ls].fa=tr[X].rs;
            tr[tr[X].rs].ls=tr[X].ls;
            delP(tr[X].fa,X,tr[X].rs);
        }else{
            int XN=fndNxt(X),LW;
            LW=tr[XN].fa;
            cnnctP(tr[XN].fa,XN,tr[XN].rs);
            tr[XN].rs=tr[X].rs;
            tr[tr[X].rs].fa=XN;
            tr[XN].ls=tr[X].ls;
            tr[tr[X].ls].fa=XN;
            tr[X].rs=XN;
            tr[XN].fa=X;
            delP(tr[X].fa,X,XN);
            rnw(LW);
        }
    }
}
inline int fndRnkN(int X){
    int P=rot;
    while(P){
        if(X<=tr[tr[P].ls].sz){
            X-=tr[tr[P].ls].sz;
            P=tr[P].ls;
        }else if(X<=tr[tr[P].ls].sz+tr[P].sz){
            return P;
        }else{
            X-=(tr[tr[P].ls].sz+tr[P].cnt);
            P=tr[P].rs;
        }
    }
    return -1;
}

inline int fndRnkS(int X){
    int P=rot,RT=0;
    while(P){
        P=(X<tr[P].v)?tr[P].ls:tr[P].rs,RT+=tr[tr[P].ls].sz;
    }
    return RT;
}
inline void trPrnt(int X){
    if(tr[X].ls){
        trPrnt(tr[X].ls);
    }
    printf("%d ",tr[X].v);
    if(tr[X].rs){
        trPrnt(tr[X].rs);
    }
}
int q;
void init(){
    scanf("%d",&q);
    int op,x;
    for(int i=1;i<=q;++i){
        scanf("%d%d",&op,&x);
        switch(op){
            case 1: {
                trIns(x);
                break;
            }
            case 2: {
                trDel(fndNm(x));
                break;
            }
            case 3: {
                printf("%d\n",fndRnkS(x));
                break;
            }
            case 4:{
                printf("%d\n",tr[fndRnkN(x)].v);
                break;
            }
            case 5: {
                printf("%d\n",tr[fndPP(x)].v);
                break;
            }
            case 6: {
                printf("%d\n",tr[fndNN(x)].v);
                break;
            }
        }
    }
} 
int main(){
    init();
    return 0;
}

 

lp2679 NOIP2015 子串

首先观察数据范围和题意,我们猜测这是一道DP题。
我们首先维护\(f_{i,j,k}\)表示,对于\(B\)中的前\(i\)个字符,在\(A\)中的前\(k\)个字符中,可以用\(j\)个子串来完成匹配。
那么,对于一个当前的字符\(i\),它如果需要匹配,我们有两种匹配方法:一是加到已有的串,二是新开一个串。
加到已有的串是很好转移的,直接\(f_{i+1,j,k+1}+=(A_{k+1}==B_{i+1})*f_{i,j,k}\);
而如果是新开一个串,朴素的考虑肯定是从\(k\)往后扫,然后把状态转移到所有之后的可行的位置:\(f_{i+1,j+1,S}+=f{i,j,k}\)。
但将一个状态转移到多个状态的加速转移是很难实现的,所以我们考虑使用填表法。
很容易可以发现,对于一个状态\(f_{i,j,k}\),它可以从上述的两种状态转移到。
故而我们可以得到一个朴素的转移方程:
\(f_{i,j,k}=(A_{k}==B_{i})*f_{i-1,j,k-1}+\sum_{S=i}^{k}f_{i-1,j-1,S}\)
观察可以发现,这个转移方程的复杂度瓶颈在于后面那个求和式——这使得整个转移的复杂度是\(O(n^{2}*m*k)\)的。
我们考虑优化转移。区间求和的转移优化已经可以说是套路了,我们只需要简单地维护一个前缀和即可。
用\(sm_{i,j,k}\)表示,要用\(1~k\)中的串\(j\)个串填充\(1~i\)的位置的方案数。
同时,\(40000000\)的空间复杂度对于\(128MB\)的内存限制来说是非常危险的。所以我们考虑滚动数组。
这样就做完了。

#include<iostream>
#include<cstdio>
/* 

*/
const long long MOD=1000000007;

int n,m,K;
long long f[2][205][1005],sm[2][205][1005];
char A[1005],B[205];
void init(){
    scanf("%d%d%d",&n,&m,&K);
    std::cin>>(A+1)>>(B+1);
    int nw=1;sm[0][0][0]=1;
    for(int i=1;i<=n;++i){
    	sm[nw][0][0]=1;
    	for(int j=1;j<=m;++j){
    		for(int k=1;k<=K;++k){
    			f[nw][j][k]=(A[i]==B[j])?((f[nw^1][j-1][k]+sm[nw^1][j-1][k-1])%MOD):0;
    			sm[nw][j][k]=(sm[nw^1][j][k]+f[nw][j][k])%MOD;
			}
		}
		nw^=1;
	}
    printf("%lld\n",sm[nw^1][m][K]);
}

int main(){
    init();
    return 0;
}