NOIP 模拟二 考试总结

SDOI2015 排序

今天看到这道题,没有一点思路,暴力都没的打。。。还是理解错题意了,操作不同位置不是说改不同的区间,而是不同操作的顺序。。。考场上如果知道这个的话最少暴力拿一半啊,因为正解本来就是暴力。。

DFS

题目问的是方案数,如果操作确定了是谁的话顺序是无所谓的,压一个A(n n)加上就好。这一点很显然,不管怎么移动,如果先一后二可以,那么先二后一也是可以的。找规律就好。

现在需要做的是找到不同的操作序列,这里的不同不是指顺序,而是指种类。因为上面说了顺序无影响,那我们可以从最小的开始,能换就换,说白了就是暴搜%%%XIN队。

暴搜的话肯定要砍大树的,上面的思路如果大力搞的话必暴,其实在枚举序列找不满足递增顺序的区间时,如果区间数>2了,直接return。因为每种操作只有一次,一次肯定换不了两个以上的区间。如果没有不满足递增的区间,说明不需要这个操作,直接整一个DFS(x+1,num){x代表当前是第几个操作,num表示用了几个操作。}如果只有一个,那就自己换一下就OK。如果有两个,那么自己跟自己块里的两段肯定不能换,因为还有另一个块不合法,自己换了那人家怎么办。所以是2×2,枚举去换,换了后检查合法性如果合法就DFS(x+1,num+1)否则不用管。还有记得回溯的时候把换了的再给人换回去
。这样这道题就AC了。

不过苣蒻还是不懂玄学复杂度,明明是2^24,虽然远远达不到这个水平,但也不至于40ms就跑完了吧,一个点才2ms,这不是a+b的时间吗哈哈。

code有些冗长了,四种情况其实可以写函数的,我直接摆在那了,反正也是打一个然后复制粘贴,不过我好像改变量时少改了一个,调了好长时间。。算了,懒得改代码了,就这样吧。

#include<bits/stdc++.h>
using namespace std;
int n,a[1000101],ans=0;
inline void fr(){freopen("c.in","r",stdin);}
inline void sc(){scanf("%d",&n);}
namespace AYX
{	inline void dfs(int x,int num)
	{	if(x==n+1)
		{	int l=1;
			for(int i=num;i;--i)
			l*=i;
			ans+=l;
			return;
		}
		int len=(1<<(x)),kk=(1<<(x-1)),m1,m2,cnt=0;
		for(int i=1;i<=(1<<n)-len+1;i+=len)
		{	for(int j=i;j<(1<<x)/2+i;++j)
			{	if(a[j]+kk!=a[j+kk])
				{	++cnt;
					if(cnt==1)
					m1=i;
					else
					m2=i;
					break;
				}
				if(cnt>2)return;
			}
		}
		if(cnt>2)return;
		if(cnt==0)
		{	dfs(x+1,num);
			return ;
		}
		if(cnt==1)
		{	
			for(int i=m1;i<=m1+kk-1;++i)
				swap(a[i],a[i+kk]);
			dfs(x+1,num+1);
			for(int i=m1;i<=m1+kk-1;++i)
				swap(a[i],a[i+kk]);
			return;
		}
		bool bo=0;
		int mm1=m1+kk,mm2=m2+kk;
		for(int i=1;i<=kk;++i)
			swap(a[i+m1-1],a[m2+i-1]);
		for(int i=1;i<=kk;++i)
		{	if(a[i+m1-1]+kk!=a[i+m1+kk-1] or a[i+m2-1]+kk!=a[i+m2+kk-1])
			{
				bo=1;break;
			}
		}
		if(!bo)dfs(x+1,num+1);
		for(int i=1;i<=kk;++i)
			swap(a[i+m1-1],a[m2+i-1]);
		bo=0;
		for(int i=1;i<=kk;++i)
			swap(a[i+mm1-1],a[m2+i-1]);
		for(int i=1;i<=kk;++i)
		{	if(a[i+m1-1]+kk!=a[i+m1+kk-1] or a[i+m2-1]+kk!=a[i+m2+kk-1])
			{
				bo=1;break;
			}
		}
		if(!bo)dfs(x+1,num+1);
		for(int i=1;i<=kk;++i)
			swap(a[i+mm1-1],a[m2+i-1]);
		bo=0;
		for(int i=1;i<=kk;++i)
			swap(a[i+m1-1],a[mm2+i-1]);
		for(int i=1;i<=kk;++i)
		{	if(a[i+m1-1]+kk!=a[i+m1+kk-1] or a[i+m2-1]+kk!=a[i+m2+kk-1])
			{
				bo=1;break;
			}
		}
		if(!bo)dfs(x+1,num+1);
		for(int i=1;i<=kk;++i)
			swap(a[i+m1-1],a[mm2+i-1]);
		bo=0;
		for(int i=1;i<=kk;++i)
			swap(a[i+mm1-1],a[mm2+i-1]);
		for(int i=1;i<=kk;++i)
		{	if(a[i+m1-1]+kk!=a[i+m1+kk-1] or a[i+m2-1]+kk!=a[i+m2+kk-1])
			{
				bo=1;break;
			}
		}
		if(!bo)dfs(x+1,num+1);
		for(int i=1;i<=kk;++i)
			swap(a[i+mm1-1],a[mm2+i-1]);
	}
	inline void work()
	{	for(int i=1;i<=(1<<n);++i)scanf("%d",&a[i]);
		dfs(1,0);printf("%d\n",ans);
	}
	inline short main()
	{sc();work();return 0;}
}
signed main()
{return AYX::main();}

