5.23考试总结(NOIP模拟2)

5.23考试总结(NOIP模拟2)

洛谷题单

看第一题第一眼,不好打呀;看第一题样例又一眼,诶,我直接一手小阶乘走人
然后就急忙去干T2T3了
后来考完一看,只有\(T1\)骗到了\(15pts\)[尴尬\(.jpg\)]

T1P3322 [SDOI2015]排序

背景

说实话,看见这题正解是dfs的那一刻,我人都傻了[流泪.jpg]

在讲这题的时候赵队@yspm 类比了线段树的思想%%%%%,在食用本篇题解时可以想一下

解题思路

最基本的一个思想:结果与操作的顺序无关,因为在更换的时候无论先换哪一个最后排列都是一定的。

因此我们肯定会用到\(A_n^n\)也就是n的阶乘,需要初始化一下,这里用的jc数组记录

然后就可以 愉快 搜索了。

  • 先是check函数;

    check(x)用来检查所有长度为1<<x的块是否满足递增,
    有一个非常妙的地方:我们已经知道这个序列是由1~\(2^n\)组成的了

    因此在判断的时候只需要循环,看第i块的第一个数是否和第j块的第一个数正好差一个块长就好了

    bool check(int x)
    {
    	for(int i=1;i<=(1<<(n-x));i++)
    		if(s[(i-1)*(1<<x)+1]+(1<<(x-1))!=s[(i-1)*(1<<x)+(1<<(x-1))+1])
    		return false;
    	return true;
    }
    
  • 再谈交换函数:
    swap(i,j,k)表示将分别以i和j开始长度为k的序列互换

       void swap(int i,int j,int k)
    {
    	for(int l=1;l<=k;l++)
    		swap(s[i+l-1],s[j+l-1]);
    }
    
  • 最后是dfs函数

    dfs(x,num)表示交换到块长为1<<x了,并且此前进行了num个操作

    如果这个块不是单调递增的,直接return就好,毕竟他对于答案是没有贡献的

    对于x==n时表明整个序列已经整完了,然后直接给ans加上jc[num]就好了

    然后直接向下进行dfs(x+1,num)不做任何处理

    对于进行处理的情况,把整个序列两块两块的看,如果不符合条件(判断方法与上面的check同)记录到一个t数组里,一会处理,
    如果需要处理的总数超过4的话直接break,剩下的直接交给后面就可以了

    然后暴力两两枚举操作,进行交换后,直接进行下一层dfs以及回溯就好了

