5.23考试总结(NOIP模拟2)
Posted Varuxn
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5.23考试总结(NOIP模拟2)相关的知识,希望对你有一定的参考价值。
5.23考试总结(NOIP模拟2)
洛谷题单
看第一题第一眼,不好打呀;看第一题样例又一眼,诶,我直接一手小阶乘走人
然后就急忙去干T2T3了
后来考完一看,只有\\(T1\\)骗到了\\(15pts\\)[尴尬\\(.jpg\\)]
\\(T1\\)P3322 [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+∑_{k=1}^{i-1}∑_{k=1}^{j-1}f[k][t]\\)
考虑优化,我们需要的只是长度,因此可以将每个端点离散化,然后分段枚举
方案分两部分:
- i从(j-1)~j选一个,前面所有的学校要么不选要么从1~(j-1)区间中选,方案数为\\(\\sum\\limits_{l=1}^{i-1}\\sum\\limits_{r=1}^{j-1} f[l][r]*C_{len}^1\\)
- i从(j-1)j选一个,前面有学校也从(j-1)j中选,定第一个从(j-1)j中选的学校为k,显然总方案数为(ki的方案数)*\\(\\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*\\frac{n+1}{m+1}\\)
然后用前缀和维护一下
f[i][j],表示1~i学校从1~j区间选的方案总数,也就是下面的式子:
\\(f[i][j]=len*f[i-1][j-1]+\\sum\\limits_{k=i-1}^{1} C_{l+p-2}^p*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;
}
以上是关于5.23考试总结(NOIP模拟2)的主要内容,如果未能解决你的问题,请参考以下文章