APIO2016 划艇

看到T2好像还可做,就仔细想了很长时间,想到了f[i][j]+=f[i-1][p] (p<=j-1),但是回头一瞧,1e9的数据。。。。。。。。。。。。。还玩个球啊,老老实实打暴力。但是n最小是100,显然没有暴力分。。。。。。。。。

好吧好吧,那就不属于我了,1e9也想过离散化,可是后面就没什么进展了。

DP

dp的话得先把1e9的问提解决掉,要不然没法搞,没错,肯定要离散化。可是离散化了后我怎么整呢,原本是实在的位置,现在就是一群无情的数字。额,其实这数字还是有情的唉,他能断开不同的区间,我们求每一段区间的贡献就好了。

啥?区间贡献,这不更没法搞了哈哈。仔细想一下,本段区间对i点的贡献,跟推出来的那个式子好像很有关系的,我们只需要知道i前面有几个点也在区间j内,比如说现在到了k点,他可以在区间j内,因为k到i之间的点是可以取0的,所以不用管他,我们只关心在k和i之间可以有几个点出现在区间j内就好,那么这里每一个k的贡献就是k之前的点在j-1之前区间的方案数×k到i的m个点在j内的方案数,前者好说,我们递推过来就求过了,关键是看后者。

对于长度为len的区间,任取m个位置ans就是C(len,m),但是现在问题是我不一定m个点都取位置,他可以不参赛的,也就是在中间夹着0呢。因为0对递增是无影响的所以我完全可以想像为在len区间里塞了m个0,但是i点得在,那么就是塞了m-1个0,这样的话方案数就是C(len+m-1,m)。

好了,这样dp就解决了,因为我do时用的是i点的前j个区间,所以数组要维护前缀和,又因为我的区间是从小到大推过来的,更新了j后j-1就没有用了,所以我们可以隐去第二维,这样省很多空间。最后ans就是所有的g数组加一起,因为后面的点都可以为0的,所以对于每一个1到i都算新方案。

%%%@yspm