\(code\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e3;
int n,ans,jc[13],s[N];
bool check(int x)
{
	for(int i=1;i<=(1<<(n-x));i++)
		if(s[(i-1)*(1<<x)+1]+(1<<(x-1))!=s[(i-1)*(1<<x)+(1<<(x-1))+1])
		return false;
	return true;
}
void swap(int i,int j,int k)
{
	for(int l=1;l<=k;l++)
		swap(s[i+l-1],s[j+l-1]);
}
void dfs(int x,int num)
{
	if(x&&!check(x))
		return ;
	if(x==n)
	{
		ans+=jc[num];
		return ;
	}
	dfs(x+1,num);
  int t[5],tot=0;
	for(int i=1;i<=(1<<(n-x));i+=2)
		if(s[i*(1<<x)+1]!=s[(i-1)*(1<<x)+1]+(1<<x))
		{
			if(tot==4)
				break;
			t[++tot]=i;
			t[++tot]=i+1;
		}
	if(!tot)
		return ;
	for(int i=1;i<=tot;i++)
		for(int j=i+1;j<=tot;j++)
		{
			swap((1<<x)*(t[i]-1)+1,(1<<x)*(t[j]-1)+1,1<<x);
			dfs(x+1,num+1);		
			swap((1<<x)*(t[i]-1)+1,(1<<x)*(t[j]-1)+1,1<<x);
		}
}
#undef int
int main()
{
	#define int long long
	scanf("%lld",&n);
	jc[0]=1;
	for(int i=1;i<=n;i++)
		jc[i]=jc[i-1]*i;
	for(int i=1;i<=(1<<n);i++)
		scanf("%lld",&s[i]);
	dfs(0,0);
	printf("%lld",ans);
	return 0;
}

T2题解 P3643 [APIO2016]划艇

背景

打了挺长时间爆搜,考完一交到洛谷,TLE是小事,重点是运行了3.73min,难怪一分没有QAQ

解题思路

先想一下最简单的打法吧

用f[i][j]表示前i所学校中,第i所学校参赛,且派出j艘划艇的方案数

显然,\(f[i][j]=1+\sum\limits_{k=1}^{i-1}\sum\limits_{t=1}^{j-1}f[k][t]\)

考虑优化,我们需要的只是长度,因此可以将每个端点离散化,然后分段枚举

方案分两部分:

  1. i从(j-1)~j选一个,前面所有的学校要么不选要么从1~(j-1)区间中选,方案数为\(\sum\limits_{l=1}^{i-1}\sum\limits_{r=1}^{j-1} f[l][r]\times C_{len}^1\)
  2. i从(j-1)~ j选一个,前面有学校也从(j-1)~j 中选,定第一个从(j-1)~j 中选的学校为k,显然总方案数为\((k\sim i的方案数)\times \sum\limits_{l=1}^{k-1}\sum\limits_{r=1}^{j-1} f[l][r]\)

将1和2式子合并一下就可以得到\(\sum\limits_{l=1}^{i-1}\sum\limits_{r=1}^{j-1} f[l][r]+\sum\limits_{l=1}^{k-1}\sum\limits_{r=1}^{j-1} f[l][r]\)

可知ki学校一定选择j-1j中的数或者选择0,

因为k和i都从j区间中选,k+1~i-1学校可以选择j区间的如果选的话一定从j区间中选,如果不选就选0,而不可以选j区间的一定不能派划艇

有一个引理

从区间[0,L]中取n个数,要求所有非零数严格递增,则方案数为\(C_n^{L+n}\)

一看这式子,直接杨辉三角走起,再一看数据范围。。。。十分不友善,考虑一下从以前的状态中转移过来:

\(C_{n+1}^{m+1}=C_n^m\times\frac{n+1}{m+1}\)

然后用前缀和维护一下

f[i][j],表示1~i学校从1~j区间选的方案总数,也就是下面的式子:
\(f[i][j]=len\times f[i-1][j-1]+\sum\limits_{k=i-1}^{1} C_{l+p-2}^p\times f[k-1][j-1]\)

\(code\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e2+10,mod=1e9+7;
int n,cnt,ans,ys[N<<1],a[N],b[N],f[N],g[N],inv[N];
void get_INV()
{
	inv[0]=inv[1]=1;
    for(int i=2;i<=n;i++)
        inv[i]=((mod-mod/i)*inv[mod%i])%mod;
}
#undef int
int main()
{
	#define int long long
	scanf("%lld",&n);
	get_INV();
	for(int i=1;i<=n;i++)
	{
		scanf("%lld%lld",&a[i],&b[i]);
		ys[++cnt]=a[i];
		ys[++cnt]=++b[i];
	}
	sort(ys+1,ys+cnt+1);
	cnt=unique(ys+1,ys+cnt+1)-ys-1;
	for(int i=1;i<=n;i++)
	{
		a[i]=lower_bound(ys+1,ys+cnt+1,a[i])-ys;
		b[i]=lower_bound(ys+1,ys+cnt+1,b[i])-ys;
	}
	f[0]=g[0]=1;
	for(int j=1;j<cnt;j++)
	{
		int len=ys[j+1]-ys[j];
		for(int i=1;i<=n;i++)
			g[i]=g[i-1]*(len+i-1)%mod*inv[i]%mod;
		for(int i=n;i>=1;i--)
			if(a[i]<=j&&j<b[i])
				for(int k=i-1,tot=1;k>=0;k--)
				{
					f[i]=(f[i]+f[k]*g[tot]%mod)%mod;
					if(a[k]<=j&&j<b[k])
						tot++;
				}
	}
	for(int i=1;i<=n;i++)
		ans=(ans+f[i])%mod;
	printf("%lld",ans);
	return 0;
}

T3题解 P3158 [CQOI2011]放棋子

背景

考试的时候这个题也看了不短时间,想到了题解的一小部分,剩下的就不知道偏到哪去了,本来一开始还想整一下dp呢,到了后来直接组合数学呀,欧拉筛呀一些玄学东西,最后的测试结果也是WA,RE,TLE五花八门的。。。

解题思路

首先感谢这篇blog,以及赵队的讲解,让我懂了这个题。。。

因为n的数据比较小吗,我们的数组可以多开那么两维

  • f[i][j][k]表示前k种颜色占领了任意i行j列的方案数。

  • g[i][j][k]表示任意k枚同色棋子放任意i行j列的方案数。

g数组主要用于维护f数组

先看f数组

\(f[i][j][k]=\sum_{l=0}^{i-1}\sum_{r=0}^{j-1}f[l][r][k-1]*g[i-l][j-r][s[k]]*C^{n-l}_{i-l}*C^{m-r}_{j-r}\)

更新的条件是$(i-l)*(j-r) \ge s[k] $

\(\sum_{l=0}^{i-1}\sum_{r=0}^{j-1}\)枚举前k-1种颜色在l行r列时的情况

\(f[l][r][k-1]\)为前k-1种颜色在l行r列时的方案数

\(g[i-l][j-r][s[k]]*C^{n-l}_{i-l}*C^{m-r}_{j-r}\)则是枚举第k种颜色在i-l行j-r列的方案数

再看g数组

\(g[i][j][k]=C^{i*j}_k-\sum_{l=1}^i\sum_{r=1}^jg[l][r][k]*C_l^i*C_r^j\)

更新的条件是\(l<i||r<j\)并且\(i*j \ge k\)

\(C^{i*j}_k\)就是把i*j个数中整出来k个

由于我们要求g[i][j][k]要求记录的是把前i行前j列占满的情况,这里需要容斥一下

\(\sum_{l=1}^i\sum_{r=1}^jg[l][r][k]*C_l^i*C_r^j\)表示前i行前j列没有占满,也就是不符合条件的方案数

一些优化

显而易见,我们在求组合数时可以用杨辉三角预处理一下。

并且通过观察我们发现g数组通过在枚举种数是可以压掉最后一维节省空间。

在运算的时候要多取mod,杨辉三角求组合数尽量初始化大一些,否则连样例都过不了(别问我为什么知道,我就是知道[流泪.jpg])

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=35,M=310,mod=1e9+9;
int n,m,t,ans,s[N],c[N*N][N*N],f[N][N][N],g[N][N];
void get_C()
{
	for(int i=0;i<N*N;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;
	}
}
#undef int
int main()
{
	#define int long long
	scanf("%lld%lld%lld",&n,&m,&t);
	f[0][0][0]=1;
	get_C();
	for(int k=1;k<=t;k++)
	{
		scanf("%lld",&s[k]);
		memset(g,0,sizeof(g));
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				if(i*j>=s[k])
					g[i][j]=c[i*j][s[k]];
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				if(i*j>=s[k])
					for(int l=1;l<=i;l++)
						for(int r=1;r<=j;r++)
							if(l<i||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++)
						if((i-l)*(j-r)>=s[k])
							f[i][j][k]=(f[i][j][k]+f[l][r][k-1]*g[i-l][j-r]%mod*c[n-l][i-l]%mod*c[m-r][j-r]%mod)%mod;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
				ans=(ans+f[i][j][t])%mod;
	printf("%lld",ans%mod);
	return 0;
}
posted @ 2021-05-23 19:40  Varuxn  阅读(107)  评论(0编辑  收藏  举报