• 2019年7月博客汇总下


    [ZJOI2007]捉迷藏

    这是我最近写过最长的代码QAQ
    码力太弱了QAQ
    动态点分治模板题。
    我们可以用三种堆来维护答案,这些堆要求支持删除非顶元素,以及查询次小值。我们把两个STL堆封装起来就可以实现。

    三种堆:

    d[x]表示以x为根的点分树中所有黑点到它分治爹的距离
    c[x]表示以x为根的所有点分儿子d堆中的最大值
    ans表示全局的最大值

    我们从c中取出最大值和次大值就可以得到过这个点分根的最长链。我们不断用它来更新答案。

    注意d的定义是到分治父亲的父亲的最大值
    c数组要加入一个0

    Code

    #include<cstdio>
    #include<queue>
    #include<algorithm>
    using namespace std;
    namespace orz{
    const int N=210000;
    const int inf=2147483647;
    struct heap{
    	priority_queue<int>q,rm;
    	heap(){
    		q.push(-inf);
    		rm.push(-inf+1);
    	}
    	inline void push(int x){
    		q.push(x);
    	}
    	inline void remove(int x){
    		if(q.top()==x)q.pop();
    		else rm.push(x);
    	}
    	inline int top(){
    		while(q.top()==rm.top())q.pop(),rm.pop();
    		return q.top();
    	}
    	inline void pop(){
    		while(q.top()==rm.top())q.pop(),rm.pop();
    		q.pop();
    	}
    	inline int second(){
    		int mx=top();
    		if(mx==-inf)return -inf;
    		q.pop();
    		int ans=top();
    		q.push(mx);
    		return ans;
    	}
    	inline int size(){
    		return q.size()-rm.size();
    	}
    	inline void show(){
    		priority_queue<int>res;
    		while(size()){
    			res.push(top());
    			printf("%d ",top());
    			pop();
    		}
    		putchar('
    ');
    		while(res.size()){
    			q.push(res.top());
    			res.pop();
    		}
    	}
    }ans,d[N],c[N];
    //d表示所有的黑点到这个分治根的距离
    //c是所有分治儿子的最长链
    int head[N],ver[N],next[N],tot;
    int size[N];
    int father[N];
    int dis[N][20];
    int color[N],cnt;
    int depth[N];
    int n,m;
    int root,all,rootmax;
    bool rm[N];
    inline void add(int x,int y){
    	next[++tot]=head[x],head[x]=tot,ver[tot]=y;
    	next[++tot]=head[y],head[y]=tot,ver[tot]=x;
    }
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    void write(int x){
    	if(x)write(x/10),putchar('0'+x%10);
    }
    inline void print(int x){
    	if(x<0){
    		putchar('-');
    		x=-x;
    	}
    	else if(!x){
    		putchar('0');
    	}
    	write(x);
    	putchar('
    ');
    }
    inline bool getOrder(){
    	char t;
    	do{t=getchar();}while(t!='G'&&t!='C');
    	return t=='G';
    }
    void getroot(int x,int fa){
    	size[x]=1;
    	int maxpart=-inf;
    	for(int i=head[x];i;i=next[i]){
    		if(rm[ver[i]]||ver[i]==fa)continue;
    		getroot(ver[i],x);
    		size[x]+=size[ver[i]];
    		maxpart=max(maxpart,size[ver[i]]);
    	}
    	maxpart=max(maxpart,all-size[x]);
    	if(maxpart<rootmax)rootmax=maxpart,root=x;
    }
    inline void addAns(heap &a){
    	ans.push(a.top()+a.second());
    }
    inline void removeAns(heap &a){
    	ans.remove(a.top()+a.second());
    }
    inline int ask(){
    	if(cnt<=1)return cnt-1;
    	return ans.top();
    }
    inline void BFS(int x,int dep){
    	queue<int>q;
    	q.push(x);
    	int t;
    	while(q.size()){
    		t=q.front();
    		q.pop();
    		d[x].push(dis[t][depth[father[x]]]);
    		for(int i=head[t];i;i=next[i]){
    			if(rm[ver[i]])continue;
    			if(dis[ver[i]][dep])continue;
    			dis[ver[i]][dep]=dis[t][dep]+1;
    			q.push(ver[i]);
    		}
    	}
    }
    int build(int x,int fa,int dep,int sum){
    	//得到根
    	rootmax=inf;
    	all=sum;
    	getroot(x,0);
    	x=root;
    	//保证size是对的
    	getroot(x,0);
    	//记录在点分树上的爸爸
    	father[x]=fa;
    	rm[x]=true;
    	depth[x]=dep;
    	//得到这一层所有点到分治根的距离
    	BFS(x,dep);
    	int son;
    	//自己也算在所有链中
    	c[x].push(0);
    	for(int i=head[x];i;i=next[i]){
    		if(rm[ver[i]])continue;
    		son=build(ver[i],x,dep+1,size[ver[i]]);
    		c[x].push(d[son].top());
    	}
    	addAns(c[x]);
    	return x;
    }
    inline void turnOn(int x){
    	//先更新对自己的影响
    	removeAns(c[x]);
    	c[x].remove(0);
    	addAns(c[x]);
    	//一层一层往上跳
    	for(int i=x;father[i];i=father[i]){
    		//先删去对答案为影响
    		removeAns(c[father[i]]);
    		c[father[i]].remove(d[i].top());
    		d[i].remove(dis[x][depth[father[i]]]);
    		c[father[i]].push(d[i].top());
    		addAns(c[father[i]]);
    	}
    }
    inline void turnOff(int x){
    	removeAns(c[x]);
    	c[x].push(0);
    	addAns(c[x]);
    	for(int i=x;father[i];i=father[i]){
    		removeAns(c[father[i]]);
    		c[father[i]].remove(d[i].top());
    		d[i].push(dis[x][depth[father[i]]]);
    		c[father[i]].push(d[i].top());
    		addAns(c[father[i]]);
    	}
    }
    inline void work(){
    //	for(int i=1;i<=n;++i)
    //		c[i].show();
    //	for(int i=1;i<=3;++i){
    //		for(int j=1;j<=n;++j)
    //			printf("%d ",dis[j][i]);
    //		putchar('
    ');
    //	}
    	cnt=n;
    	int x;
    	m=read();
    	for(int i=1;i<=m;++i){
    		switch(getOrder()){
    			case true:
    				print(ask());
    				break;
    			case false:
    				x=read();
    				switch(color[x]){
    					case 0:
    						//开灯
    						turnOn(x);
    						color[x]=1;
    						--cnt;
    						break;
    					case 1:
    						//熄灯
    						turnOff(x);
    						color[x]=0;
    						++cnt;
    						break;
    				}
    				break;
    		}
    	}
    }
    int QAQ(){
    	n=read();
    	for(int i=1;i<n;++i)
    		add(read(),read());
    	build(1,0,1,n);
    	work();
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    7.27爆零赛

    这次考试题起名比较随意,题目难度不是按顺序排的,大概是倒序的。

    给出(n)个正整数(a_1,a_2…a_n)和一个质数(mod).一个变量(x)初始为(1).进行(m)次操作.每次在(n)个数中随机选一个(a_i),然后(x=xa_i).问(m)次操作之后(x)的取值的期望。
    答案输出a乘b的逆元的形式

    NOIP模拟赛T1考O(n^2)矩阵乘法和原根,我佛了。
    首先我们可以把问题转化为一个假期望,我们只需要求出每一种数值的方案数乘上数值大小除以(n^m)就可以了。
    我们可以很容易的发现dp的转移是类似矩阵转移的形式。
    所以就去打了一个(O(mod^3log m))的矩阵快速幂。然后因为常数太大被卡常卡死了。
    题解中说这样可以得80分,然而一些人尝试去卡常只得了50分,而我卡了半天的才卡到了30分。
    这个数据把mod设得太卡矩阵乘法导致了暴力反而能得更多的分,我觉得这很不合理QAQ。
    我们很容易看出来这里需要一个(O(n^2))的矩阵乘法,但我们现在只知道一个循环矩阵能做到。
    所以正解肯定是一个循环矩阵。
    但是我们发现因为原题中做的是乘法,所以转移是乱跳的,这不好。而出题人给了我们一些提示.

    孙金宁教你学数学
    质数P的原根g满足1<=rt<P,且rt的1次方,2次方…(P-1)次方在模P意义下可以取遍1到(P-1)的所有整数.
    欧拉定理:对于质数P,1<=x<P的任意x的P-1次方在模P意义下都为1.
    显然,原根的1次方,2次方…(P-2)次方在模P意义下都不为1,只有(P-1)次方在模P意义下为1.这也是一个数成为原根的充分必要条件.

    因为原根的几次方可以取遍模p剩余系的所有数,反过来说就是每一个数都可以用原根的几次方的形式表示。那就很棒了,因为这样就可以将乘法变成加法,而加法是明显会形成一个循环矩阵的,所以这题就A了。

    Code

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    namespace orz{
    const int N=1100;
    const int MOD=1000000007;
    bool vis[N];
    int b[N];
    int c[N];
    int n,m,mod;
    int root;
    int ans[N],base[N];
    int res[N];
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    inline bool check(long long x){
    	long long res=x;
    	for(int i=1;i<mod-1;++i){
    		if(res==1)return false;
    		res=res*x%mod;
    	}	
    	return true;
    }
    inline int pow(long long x,int y){
    	long long ans=1;
    	while(y){
    		if(y&1)ans=ans*x%MOD;
    		x=x*x%MOD;
    		y>>=1;
    	}
    	return ans;
    }
    inline void mul(int *a,int *b){
    	for(int i=0;i<mod-1;++i)
    		res[i]=0;
    	for(int i=0;i<mod-1;++i)
    		for(int j=0;j<mod-1;++j)
    			res[(i+j)%(mod-1)]=(res[(i+j)%(mod-1)]+1ll*a[i]*b[j]%MOD)%MOD;
    	for(int i=0;i<mod-1;++i)
    		a[i]=res[i];
    }
    int QAQ(){
    	n=read(),m=read(),mod=read();
    	for(int i=1;i<mod;++i)
    		if(check(i)){
    			root=i;
    			break;
    		}
    	for(int i=1,x=root;i<mod-1;++i,x=x*root%mod)
    		b[x]=i,c[i]=x;
    	c[0]=1,b[1]=0;
    	for(int i=1;i<=n;++i)
    		++base[b[read()]];
    	ans[0]=1;
    	int y=m;
    	while(y){
    		if(y&1)mul(ans,base);
    		mul(base,base);
    		y>>=1;	
    	}
    	long long fans=0;
    	for(int i=0;i<mod;++i)
    		fans=(fans+(long long)ans[i]*c[i]%MOD)%MOD;
    	printf("%lld
    ",fans*pow(pow(n,m),MOD-2)%MOD);
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    后面两道题懒得转格式了

    单车联通大街小巷.这就是出题人没有写题目背景的原因.
    对于一棵树,认为每条边长度为1,每个点有一个权值a[i].dis(u,v)为点u到v的最短路径的边数.dis(u,u)=0.对每个点求出一个重要程度.点x的重要程度b[x]定义为其他点到这个点的距离乘上对应的点权再求和. 即:b[x]=a[1]dis(1,x)+a[2]dis(2,x)+....+a[n]*dis(n,x)
    现在有很多树和对应的a数组,并求出了b数组.不幸的是,记录变得模糊不清了.幸运的是,树的形态完好地保存了下来,a数组和b数组至少有一个是完好无损的,但另一个数组完全看不清了.
    希望你求出受损的数组.多组数据.

    从a求b:
    换根DP,秒了。
    从b求a:
    我们取1号节点为根。
    sum表示所有节点a值之和,size表示子树a值之和。
    我们考虑从a求b的过程,因为我们原来是换根求的b数组,所以相邻两个节点的b值相减后为(sum-2size_y)
    如果我们能知道sum的话所有值就能求出来了,但是要怎么求sum呢,考场上没想出来QAQ。
    总是想着把它们加起来可以搞出sum,结果不行。
    我们发现一号节点没有这个式子,所以我们把它的式子暴力写出来
    (b_1=sumlimits_{i=1}^{n}a_idep_i)
    我们惊奇的发现这个式子等于
    (sumlimits_{i=2}^nsize_i)
    减一下就出来了。
    考场上就差最后两行了,Orz。

    Code

    #include<cstdio>
    #include<algorithm>
    #define tkj 0
    using namespace std;
    namespace orz{
    const int N=300000;
    long long a[N],b[N],size[N];
    int head[N],next[N],ver[N],tot;
    long long qwq[N];
    long long sum=0;
    inline int read(){
        int a=1,b=0;char t;
        do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
        do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
        return a*b;
    }
    inline void add(int x,int y){
    	next[++tot]=head[x],head[x]=tot,ver[tot]=y;
    	next[++tot]=head[y],head[y]=tot,ver[tot]=x;
    }
    inline void dky(int x,int father){
    	size[x]=a[x];b[x]=0;
    	for(int i=head[x];i;i=next[i]){
    		if(ver[i]==father)continue;
    		dky(ver[i],x);
    		size[x]+=size[ver[i]];
    		b[x]+=b[ver[i]]+size[ver[i]];
    	}
    }
    inline void ykd(int x,int father){
    	for(int i=head[x];i;i=next[i]){
    		if(ver[i]==father)continue;
    		qwq[ver[i]]=b[ver[i]]-b[x];
    		ykd(ver[i],x);
    	}
    }
    inline void sfd(int x,int father){
    	a[x]=size[x]=(sum-qwq[x])/2;
    	for(int i=head[x];i;i=next[i]){
    		if(ver[i]==father)continue;
    		sfd(ver[i],x);
    		a[x]-=size[ver[i]];
    	}
    }
    inline void dfs(int x,int father){
    	for(int i=head[x];i;i=next[i]){
    		if(ver[i]==father)continue;
    		b[ver[i]]=b[x]+sum-2*size[ver[i]];
    		dfs(ver[i],x);
    	}
    }
    int QAQ(){
    //	freopen("qaq.in","r",stdin);
    	int t;
    	t=read();
    	while(t--){
    		int n=read();
    		for(int i=1;i<=n;++i)
    			head[i]=0;
    		tot=0;
    		for(int i=1;i<n;++i)
    			add(read(),read());
    		switch(read()){
    			case 0:
    				sum=0;
    				for(int i=1;i<=n;++i)
    					sum+=(a[i]=read());
    				dky(1,tkj);
    				dfs(1,tkj);
    				for(int i=1;i<=n;++i)
    					printf("%lld ",b[i]);
    				break;
    			case 1:
    				sum=0;
    				for(int i=1;i<=n;++i)
    					b[i]=read();
    				ykd(1,tkj);
    				for(int i=2;i<=n;++i)
    					sum+=qwq[i];
    				sum=(sum+2*b[1])/(long long)(n-1);
    				sfd(1,tkj);
    				a[1]=sum;
    				for(int i=2;i<=n;++i)
    					a[1]-=a[i];
    				for(int i=1;i<=n;++i)
    					printf("%lld ",a[i]);
    				break;
    		}
    		putchar('
    ');
    	}
        return false;
    }
    }
    int main(){
        return orz::QAQ();
    }
    

    出个题就好了.这就是出题人没有写题目背景的原因.
    你在平面直角坐标系上.
    你一开始位于(0,0).
    每次可以在上/下/左/右四个方向中选一个走一步.
    即:从(x,y)走到(x,y+1),(x,y-1),(x-1,y),(x+1,y)四个位置中的其中一个.
    允许你走的步数已经确定为n.现在你想走n步之后回到(0,0).但这太简单了.你希望知道有多少种不同的方案能够使你在n步之后回到(0,0).当且仅当两种方案至少有一步走的方向不同,这两种方案被认为是不同的.
    答案可能很大所以只需要输出答案对109+7取模后的结果.(109+7=1000000007,1和7之间有8个0)
    这还是太简单了,所以你给能够到达的格点加上了一些限制.一共有三种限制,加上没有限制的情况,一共有四种情况,用0,1,2,3标号:
    0.没有任何限制,可以到达坐标系上所有的点,即能到达的点集为{(x,y)|x,y为整数}
    1.只允许到达x轴非负半轴上的点.即能到达的点集为{(x,y)|x为非负数,y=0}
    2.只允许到达坐标轴上的点.即能到达的点集为{(x,y)|x=0或y=0}
    3.只允许到达x轴非负半轴上的点,y轴非负半轴上的点以及第1象限的点.即能到达的点集为{(x,y)|x>=0,y>=0}

    type0,1,3 组合数,秒了
    type2的数据较小,明显是一个DP,可是我不会。
    有10分数据是<=100的,复杂度大一些也能过,暴力建图后跑矩阵快速幂。复杂度(O((2n)^3log n)),成功水到分。
    正解是DP,我们把问题转化,变成了从(0,0)出发沿一个方向瞎走,回到(0,0),换(或不换)个方向再次瞎走。于是就变成了一个类似背包的东西,原行走序列被分成了一段一段的,考场上想到这里就没往下想,因为如果在半路走回零会导致统计重复,所以我们的一个单位应该是从零出去再回来中间不经过(0,0),这是(i-2)/2的卡特兰数,DP就完了。

    Code

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    namespace orz{
    const int N=210000;
    const int MOD=1000000007;
    long long fac[N];
    long long inv[N];
    long long f[N];
    int tot;
    inline int read(){
        int a=1,b=0;char t;
        do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
        do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
        return a*b;
    }
    inline int pow(long long x,int y){
    	long long ans=1;
    	while(y){
    		if(y&1)ans=ans*x%MOD;
    		x=x*x%MOD;
    		y>>=1;
    	}
    	return ans;
    }
    inline long long C(int n,int m){
    	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
    }
    inline long long Catalan(int n){
    	return (C(2*n,n)-C(2*n,n-1)+MOD)%MOD;
    }
    int QAQ(){
    	int n=read();
    	fac[0]=1;
    	for(int i=1;i<=n;++i)
    		fac[i]=fac[i-1]*i%MOD;
    	inv[n]=pow(fac[n],MOD-2);
    	for(int i=n-1;i>=0;--i)
    		inv[i]=inv[i+1]*(i+1)%MOD;
    	long long ans;
    	switch(read()){
    		case 0:
    			ans=0;
    			for(int i=0;i<=n;i+=2){
    				ans=(ans+C(n,i)*C(i,i/2)%MOD*C(n-i,(n-i)/2)%MOD)%MOD;
    			}
    			printf("%lld
    ",ans);
    			break;
    		case 1:
    			printf("%lld
    ",Catalan(n/2));
    			break;
    		case 2:
    			f[0]=1;
    			for(int i=0;i<=n;i+=2)
    				for(int j=0;j<i;j+=2)
    					f[i]=(f[i]+f[j]*4*Catalan((i-j)/2-1))%MOD;
    			printf("%lld
    ",f[n]);
    			break;
    		case 3:
    			ans=0;
    			for(int i=0;i<=n;i+=2)
    				ans=(ans+C(n,i)*Catalan(i/2)%MOD*Catalan((n-i)/2)%MOD)%MOD;
    			printf("%lld
    ",ans);
    			break;
    	}
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    循环矩阵学习笔记

    循环矩阵是一个方阵,大概形式是这样的:
    12345
    51234
    45123
    34512
    23451
    它有一些很好的性质,比如循环矩阵加循环矩阵还是循环矩阵,循环矩阵乘循环矩阵还是一个循环矩阵。
    所以我们的矩阵乘法就可以由(O(n^3))优化到(O(n^2))我们只需要把矩阵(n^2)的乘出来一行,之后就都是循环的了,最暴力的方法就是(O(n^2))赋值,这样的复杂度是对的,可是它不够优美。我们可以用一个(n^2)的循环来代替这个过程,我们只需要计算好每一个位置对哪里有贡献就可以了。
    如果矩阵是从0开始的,贡献的位置是((i+j)mod n)
    如果矩阵是从1开始的,贡献的位置是((i+j-2)mod n+1)

    [P5056][模板]插头DP

    插头DP模板题,大力分类讨论就完了。

    Code

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    namespace orz{
    const int MOD=233333,N=1000000;
    int map[14][14];
    int n,m;
    int ex,ey;
    long long ans;
    struct HashMap{
    	int head[MOD],val[N],next[N],tot;
    	long long cnt[N];
    	inline void clear(){
    		memset(head,0,sizeof(head));
    		tot=0;
    	}
    	inline long long& operator[](const int &x){
    		int pos=x%MOD;
    		for(int i=head[pos];i;i=next[i])
    			if(val[i]==x)
    				return cnt[i];
    		next[++tot]=head[pos],head[pos]=tot,val[tot]=x,cnt[tot]=0;
    		return cnt[tot];
    	}
    }f[2];
    inline int find(int &s,int id){
    	return s>>((id-1)<<1)&3;
    }
    inline void set(int &s,int id,int val){
    	id=(id-1)<<1;
    	s&=~(3<<id);
    	s|=val<<id;
    }
    inline int link(int &s,int id){
    	int delta=(find(s,id)==1?1:-1);
    	int t,cnt=0;
    	for(int pos=id;pos<=m+1;pos+=delta){
    		t=find(s,pos);
    		if(t==1)++cnt;
    		else if(t==2)--cnt;
    		if(!cnt)return pos;
    	}
    	return -1;
    }
    inline void solve(int x,int y){
    	HashMap &now=f[((x-1)*m+y)&1];
    	HashMap &last=f[(((x-1)*m+y)&1)^1];
    	now.clear();
    	int tot=last.tot;
    	int t1,t2;
    	long long val;
    	int state;
    	for(int i=1;i<=tot;++i){
    		state=last.val[i];
    		val=last.cnt[i];
    		t1=find(state,y);
    		t2=find(state,y+1);
    		if(!t1&&!t2){
    			if(map[x][y]&&map[x+1][y]&&map[x][y+1]){
    				set(state,y,1);
    				set(state,y+1,2);
    				now[state]+=val;
    			}
    			else if(!map[x][y]){
    				now[state]+=val;
    			}
    		}
    		else if(!t1&&t2){
    			if(map[x][y+1])now[state]+=val;
    			if(map[x+1][y]){
    				set(state,y,t2);
    				set(state,y+1,0);
    				now[state]+=val;
    			}	
    		}
    		else if(t1&&!t2){
    			if(map[x+1][y])now[state]+=val;
    			if(map[x][y+1]){
    				set(state,y+1,t1);
    				set(state,y,0);
    				now[state]+=val;	
    			}
    		}
    		else if(t1==1&&t2==1){
    			set(state,link(state,y+1),1);
    			set(state,y,0);
    			set(state,y+1,0);
    			now[state]+=val;
    		}
    		else if(t1==1&&t2==2){
    			if(x==ex&&y==ey)
    				ans+=val;
    		}
    		else if(t1==2&&t2==1){
    			set(state,y,0);
    			set(state,y+1,0);
    			now[state]+=val;
    		}
    		else if(t1==2&&t2==2){
    			set(state,link(state,y),2);
    			set(state,y,0);
    			set(state,y+1,0);
    			now[state]+=val;
    		}
    	}
    }
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    inline int getMap(){
    	char t;
    	do{t=getchar();}while(t!='*'&&t!='.');
    	return t=='.';
    }
    int QAQ(){
    //	freopen("qaq.in","r",stdin);
    	n=read(),m=read();
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			map[i][j]=getMap();
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			if(map[i][j])
    				ex=i,ey=j;
    	f[0][0]=1;
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=m;++j)
    			solve(i,j);
    		if(i!=n){
    			HashMap &now=f[(i*m)&1];
    			for(int i=1;i<=now.tot;++i)
    				now.val[i]<<=2;
    		}
    	}
    	printf("%lld
    ",ans);
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    BigInt类

    Code

    class BigInt{
    #define LEN 6
    #define base 100000000
    	private:
    		int len,s[LEN];
    	public:
    		BigInt(){
    			len=0;
    			memset(s,0,sizeof(s));
    		}
    		BigInt(int x){
    			len=0;
    			memset(s,0,sizeof(s));
    			while(x){
    				s[++len]=x%base;
    				x/=base;
    			}
    		}
    		BigInt operator+(const BigInt &b)const{
    			BigInt res;
    			res.len=max(this->len,b.len);
    			for(int i=1;i<=res.len;++i)
    				res.s[i]+=this->s[i]+b.s[i],res.s[i+1]+=res.s[i]/base,res.s[i]%=base;
    			if(res.s[res.len+1])++res.len;
    			return res;
    		}
    		void operator+=(const BigInt &b){
    			*this=*this+b;
    		}
    		inline void print(){
    			printf("%d",s[len]);
    			for(int i=len-1;i>=0;--i)
    				printf("%08d",s[i]);
    		}
    };
    

    [SDOI2011]地板

    这一天,OIer们终于想起了,被插头DP支配的恐惧

    毒瘤插头DP题又写了170行。
    不过好像是因为我没有去压行。
    如果把转移封装起来的话应该能短不少。

    在这一题中我们设2中插头,转过向的和没转过向的,然后就可以大力分类讨论了。
    最后输出答案可以输出最后一次的状态0。

    Code

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    namespace orz{
    const int N=2000000;
    const int MOD=499211;
    const int P=20110520;
    struct HashMap{
    	int head[MOD],next[N],val[N],cnt[N],tot;
    	HashMap(){
    		memset(head,0,sizeof(head));
    		tot=0;
    	}
    	inline void clear(){
    		memset(head,0,sizeof(head));
    		tot=0;
    	}
    	inline int& operator[](const int &x){
    		int pos=x%MOD;
    		for(int i=head[pos];i;i=next[i])
    			if(val[i]==x)
    				return cnt[i];
    		next[++tot]=head[pos],head[pos]=tot,val[tot]=x,cnt[tot]=0;
    		return cnt[tot];
    	}
    }f[2];
    inline int find(int &s,int id){
    	return (s>>((id-1)<<1))&3;
    }
    inline void set(int &s,int id,int val){
    	id=(id-1)<<1;
    	s&=~(3<<id);
    	s|=(val<<id);
    }
    bool qwq[200][200];
    bool map[200][200];
    int n,m;
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    inline bool getMap(){
    	char t;
    	do{t=getchar();}while(t!='*'&&t!='_');
    	return t=='_';
    }
    inline void show(int s){
    	for(int i=1;i<=m+1;++i)
    		printf("%d",find(s,i));
    }
    inline void solve(int x,int y){
    	HashMap &now=f[((x-1)*m+y)&1];
    	HashMap &last=f[(((x-1)*m+y)&1)^1];
    	now.clear();
    	int tot=last.tot;
    	int t1,t2,s,cnt;
    	for(int i=1;i<=tot;++i){
    		s=last.val[i];
    		cnt=last.cnt[i];
    		t1=find(s,y);
    		t2=find(s,y+1);
    		if(!t1&&!t2){
    			if(!map[x][y]){
    				set(s,y,0);
    				set(s,y+1,0);
    				now[s]=(now[s]+cnt)%P;
    				continue;
    			}
    			if(map[x][y]&&map[x+1][y]&&map[x][y+1]){
    				set(s,y,2);
    				set(s,y+1,2);
    				now[s]=(now[s]+cnt)%P;
    			}
    			if(map[x][y]&&map[x+1][y]){
    				set(s,y,1);
    				set(s,y+1,0);
    				now[s]=(now[s]+cnt)%P;
    			}
    			if(map[x][y]&&map[x][y+1]){
    				set(s,y,0);
    				set(s,y+1,1);
    				now[s]=(now[s]+cnt)%P;
    			}
    		}
    		else if(!t1&&t2==1){
    			if(map[x][y+1]){
    				set(s,y,0);
    				set(s,y+1,2);
    				now[s]=(now[s]+cnt)%P;
    			}
    			if(map[x+1][y]){
    				set(s,y,1);
    				set(s,y+1,0);
    				now[s]=(now[s]+cnt)%P;
    			}
    		}
    		else if(!t1&&t2==2){
    			if(map[x+1][y]){
    				set(s,y,2);
    				set(s,y+1,0);
    				now[s]=(now[s]+cnt)%P;
    			}
    			set(s,y,0);
    			set(s,y+1,0);
    			now[s]=(now[s]+cnt)%P;
    		}
    		else if(t1==1&&!t2){
    			if(map[x][y+1]){
    				set(s,y,0);
    				set(s,y+1,1);
    				now[s]=(now[s]+cnt)%P;
    			}
    			if(map[x+1][y]){
    				set(s,y,2);
    				set(s,y+1,0);
    				now[s]=(now[s]+cnt)%P;
    			}
    		}
    		else if(t1==1&&t2==1){
    			set(s,y,0);
    			set(s,y+1,0);
    			now[s]=(now[s]+cnt)%P;
    		}
    		else if(t1==2&&!t2){
    			if(map[x][y+1]){
    				set(s,y,0);
    				set(s,y+1,2);
    				now[s]=(now[s]+cnt)%P;
    			}
    			set(s,y,0);
    			set(s,y+1,0);
    			now[s]=(now[s]+cnt)%P;
    		}
    	}
    }
    int QAQ(){
    	n=read(),m=read();
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			qwq[i][j]=getMap();
    	if(n<m){
    		for(int i=1;i<=n;++i)
    			for(int j=1;j<=m;++j)
    				map[j][i]=qwq[i][j];
    		swap(n,m);
    	}
    	else {
    		for(int i=1;i<=n;++i)
    			for(int j=1;j<=m;++j)
    				map[i][j]=qwq[i][j];
    	}
    	f[0][0]=1;
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=m;++j)
    			solve(i,j);
    		if(i!=n){
    			HashMap &last=f[(i*m)&1];
    			for(int i=1;i<=last.tot;++i)
    				last.val[i]<<=2;
    		}
    	}
    	HashMap &ans=f[(n*m)&1];
    	printf("%d
    ",ans[0]);
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    [COGS775]山海经

    听说是线段树恶心题,结果一遍过了。
    就是区间最长子段和,但是要求询问具体方案。而且不设SpecialJudge,要求输出字典序最小的解。
    区间最长子段和很容易维护,细节全在答案的维护。
    因为要求字典序最小,所以我们在答案相等是时候也尽量要取靠左的,所以很容易就A了。

    Code

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    namespace orz{
    #define lc x<<1
    #define rc x<<1|1
    const int N=1100000;
    int a[N];
    int sum[N];
    struct node{
    	int l,r;
    	int ans,lans,rans;
    	int lp,rp,llp,rrp;
    }t[(N<<2)+233];
    inline int calc(const int &l,const int &r){
    	return sum[r]-sum[l-1]; 
    }
    inline node merge(node a,node b){
    	node res;
    	//先维护无关紧要的信息
    	res.l=a.l;
    	res.r=b.r;
    	//=================
    	//从左往右更新值
    	//更新左答案,左答案位置
    	//左答案的右端点要尽量小
    	res.lans=a.lans;
    	res.llp=a.llp;
    	if(calc(a.l,a.r)+b.lans>res.lans){
    		res.llp=b.llp;
    		res.lans=calc(a.l,a.r)+b.lans;
    	}
    	//更新右答案,右答案位置
    	res.rans=b.rans;
    	res.rrp=b.rrp;
    	if(calc(b.l,b.r)+a.rans>=res.rans){
    		res.rrp=a.rrp;
    		res.rans=calc(b.l,b.r)+a.rans;
    	}
    	//更新答案,更新答案位置
    	if(a.ans>=b.ans){
    		res.ans=a.ans;
    		res.lp=a.lp;
    		res.rp=a.rp;
    	}
    	else{
    		res.ans=b.ans;
    		res.lp=b.lp;
    		res.rp=b.rp;
    	}
    	if(a.rans+b.lans>res.ans){
    		res.ans=a.rans+b.lans;
    		res.lp=a.rrp;
    		res.rp=b.llp;
    	}
    	else if(a.rans+b.lans==res.ans){
    		if(a.rrp<res.lp){
    			res.ans=a.rans+b.lans;
    			res.lp=a.rrp;
    			res.rp=b.llp;
    		}
    	}
    	return res;
    }
    void build(int x,int l,int r){
    	if(l==r){
    		t[x].ans=t[x].lans=t[x].rans=a[l];
    		t[x].l=t[x].r=t[x].lp=t[x].rp=t[x].llp=t[x].rrp=l;
    		return ;
    	}
    	int mid=(l+r)>>1;
    	build(lc,l,mid);
    	build(rc,mid+1,r);
    	t[x]=merge(t[lc],t[rc]);
    }
    inline node query(int x,int l,int r){
    	if(t[x].l==l&&t[x].r==r)return t[x];
    	int mid=(t[x].l+t[x].r)>>1;
    	if(r<=mid)return query(lc,l,r);
    	else if(l>mid)return query(rc,l,r);
    	else return	merge(query(lc,l,mid),query(rc,mid+1,r));
    }
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    int QAQ(){
    	int n,m;
    	int l,r;
    	node res;
    	n=read(),m=read();
    	for(int i=1;i<=n;++i)
    		a[i]=read();
    	for(int i=1;i<=n;++i)
    		sum[i]=sum[i-1]+a[i];
    	build(1,1,n);
    	while(m--){
    		l=read(),r=read();
    		res=query(1,l,r);
    		printf("%d %d %d
    ",res.lp,res.rp,res.ans);
    	}
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    [LOJ10222]佳佳的Fibonacci

    矩阵乘法模板题
    我们把nfn的式子拆开就可以求出矩阵
    乘就完事了

    t(n) n+1fn+1 nfn fn+1 fn
    t(n-1) 1
    nfn 1 1 1
    (n-1)fn-1 1
    fn 1 1 1
    fn-1 2 1

    Code

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    namespace orz{
    const int N=6;
    int MOD;
    struct Matrix{
        int a[N][N];
        Matrix(){
            memset(a,0,sizeof(a));
        }
        Matrix operator*(const Matrix &b)const{
            Matrix res;
            for(int i=1;i<=5;++i)
                for(int j=1;j<=5;++j)
                    for(int k=1;k<=5;++k)
                        res.a[i][j]=(res.a[i][j]+1ll*a[i][k]*b.a[k][j]%MOD)%MOD;
            return res;
        }
    }ans,base;
    inline int read(){
        int a=1,b=0;char t;
        do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
        do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
        return a*b;
    }
    int QAQ(){
    	int n=read();
    	MOD=read();
    	Matrix ans,base;
    	base.a[1][1]=1;
    	base.a[2][1]=1;
    	base.a[2][2]=1;
    	base.a[3][2]=1;
    	base.a[4][2]=1;
    	base.a[5][2]=2;
    	base.a[2][3]=1;
    	base.a[4][4]=1;
    	base.a[5][4]=1;
    	base.a[4][5]=1;
    	ans.a[1][1]=1;
    	ans.a[1][2]=2;
    	ans.a[1][3]=1;
    	ans.a[1][4]=1;
    	ans.a[1][5]=1;
    	int y=n-1;
    	while(y){
    		if(y&1)ans=ans*base;
    		y>>=1;
    		base=base*base;
    	}
    	printf("%d
    ",ans.a[1][1]);
        return false;
    }
    }
    int main(){
        return orz::QAQ();
    }
    

    7.29爆零赛

    爆零了QAQ
    上来一看,T1扫描线,T2启发式合并平衡树,T3期望不会。
    自闭了,打了暴力分。

    这三道题目名字也挺不正经的说。

    辣鸡

    这题其实你如果要扫描线拧干的话估计也能干出来,可是我并不会扫描线。
    这题(n^2)就完事了,循环的时候加一个小优化,先把它排了序,如果以后再也不能对答案贡献了就跳出。
    然后就可以见证(n^2)(100000)了。

    Code

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    namespace orz{
    const int N=200000;
    struct point{
    	long long x1,y1,x2,y2;
    	bool operator<(const point &b)const{
    		return this->x1^b.x1?this->x1<b.x1:this->y1<b.y1;
    	}
    }a[N];
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    inline long long calc(long long l1,long long r1,long long l2,long long r2){
    	long long res=0,tl=max(l1-1,l2),tr=min(r1-1,r2);
    	res+=max(0ll,tr-tl+1);
    	tl=max(l1+1,l2),tr=min(r1+1,r2);
    	res+=max(0ll,tr-tl+1);
    	return res;
    }
    int QAQ(){
    	int n=read();
    	long long ans=0;
    	for(register int i=1;i<=n;++i)
    		a[i].x1=read()+1,a[i].y1=read()+1,a[i].x2=read()+1,a[i].y2=read()+1;
    	for(register int i=1;i<=n;++i)
    		ans+=(a[i].x2-a[i].x1)*(a[i].y2-a[i].y1)*2ll;
    	sort(a+1,a+n+1);
    	for(register int i=1;i<=n;++i){
    		for(register int j=i+1;j<=n;++j){
    			if(a[j].x1>a[i].x2+1)break;
    			if(a[j].x1==a[i].x2+1){ans+=calc(a[i].y1,a[i].y2,a[j].y1,a[j].y2);}
    			else if(a[j].y1==a[i].y2+1){ans+=calc(a[i].x1,a[i].x2,a[j].x1,a[j].x2);}
    			else if(a[j].x2==a[i].x1-1){ans+=calc(a[i].y1,a[i].y2,a[j].y1,a[j].y2);}
    			else if(a[j].y2==a[i].y1-1){ans+=calc(a[i].x1,a[i].x2,a[j].x1,a[j].x2);}
    		}
    	}
    	printf("%lld
    ",ans);
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    模板

    题解是启发式合并动态开点线段树,因为我比较懒,所以还是去写启发式合并Splay了。
    平衡树启发式合并跑的还挺快的。
    过了样例之后出了两个问题,一个是没判断k=0的情况T了,第二个是边数组没开2倍T了。
    Orz。

    Code

    #include<cstdio>
    #include<algorithm>
    #include<set>
    using namespace std;
    namespace orz{
    #define IT set<WSL>::iterator
    const int N=110000;
    struct SplayNode{
    	int son[2],father,size,cnt,val,time,color;
    }t[N*18];
    int tot=0;
    int res;
    int head[N],ver[N*2],next[N*2],cwy;
    inline void add(int x,int y){
    	next[++cwy]=head[x],head[x]=cwy,ver[cwy]=y;
    	next[++cwy]=head[y],head[y]=cwy,ver[cwy]=x;
    }
    struct WSL{
    	int color;
    	mutable int time;
    	WSL(int c,int t){
    		color=c;
    		time=t;
    	}
    	bool operator<(const WSL&b)const{
    		return this->color<b.color;
    	}
    };
    struct Splay{
    	int root;
    	inline void update(int x){
    		t[x].size=t[t[x].son[0]].size+t[t[x].son[1]].size+1;
    		t[x].cnt=t[t[x].son[0]].cnt+t[t[x].son[1]].cnt+t[x].val;
    	}
    	inline bool get(int x){
    		return t[t[x].father].son[1]==x;
    	}
    	inline void rotate(int x){
    		int y=t[x].father,z=t[y].father,k=get(x);
    		t[z].son[get(y)]=x;
    		t[x].father=z;
    		t[y].son[k]=t[x].son[k^1];
    		t[t[y].son[k]].father=y;
    		t[x].son[k^1]=y;
    		t[y].father=x;
    		update(y);
    	}
    	inline void splay(int x){
    		int y,z;
    		while(t[x].father){
    			y=t[x].father;z=t[y].father;
    			if(z)
    				rotate(get(x)^get(y)?x:y);
    			rotate(x);
    		}
    		update(x);
    		root=x;
    	}
    	inline int findAns(int k){
    		int x=root;
    		int ans=0;
    		if(k>=t[x].size){
    			return t[x].cnt;
    		}
    		if(k==0){
    			return 0;
    		}
    		while(true){
    			if(k<=t[t[x].son[0]].size)x=t[x].son[0];
    			else if(k<=t[t[x].son[0]].size+1)return ans+t[t[x].son[0]].cnt+t[x].val;
    			else ans+=t[t[x].son[0]].cnt+t[x].val,k-=t[t[x].son[0]].size+1,x=t[x].son[1];
    		}
    	}
    	inline void insert(int time,int color,int val){
    		int father=0,x=root;
    		while(x&&t[x].time!=time){
    			father=x;
    			x=t[x].son[time>t[x].time];
    		}
    		x=++tot;
    		if(father)t[father].son[time>t[father].time]=x;
    		t[x].time=time;t[x].size=1;t[x].cnt=t[x].val=val;
    		t[x].father=father;t[x].color=color;
    		splay(x);
    	}
    	inline void change(int time){
    		int x=root;
    		while(x&&time!=t[x].time){
    			x=t[x].son[time>t[x].time];
    		}
    		t[x].val=0;
    		splay(x);
    	}
    	inline int size(){
    		return t[root].size;
    	}
    };
    struct QWQ{
    	set<WSL>dky;
    	Splay s;
    	inline int size(){
    		return s.size();
    	}
    	inline void insert(int time,int color){
    		IT it=dky.find(WSL(color,0));
    		if(it==dky.end()){
    			dky.insert(WSL(color,time));
    			s.insert(time,color,1);
    		}
    		else if(time>it->time){
    			s.insert(time,color,0);
    		}
    		else{
    			s.change(it->time);
    			it->time=time;
    			s.insert(time,color,1);
    		}
    	}
    }a[N];
    int p[N],tong[N],ans[N];
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    inline void merge(int x){
    	if(!x)return;
    	merge(t[x].son[0]);
    	a[res].insert(t[x].time,t[x].color);
    	merge(t[x].son[1]);
    }
    inline void dfs(int x,int father){
    	for(int i=head[x];i;i=next[i]){
    		if(ver[i]==father)continue;
    		dfs(ver[i],x);
    		if(a[p[x]].size()<a[p[ver[i]]].size()){
    			res=p[ver[i]];
    			merge(a[p[x]].s.root);
    			p[x]=p[ver[i]];
    		}
    		else{
    			res=p[x];
    			merge(a[p[ver[i]]].s.root);
    		}
    	}
    	ans[x]=a[p[x]].s.findAns(tong[x]);
    }
    int QAQ(){
    	int n,m,q,x,c;
    	n=read();
    	for(int i=1;i<n;++i)
    		add(read(),read());
    	for(int i=1;i<=n;++i)
    		tong[i]=read(),p[i]=i;
    	m=read();
    	for(int i=1;i<=m;++i)
    		x=read(),c=read(),a[p[x]].insert(i,c);	
    	dfs(1,0);
    	q=read();
    	for(int i=1;i<=q;++i)
    		printf("%d
    ",ans[read()]);
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    大佬

    题解有看不懂的DP做法,实际上有很优秀的快速幂做法,时间复杂度是(mlog k)的,比题解的(n^2m)不知道高到哪里去了。
    开始的时候我设的dp状态一维是天数,因为前面对后面有影响导致不可转移。实际上我们的所有情况包括了m取值的所有情况,而所有长度为k的区间的所有情况也都存在,所以我们只需要考虑长度为k的区间,乘上(n-k+1)就可以了。
    而对于长度为k的区间,最大值为i的方案数为(i^k-(i-1)^k)

    Code

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    namespace orz{
    const long long MOD=1000000007;
    inline long long read(){
    	long long a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    inline long long pow(long long x,long long y){
    	long long ans=1;
    	while(y){
    		if(y&1)ans=ans*x%MOD;
    		y>>=1;
    		x=x*x%MOD;
    	}
    	return ans%MOD;
    }
    int QAQ(){
    	long long n=read(),m=read(),k=read();
    	long long ans=0;
    	if(n<k){printf("0
    ");return false;}
    	for(int i=1;i<=m;++i)
    		ans=(ans+read()*((pow(i,k)-pow(i-1,k))%MOD+MOD)%MOD*(n-k+1)%MOD)%MOD;
    	printf("%lld
    ",ans*pow(pow(m,k),MOD-2)%MOD);
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    [SCOI2014]方伯伯的玉米田

    这是个DP
    看起来好像是一个LIS,我们考虑如何处理区间同时加一。
    因为我们求的是最长不下降子序列,所以一次区间加一定是从一个点开始加到最后,否则会导致后面的变小。
    所以我们设(f_{i,j})表示考虑了前i个玉米,进行了j次拔高的最大长度,之后就是一个LIS了。
    注意第二层循环要反着来,否则会自己加自己。

    Code

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    namespace orz{
    const int N=20000;
    const int mx=5550;
    const int mxk=550;
    int a[N];
    int c[5600][600];
    int n,k;
    inline int ask(int x,int y){
    	int ans=0;
    	for(int i=x;i;i-=i&-i)
    		for(int j=y;j;j-=j&-j)
    			ans=max(ans,c[i][j]);
    	return ans;
    }
    inline void add(int x,int y,int val){
    	for(int i=x;i<=mx;i+=i&-i)
    		for(int j=y;j<=mxk;j+=j&-j)
    			c[i][j]=max(c[i][j],val);
    }
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    int QAQ(){
    	n=read(),k=read();
    	for(int i=1;i<=n;++i)
    		a[i]=read();
    	int ans=0;
    	int fans=0;
    	for(int i=1;i<=n;++i)
    		for(int j=k+1;j;--j){
    			ans=ask(a[i]+j,j)+1;
    			add(a[i]+j,j,ans);
    			fans=max(fans,ans);
    		}
    	printf("%d
    ",fans);
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    [SDOI2011]拦截导弹

    第一问很简单,裸的CDQ优化DP。
    第二问就很难计算了。
    它大概是一个假的概率,等于经过某个导弹的方案数除以总方案数。
    我们考虑如何判断一个点是否在x到y的最短路上,我们可以从x跑一遍最短路,从y跑一遍最短路,如果一个点两个dis的值加起来等于x到y的最短路长度加一,那么这个点就是好的。这个显然。
    我们用相同的思路处理这一道题。
    我们先正着跑一遍最长不上升子序列,dp数组为(f_i),方案数为(fc_i),再倒着跑一遍最长不下降子序列,dp数组为(g_i),方案数为(gc_i)如果(f_i+g_i=ans)那么经过它的方案数为(fc_i*gc_i)

    之后我们发现我们还是不会求,CDQ怎么统计方案数?
    我们发现我们的树状数组只能求出最优方案,而不能求出方案数。
    这时候就需要我们去魔改一下我们的树状数组了。
    我们再记一个cnt数组,我们在更新最大值的时候把它更新,如果最大值和插入值相等就累加。
    然后我们发现跑出来的全是0,因为我们的计数没有初值,把所有数初值赋为0吗?那你必WA,不用想也知道那样一定会出错。
    我们发现只有一个数作为第一个数出现时才会从0转移,所以当右区间的一个数从0转移时我们将它跳过,最后跑到它本身如果还是0那说明它是从0转移的,我们为它赋上初值。
    这题方案数还挺多的,需要开double,比如10 10 9 9 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1就有(2^{10})种总方案。

    Code

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    namespace orz{
    const int N=200000;
    struct node{
    	int t,h,v;
    	bool operator<(const node&b)const{
    		return this->h<b.h;
    	}
    }a[N],t[N];
    int hc[N],vc[N],htot,vtot,c[N];
    double cnt[N];
    int n,f[N],g[N];
    double fc[N],gc[N];
    inline void add(int x,int ans,double tot){
    	for(;x<=n+2;x+=x&-x){
    		if(c[x]<ans)c[x]=ans,cnt[x]=tot;
    		else if(ans==c[x])cnt[x]+=tot;
    	}
    }
    inline int ask(int x,double &res){
    	res=0;
    	int ans=0;
    	for(;x;x-=x&-x){
    		if(ans<c[x])ans=c[x],res=cnt[x];
    		else if(ans==c[x])res+=cnt[x];
    	}
    	return ans;
    }
    inline void remove(int x){
    	for(;x<=n;x+=x&-x)
    		c[x]=cnt[x]=0;
    }
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    void cdq(int l,int r){
    	if(l==r){
    		if(!f[a[l].t]){
    			f[a[l].t]=1;
    			fc[a[l].t]=1;
    		}
    		return;
    	}
    	int mid=(l+r)>>1;
    	cdq(l,mid);
    	int tl=l,tr=mid+1,cwy;
    	double res;
    	for(int i=l;i<=r;++i)t[i]=a[i];
    	sort(t+l,t+mid+1);
    	sort(t+mid+1,t+r+1);
    	for(int i=l;i<=r;++i){
    		if((tl<=mid&&t[tl].h<=t[tr].h)||tr>r){
    			add(t[tl].v,f[t[tl].t],fc[t[tl].t]);
    			++tl;
    		}
    		else{
    			cwy=ask(t[tr].v,res)+1;
    			if(cwy==1){++tr;continue;}
    			if(f[t[tr].t]<cwy)f[t[tr].t]=cwy,fc[t[tr].t]=res;
    			else if(f[t[tr].t]==cwy)fc[t[tr].t]+=res;
    			++tr;
    		}
    	}
    	for(int i=l;i<=mid;++i)
    		remove(t[i].v);
    	cdq(mid+1,r);
    }
    int QAQ(){
    	n=read();
    	for(int i=1;i<=n;++i)
    		a[i].h=hc[i]=read(),a[i].v=vc[i]=read(),a[i].t=i;
    	sort(hc+1,hc+n+1);
    	sort(vc+1,vc+n+1);
    	htot=unique(hc+1,hc+n+1)-hc-1;
    	vtot=unique(vc+1,vc+n+1)-vc-1;
    	for(int i=1;i<=n;++i){
    		a[i].h=lower_bound(hc+1,hc+htot+1,a[i].h)-hc;
    		a[i].v=lower_bound(vc+1,vc+vtot+1,a[i].v)-vc;
    	}
    	for(int i=1;i<=n;++i)a[i].h=n+1-a[i].h;
    	for(int i=1;i<=n;++i)a[i].v=n+1-a[i].v;
    	cdq(1,n);
    	for(int i=1;i<=n;++i)a[i].h=n+1-a[i].h;
    	for(int i=1;i<=n;++i)a[i].v=n+1-a[i].v;
    	for(int i=1;i<=n;++i)g[i]=f[i],gc[i]=fc[i];
    	for(int i=1;i<=n;++i)f[i]=fc[i]=0;
    	for(int i=1,j=n;i<j;++i,--j)swap(a[i],a[j]);
    	cdq(1,n);
    	int ans=0;
    	double tot=0;
    	for(int i=1;i<=n;++i)
    		ans=max(ans,g[i]);
    	for(int i=1;i<=n;++i)
    		if(g[i]==ans)
    			tot+=gc[i];
    	printf("%d
    ",ans);
    	for(int i=1;i<=n;++i){
    		if(f[i]+g[i]==ans+1)printf("%lf ",(fc[i]*gc[i])/tot);
    		else printf("0.0000000 ");
    	}
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    后缀数组(SA)学习笔记

    由于HZ学长讲的过于清晰,所以蒟蒻并没有听懂。
    以下内容是我从网上各位大神的博客学习后总结的,感谢他们的无私付出。
    后半部分基本来自2009年罗穗骞的论文,感谢他写出了如此清晰的国家集训队论文,这是少数几个蒟蒻也能看懂的国集论文。

    没听懂QAQ
    后缀数组是处理字符串的一个好东西。
    它是一个数组。
    我们先把所有的后缀排个序,然后我们就可以得到后缀数组。
    以下所有第i个指的是原字符串i到len的子串。
    排名第i的所有后缀排过序后的第i个后缀。
    这里的排序指的是按照字典序排序。
    a<ab
    一些数组定义(以下都是数组):
    rank表示第i个后缀的排名
    sa表示排名第i的后缀是第几个后缀
    还有一个常用的height数组之后再定义吧。

    我们可以发现rank数组和sa数组是互逆的,求一个就可以了。
    但是怎样优秀的求就是一个问题。
    重载运算符?
    复杂度为O(n^2logn)

    有一些优秀的算法可以解决这个问题。
    其中倍增法最为常用。
    时间复杂度为(O(nlog n))

    倍增算法是基于后缀的一些性质。
    第i+1的后缀的第一个字符是第i个后缀的第二个字符。
    所以我们在算出第一个字符顺序的同时也得到了第二个字符的排序,我们算出1,2个字符的排序后也得到了第3,4个字符的排序。
    所以我们就可以倍增的用双关键字的排序来做了。
    直接用是sort是(O(nlog^2n))的。
    用基数排序会更优秀。

    写了很多注释的代码

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    namespace orz{
    const int N=1100000;
    char s[N];
    int n,m;
    int x[N],y[N],c[N],sa[N];
    inline void SA(){
    	//c是一个桶,现在统计了对应元素的个数
    	for(int i=1;i<=n;++i)++c[x[i]=s[i]];
    	//求了一个前缀和
    	for(int i=1;i<=m;++i)c[i]+=c[i-1];
    	//求出只用第一个字母的SA值
    	//如果所有字符都不相同的话,对应的排名就是前缀和
    	//为了保证sa互不相同要每次减一
    	//这里正着倒着都对
    	//其实好像乱着也对
    	for(int i=n;i>=1;--i)sa[c[x[i]]--]=i;
    	//开始倍增
    	//k的意义为目前已经处理出了关键字为前的SA,扩展到2×k的SA
    	for(int k=1;k<=n;k<<=1){
    		//x表示的是第i个后缀当前的rank,相同rank会重复。
    		//p只是一个计数器
    		int p=0;
    		//y表示以第二关键字排序的SA值
    		//先把长度不够的给排到前面
    		//这里正着倒着都对,因为之前已经排好了
    		for(int i=n-k+1;i<=n;++i)y[++p]=i;
    		//倍增法求SA的核心操作
    		//第二关键字可以从第一关键字得到
    		for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
    		//清空桶
    		for(int i=1;i<=m;++i)c[i]=0;
    		//用第一关键字信息更新桶
    		for(int i=1;i<=n;++i)++c[x[i]];
    		//做前缀和
    		for(int i=1;i<=m;++i)c[i]+=c[i-1];
    		//与上面的排序差不多,只是按y的顺序来
    		for(int i=n;i>=1;--i)sa[c[x[y[i]]]--]=y[i];
    		//更新出下一次的x
    		//现在的SA数组已经是按2k排序的SA数组了
    		//先把原来的y废了
    		//现在的y是上一次的x
    		swap(x,y);
    		//计数器清空
    		p=1;
    		//设置第一位
    		x[sa[1]]=1;
    		//如果相同的话拥有相同rank
    		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]?p:++p;
    		//如果p=n说明所有后缀都排好了
    		if(p==n)break;
    		m=p;
    	}
    }
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    int QAQ(){
    	scanf("%s",s+1);
    	n=strlen(s+1),m='z';
    	SA();
    	for(int i=1;i<=n;++i)
    		printf("%d ",sa[i]);
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    没有注释的板子

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    namespace orz{
    const int N=1100000;
    char s[N];
    int x[N],y[N],c[N],sa[N];
    int n,m;
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    inline void SA(){
    	for(int i=1;i<=n;++i)++c[x[i]=s[i]];
    	for(int i=1;i<=m;++i)c[i]+=c[i-1];
    	for(int i=n;i;--i)sa[c[x[i]]--]=i;
    	for(int k=1;k<=n;k<<=1){
    		int p=0;
    		for(int i=n-k+1;i<=n;++i)y[++p]=i;
    		for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
    		for(int i=1;i<=m;++i)c[i]=0;
    		for(int i=1;i<=n;++i)++c[x[i]];
    		for(int i=1;i<=m;++i)c[i]+=c[i-1];
    		for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
    		swap(x,y);
    		p=1;
    		x[sa[1]]=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]?p:++p;
    		if(p==n)break;
    		m=p;
    	}
    }
    int QAQ(){
    	scanf(" %s",s+1);
    	n=strlen(s+1);
    	m='z';
    	SA();
    	for(int i=1;i<=n;++i)
    		printf("%d ",sa[i]);
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    我们终于出了SA数组,可是仍然不知道该怎么用。
    我们一般还需要height数组。
    (height_i)表示排名为i的后缀与排名为i-1的后缀的LCP
    如果直接求就(O(n^2))了。
    那就很不好
    (h_i=height_{rank_i})
    即h数组表示第i个后缀的height值
    我们有一个性质:(h_i>=h_{i-1}-1)

    证明

    我们设k为第i-1个后缀排名前一个后缀
    (lcp(k,i-1)=h_{i-1})
    把这两个后缀都去掉首字符
    变成了k+1和i
    而它们的lcp少了1
    这个只是方便理解的证明,实际上好像不是佷严谨。

    带求height的板子

    inline void SA(){
    	for(int i=1;i<=n;++i)++c[x[i]=a[i]];
    	for(int i=1;i<=m;++i)c[i]+=c[i-1];
    	for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
    	for(int k=1;k<=n;k<<=1){
    		int p=0;
    		for(int i=n-k+1;i<=n;++i)y[++p]=i;
    		for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
    		for(int i=1;i<=m;++i)c[i]=0;
    		for(int i=1;i<=n;++i)++c[x[i]];
    		for(int i=1;i<=m;++i)c[i]+=c[i-1];
    		for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
    		swap(x,y);
    		p=1;
    		x[sa[1]]=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]?p:++p;
    		if(p==n)break;
    		m=p;
    	}
    	for(int i=1;i<=n;++i)
    		rank[sa[i]]=i;
    	for(int i=1,p=0;i<=n;++i){
    		if(rank[i]==1)continue;
    		if(p)--p;
    		int j=sa[rank[i]-1];
    		while(a[i+p]==a[j+p])++p;
    		height[rank[i]]=p;
    	}
    }
    

    SA一些常用套路的总结

    最长可重叠重复子串

    重复子串的定义:在字符串中至少出现两次的子串
    输出height的最大值就可以了

    最长不可重叠重复子串

    最长可重叠k重复子串

    我们依旧是把问题转化为判定,使用二分来解决.
    二分长度。如果存在一段连续的height都大于等于这个长度且这些数的个数大于等于k-1,那么这个长度就是可行的。

    USACO06DEC牛奶模式Milk Patterns

    Code

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    namespace orz{
    const int N=40000;
    int x[N],y[N],c[1100000],sa[N],rank[N],height[N];
    int a[N];
    int n,k,m;
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    inline void SA(){
    	for(int i=1;i<=n;++i)++c[x[i]=a[i]];
    	for(int i=1;i<=m;++i)c[i]+=c[i-1];
    	for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
    	for(int k=1;k<=n;k<<=1){
    		int p=0;
    		for(int i=n-k+1;i<=n;++i)y[++p]=i;
    		for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
    		for(int i=1;i<=m;++i)c[i]=0;
    		for(int i=1;i<=n;++i)++c[x[i]];
    		for(int i=1;i<=m;++i)c[i]+=c[i-1];
    		for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
    		swap(x,y);
    		p=1;
    		x[sa[1]]=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]?p:++p;
    		if(p==n)break;
    		m=p;
    	}
    	for(int i=1;i<=n;++i)
    		rank[sa[i]]=i;
    	for(int i=1,p=0;i<=n;++i){
    		if(rank[i]==1)continue;
    		if(p)--p;
    		int j=sa[rank[i]-1];
    		while(a[i+p]==a[j+p])++p;
    		height[rank[i]]=p;
    	}
    }
    inline bool check(int x){
    	int res=0;
    	for(int i=2;i<=n;++i){
    		if(height[i]>=x)++res;
    		else res=0;
    		if(res>=k-1)return true;
    	}	
    	return false;
    }
    int QAQ(){
    	n=read(),k=read();
    	for(int i=1;i<=n;++i)
    		m=max(m,a[i]=read());
    	SA();
    	int l=0,r=n,mid;
    	while(l<r){
    		mid=(l+r+1)>>1;
    		if(check(mid))l=mid;
    		else r=mid-1;
    	}
    	printf("%d
    ",l);
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    不同子串个数

    一个很显然的转化是将子串变成后缀的前缀。
    所以问题也就变成了求不同的后缀的前缀的个数
    我们按照排完的顺序依次插入后缀,每插入一个后缀,都会多(n-sa_i-height_i+1)个本质不同的子串。把它们加起来就可以得到个数,注意第一个后缀是(n-sa_1+1)

    LuoguP2408不同子串个数

    Code

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    namespace orz{
    const int N=200000;
    int x[N],y[N],c[N],sa[N],height[N],rank[N];
    char s[N];
    int n,m;
    inline int read(){
    	int a=1,b=0;char t;
    	do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    	do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    	return a*b;
    }
    inline void SA(){
    	for(int i=1;i<=n;++i)++c[x[i]=s[i]];
    	for(int i=1;i<=m;++i)c[i]+=c[i-1];
    	for(int i=n;i;--i)sa[c[x[i]]--]=i;
    	for(int k=1;k<=n;k<<=1){
    		int p=0;
    		for(int i=n-k+1;i<=n;++i)y[++p]=i;
    		for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
    		for(int i=1;i<=m;++i)c[i]=0;
    		for(int i=1;i<=n;++i)++c[x[i]];
    		for(int i=1;i<=m;++i)c[i]+=c[i-1];
    		for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
    		swap(x,y);
    		p=1;
    		x[sa[1]]=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]?p:++p;
    		if(p==n)break;
    		m=p;
    	}
    	for(int i=1;i<=n;++i)
    		rank[sa[i]]=i;
    	for(int i=1,p=0;i<=n;++i){
    		if(rank[i]==1)continue;
    		if(p)--p;
    		int j=sa[rank[i]-1];
    		while(s[i+p]==s[j+p])++p;
    		height[rank[i]]=p;
    	}
    }
    int QAQ(){
    	n=read();
    	scanf("%s",s+1);
    	m='z';
    	SA();
    //	for(int i=1;i<=n;++i)
    //		printf("%d %d
    ",sa[i],height[i]);
    //	putchar('
    ');
    	long long ans=0;
    	ans+=n-sa[1]+1;
    	for(int i=2;i<=n;++i)
    		ans+=(long long)n-sa[i]+1-height[i];
    	printf("%lld
    ",ans);
    	return false;
    }
    }
    int main(){
    	return orz::QAQ();
    }
    

    对于这个思路,我们还可以发现这样插入的子串是按照字典序从小到大的,所以我们还可以二分取出第k大的本质不同的字串。

    Code

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    namespace orz{
    const int N=100;
    int x[N],y[N],c[N],sa[N],rank[N],height[N];
    long long sum[N];
    char s[N];
    int f[N][20];
    int Log[N];
    int n,m;
    inline long long read(){
        long long a=1,b=0;char t;
        do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
        do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
        return a*b;
    }
    inline void SA(){
    	for(int i=1;i<=n;++i)++c[x[i]=s[i]];
    	for(int i=1;i<=m;++i)c[i]+=c[i-1];
    	for(int i=n;i;--i)sa[c[x[i]]--]=i;
    	for(int k=1;k<=n;k<<=1){
    		int p=0;
    		for(int i=n-k+1;i<=n;++i)y[++p]=i;
    		for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
    		for(int i=1;i<=m;++i)c[i]=0;
    		for(int i=1;i<=n;++i)++c[x[i]];
    		for(int i=1;i<=m;++i)c[i]+=c[i-1];
    		for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
    		swap(x,y);
    		p=1;
    		x[sa[1]]=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]?p:++p;
    		if(p==n)break;
    		m=p;
    	}
    	for(int i=1;i<=n;++i)
    		sa[rank[i]]=i;
    	for(int i=1,p=0;i<=n;++i){
    		if(rank[i]==1)continue;
    		if(p)--p;
    		int j=sa[rank[i]-1];
    		while(s[i+p]==s[j+p])++p;
    		height[rank[i]]=i;
    	}
    }
    inline void preLog(){
    	for(int i=0;(1<<i)<=n;++i)
    		Log[1<<i]=i;
    	for(int i=0,res=0;i<=n;++i){
    		if(Log[i])res=Log[i];
    		else Log[i]=res;
    	}
    }
    inline void findString(long long x,int &tl,int &tr){
    	int l=1,r=n,mid;
    	while(l<r){
    		mid=(l+r+1)>>1;
    		if(sum[mid]>=x)r=mid-1;
    		else l=mid;
    	}
    	tl=sa[l];
    	tr=sa[l]+x-sum[l]+height[l];
    }
    int QAQ(){
    	long long tot=0;
    	int q;
    	long long x,y;
    	int xl,xr,yl,yr;
    	n=read(),q=read();
    	m='z';
    	sum[1]=n-sa[1]+1;
    	for(int i=2;i<=n;++i)
    		sum[i]=n-sa[i]-height[i]+1;
    	for(int i=1;i<=n;++i)
    		sum[i]+=sum[i-1];
    	while(m--){
    		x=read(),y=read();
    		findString(x,xl,xr);
    		findString(y,yl,yr);
    		printf("%d %d %d %d
    ",xl,xr,yl,yr);
    	}
        return false;
    }
    }
    int main(){
        return orz::QAQ();
    }
    

    没写完2

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    namespace orz{
    const int N=100;
    int n,m;
    int c[N],x[N],y[N];
    int Log[N];
    inline void log2(int x){
    
    }
    inline void logPre(){
    	
    }
    struct String{
    	int f[N][20];
    	inline void getMin(){
    	
    	}
    	inline void SA(){
    	
    	}
    }
    inline int read(){
        int a=1,b=0;char t;
        do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
        do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
        return a*b;
    }
    int QAQ(){
        return false;
    }
    }
    int main(){
        return orz::QAQ();
    }
    
  • 相关阅读:
    spring AOP操作
    spring AOP理解和相关术语
    spring 注解管理
    spring 注入属性
    spring bean标签常用属性
    spring属性注入方式
    spring bean实例化的三种方式
    struts2常用标签
    Codeforces 484(#276 Div 1) B Maximum Value 筛法
    Codeforces 484(#276 Div 1) A Bits 乱搞
  • 原文地址:https://www.cnblogs.com/oiertkj/p/12203542.html
Copyright © 2020-2023  润新知