#include<bits/stdc++.h>
#define int long long
#define low(x) lower_bound(c+1,c+1+tot,x[i])-c
using namespace std;
const int mod=1e9+7;
int n,len,a[505],b[505],c[1015],num,sum[505]={1},C[505]={1},inv[505]={0,1},ans;
inline void fr(){freopen("c.in","r",stdin);}
inline void sc(){scanf("%lld",&n);}
namespace AYX
{	inline void work()
	{	for(int i=1;i<=n;++i)scanf("%lld%lld",&a[i],&b[i]),c[++num]=a[i],c[++num]=++b[i];
		for(int i=2;i<=n;++i)inv[i]=(mod-mod/i)*inv[mod%i]%mod;
		sort(c+1,c+1+num);
		int tot=unique(c+1,c+1+num)-c-1;
		for(int i=1;i<=n;++i)
		a[i]=low(a),b[i]=low(b);
		for(int j=1;j<tot;++j)
		{	int len=c[j+1]-c[j];
			for(int i=1;i<=n;++i)C[i]=(((C[i-1]*(len+i-1))%mod)*inv[i])%mod;
			for(int i=n;i;--i)
			{	if(a[i]<=j and b[i]>=j+1)
				{	int f=0,m=1,cc=len;
					for(int k=i-1;k>=0;--k)
					{	f=(f+cc*sum[k])%mod;
						if(a[k]<=j and b[k]>=j+1)cc=C[++m];
					}
					sum[i]=(sum[i]+f)%mod;
				}
			}
		}
		for(int i=1;i<=n;++i)ans=(ans+sum[i])%mod;
		printf("%lld",ans);
	}	
	inline short main()
	{sc();work();return 0;}
}
signed main()
{return AYX::main();}

CQOI2011 放棋子

很明显的dp题。算是组合计数类dp吧。

一个很妙的地方,一个棋子占据一行一列,我们去掉这一行一列,状态变为(n-1)* (m-1)的棋盘,这算是dp的一个子状态,递推的味道很浓。

我们考虑当前到了第k个颜色,f[i][j][k]表示前k种颜色占据了i行j列,这i和j只是个数,具体位置任意。那么f[i][j][k]=f[l][r][k-1]* g[i-l][j-r]* c[n-l][i-l]* c[m-r][j-r].
组合数很好理解,都说了ij是任意的。那g是啥呢???

g表示对于第k种颜色的c[k]个棋子,占据i行j列的方案数,这里的i和j也是任意的哦。
g全部的方案明显为C(i* j,c[k]),可是这么着放不一定占全了这i行j列,怎么办?减去不合法的呗。g[i][j]-=g[l][r]* C(i,l)* C(j,r).(l<i or r<j)同样也是递推的。

有人就疑问了,为什么有两次组合数呢?其实很好理解,推g数组时l和r是在一个确定的i和j里选的,也就是说这个组合数是l和r相对于i和j的小范围的,而f数组的组合数是相对整张棋盘的。

好了,本题到此结束。

code

#include<bits/stdc++.h>
#define int long long
#define m(x) memset(x,0,sizeof(x))
using namespace std;
const int mod=1000000009;
int n,m,C,ans,num[11],c[901][901],f[31][31][31],g[31][31];
inline void fr(){freopen("c.in","r",stdin);}
inline void sc(){scanf("%lld%lld%lld",&n,&m,&C);}
namespace AYX
{	
	inline void work()
	{	for(int i=1;i<=C;i++)scanf("%lld",&num[i]);
		for(int i=0;i<=n*m;++i)
		{	c[i][0]=1;
			for(int j=1;j<=i;++j)
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
		}
		f[0][0][0]=1;
		for(int k=1;k<=C;++k)
		{	m(g);
			for(int i=1;i<=n;++i)
			for(int j=1;j<=m;++j)
			{	if(i*j>=num[k])
				{	g[i][j]=c[i*j][num[k]];
					for(int l=1;l<=i;++l)
					for(int r=1;r<=j;++r)
					if(l<i or r<j)
					g[i][j]=(g[i][j]-(((g[l][r]*c[i][l])%mod)*c[j][r])%mod+mod)%mod;
				}
			}
			for(int i=1;i<=n;++i)
			for(int j=1;j<=m;++j)
			for(int l=0;l<i;++l)
			for(int r=0;r<j;++r)
			{	int nx=i-l,ny=j-r;
				if(nx*ny>=num[k])
				f[i][j][k]=(f[i][j][k]+((f[l][r][k-1]*g[nx][ny]%mod*c[n-l][nx])%mod)%mod*c[m-r][ny]%mod)%mod;
			}
		}
		for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		ans=(ans+f[i][j][C])%mod;
		printf("%lld\n",ans);
	}
	inline short main()
	{sc();work();return 0;}
}
signed main()
{return AYX::main();}
posted @ 2021-10-01 20:01  -zxb-  阅读(38)  评论(0)    收藏  举报