背景:
继国庆自闭四联考之后,确实,是很自闭,还做了些比较疯狂的事情,然后顿悟了很多,然后刚知道怎么去努力,刚知道努力的方法和方向就赶快滚回来写博客了,毕竟是在将近考试的时候出现了问题,还是赶快狗回来重要,,,不多扯了,毕竟都写好多了,而且今天任务有些重,就好好写吧,
(还要吐槽一下,,,不知道是谁说的,这套题适合我的,,,,,,我**)
题目:
Day1:
T1:煎蛋的疑惑(excatalan)
题目大意:
求 n n n对括号至少有 2 ∗ m 2*m 2∗m 个括号失配的序列个数。
题解:
先考虑 m = = 0 m==0 m==0 的时候,就是卡特兰数了,(可是我考场上忘掉了卡特兰数是什么了,然后就在自己硬推了一遍,然后就推出来了卡特兰数的递推式,但是那种比较菜的那种是要双重循环写的,然后这道题就赤裸裸的挂掉了,,,,)下面就用折线法来证一遍:
设左括号就是向左上方画一条线,右括号就是向右下方画一条线,就像是下面第一张图,就是正确的匹配。
如果都是正确的那这条线就是在x轴的上方。运用容斥的想法,求正确的个数就直接用总方案数来减去不正确的方案数就好了,总方案数是什么呢,由于每个折线或者是括号是有两种选法,要从中选出n个,就是
2
∗
n
2*n
2∗n 而不合法的
(
2
n
n
)
\dbinom{2n}{n}
(n2n) ,然后就是不合法的方案数,例如下面的第二张图,当一个不匹配的时候就不用管后面了,后面怎么都是不匹配,所以就是::
(
2
n
n
−
1
)
\dbinom{2n}{n-1}
(n−12n) ,所以结论来了:
h
(
n
)
=
(
2
n
n
)
−
(
2
n
n
−
1
)
h(n)=\dbinom{2n}{n}-\dbinom{2n}{n-1}
h(n)=(n2n)−(n−12n)
这就是神奇的卡特兰数,就是在解决有一个东西有两种情况在排列的时候要出现过前面的情况再出现这个情况时才是合法的,就像是括号的匹配法则一样(可能总结的比较草率了,之后会再写博客进行总结)
知道
m
=
=
0
m==0
m==0 的情况之后就很好知道其他的情况了,(然而我考场上并没有推出来,,,)观察上面的图,再结合题意,他让求的是至少有
2
∗
m
2*m
2∗m 个括号失配但是普通的卡特兰数是在“0”的上下保持界限,是至少有0个的括号失配,所以就可以想到把x轴进行下移,下移m,这样就保证了题意。
那么答案就是:
h
(
n
,
m
)
=
(
2
n
n
)
−
(
2
n
n
−
m
−
1
)
h(n,m)=\dbinom{2n}{n}-\dbinom{2n}{n-m-1}
h(n,m)=(n2n)−(n−m−12n)
a n s = h ( n , m ) − h ( n , m − 1 ) ans=h(n,m)-h(n,m-1) ans=h(n,m)−h(n,m−1)
代码:
#include<bits/stdc++.h>
#define int long long
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int sea=1e6+7;
const int mod=998244353;
int n,m,f[sea],jc[sea],ny[sea];
int ksm(int a,int b)
{
int s=1;
while(b)
{
if(b&1) s=s*a%mod;
a=a*a%mod;
b>>=1;
}
return s;
}
int C(int x,int y)
{
if(x<y) return 0; if(x<0||y<0) return 0;
return jc[x]*ny[y]%mod*ny[x-y]%mod;
}
int excatlan(int x,int y){return (C(2*x,x)-C(2*x,x-y-1)+mod)%mod;}
signed main()
{
freopen("excatalan.in","r",stdin);
freopen("excatalan.out","w",stdout);
n=read(); m=read(); jc[0]=1;
over(i,1,n*2) jc[i]=jc[i-1]*i%mod;
ny[n*2]=ksm(jc[n*2],mod-2);
lver(i,n*2,1) ny[i-1]=ny[i]*i%mod;
if(m==0) printf("%lld\n",excatlan(n,0));
else printf("%lld\n",(excatlan(n,m)-excatlan(n,m-1)+mod)%mod);
// if(n==m) {printf("1\n");return 0;}
// if(m==1) {if(n>2) printf("%lld\n",ksm(2,n)-2);else if(n==2) puts("3");return 0;}
// if(m==0)
// {
// f[1]=1; f[2]=2;
// over(i,3,n) {over(j,1,i) f[i]=(f[i]+f[j]*(i-j))%mod; f[i]=(f[i]+2)%mod;}
// printf("%lld\n",f[n]);
// }
return 0;
}
T2:蘑菇(shimeji)
简化题目:
给你一棵树,删除每一条边的概率是 1 2 \frac{1}{2} 21 ,分解这棵树的混乱度是他所有联通块大小的积,求分解后的期望混乱度。
题解:
(一看到联通块就想到了洛谷上的板子题“连通数”,当时大佬们都在写而我这个菜鸡在订正所以就没写,,后悔了,,,,其实写不写都一样,,,)
正解:
DP啊,上来就设状态: f [ i ] f[i] f[i]:以 i i i 为根联通块里的已经选了一个点的答案, g [ i ] g[i] g[i] :以 i i i 为根联通块里的没有选点的答案。然后,,直接枚举边DP即可。至于期望,就在最后乘上 ( 1 2 ) n − 1 (\frac{1}{2})^{n-1} (21)n−1(这里别忘了求逆元)即可。
代码:
//#include<bits/stdc++.h>
#define int long long
//#define D double
//#define over(i,s,t) for(int i=s;i<=t;i++)
//#define lver(i,t,s) for(int i=t;i>=s;i--)
//using namespace std;
//inline int read()
//{
// int s=0,w=1;char ch=getchar();
// while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
// while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
// return s*w;
//}
//const int sea=1e6+7;
//const int mod=998244353;
//int n,m,f[sea];
//int ksm(int a,int b)
//{
// int s=1;
// while(b)
// {
// if(b&1) s=s*a%mod;
// a=a*a%mod;
// b>>=1;
// }
// return s%mod;
//}
//signed main()
//{
// freopen("shimeji.in","r",stdin);
// freopen("shimeji.out","w",stdout);
// n=read();
// over(i,1,n-1) {int x=read(),y=read();}
// int ans=(2+ksm(ksm(2,n),mod-2)-ksm(ksm(2,n-3),mod-2))%mod;
// printf("%d\n",ans);
// return 0;
//}
#include<bits/stdc++.h>
#define int long long
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int sea=2e6+7;
const int mod=998244353;
const int inv2=(mod+1)/2;
struct see{int ver,next,edge;}e[sea<<1];
int n,m,tot,last[sea],f[sea],g[sea];
void add(int x,int y){e[++tot].ver=y;e[tot].next=last[x];last[x]=tot;}
int ksm(int a,int b)
{
int s=1;
while(b)
{
if(b&1) s=s*a%mod;
a=a*a%mod;
b>>=1;
}
return s;
}
void dfs(int x,int fa)
{
f[x]=g[x]=1;
for(int i=last[x];i;i=e[i].next)
{
int y=e[i].ver;if(y==fa) continue;dfs(y,x);
f[x]=(f[x]*g[y]+f[y]*g[x])%mod;
g[x]=g[x]*g[y]%mod;
}
g[x]=(g[x]+f[x])%mod;
}
signed main()
{
freopen("shimeji.in","r",stdin);
freopen("shimeji.out","w",stdout);
n=read();
over(i,1,n-1)
{
int x=read(),y=read();
add(x,y); add(y,x);
}
dfs(1,0); printf("%d\n",f[1]*ksm(inv2,n-1)%mod);
return 0;
}
T3:墙(wall)
简化题目:
给你一个n的任意配排列,求子序列的个数,子序列满足:每个数的大小都在与它相邻的右边的两个数之间,且序列长度为奇数。
题解:
(这个题,我看到范围就知道首先正解是 O ( n 2 ) O(n^2) O(n2)的,所以就看着写了个 O ( n 3 ) O(n^3) O(n3)的暴力模拟,然而,码力过菜的我没有调出来就暴毙了,,,)
正解:
又DP,上来先设状态: 设
f
[
i
]
f[i]
f[i]代表以
a
[
i
]
a[i]
a[i]为终点,上一个数大于
a
[
i
]
a[i]
a[i]的方案数,
g
[
i
]
g[i]
g[i]代表上个数
小于
a
[
i
]
a[i]
a[i]的方案数。转移时先按 a[i]从小到大枚举 i,再按 a[j]从大到小枚举j,若 j<i 则用 f[j]更新 g[i],否则用 g[i]更新 f[j]。复杂度 O(
n
2
n^2
n2),但空间复杂就是
O
(
n
)
O(n)
O(n),就可以过了。
代码:
#include<bits/stdc++.h>
#define int long long
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int sea=6060;
int n,mod,ans,a[sea],p[sea],f[sea],g[sea];//f[i]上升g[i]下降
//bool check(int x,int y)
//{
// int xx=a[x],yy=a[x],s=0,t=0;
// over(i,x,y)
// {
// if(i+2>y||i+1>y) break;
if(a[i]<a[i-1]&&a[i]>a[i+1]||a[i]>a[i-1]&&a[i]<a[i+1]) {puts("1");return 0;}
// if(a[i]<a[i+1]&&a[i+1]<a[i+2]||a[i]>a[i+1]&&a[i+1]>a[i+2]) return 0;
// }
// over(i,x+1,y) if(a[i]>xx) s++,xx=a[i];
// over(i,x+1,y) if(a[i]<yy) t++,yy=a[i];
// if(s>=(y-x+1)/2&&t>=(y-x+1)/2) return 1; else return 0;
//}
int add(int &x,int y){x+=y;if(x>=mod) x-=mod;}
void so()
{
over(i,1,n) lver(j,i-1,1)
if(p[j]<p[i]) add(f[i],g[j]);else add(g[j],f[i]);
}
signed main()
{
freopen("wall.in","r",stdin);
freopen("wall.out","w",stdout);
n=read(); mod=read(); over(i,1,n) a[i]=read(),p[a[i]]=i;
over(i,1,n) f[i]=1,g[i]=0; so();over(i,1,n) add(ans,f[i]);
over(i,1,n) f[i]=0,g[i]=1; so();over(i,1,n) add(ans,g[i]);
add(ans,mod-n%mod); printf("%lld\n",ans);
// int flag=1; over(i,1,n) if(a[i]!=i) flag=0;
// if(flag){ printf("%lld\n",n%mod);return 0;}
// else if(mod==2) {puts("0");return 0;}
// else
// {
// int ans=n;
// over(len,3,n) over(l,1,n)
// if((l+len-1)<=n&&check(l,l+len-1)) ans++;
// printf("%lld\n",ans%mod);
// }
return 0;
}
Day1总结:
(好多都写到小本本上了,今天右手不听使唤,,,,是因为昨天打了场四年以来的第一场羽毛球,,右手废了,,现在是抖的,,)
这次就是在自闭中醒来吧,经过了四天的自闭,我可能以后都不会再自闭了,不管考多少分都不会伤心了,,,(直接抄小本本吧)
1.单步查询要认真,谨记机会只有一次。
2.T1的题(猜):递推(矩乘加速)or 数论 or 序列简题(数据结构) or 公式 or 黄—绿题的模拟
3.递推和组合数要捡起来,要复习,要问,要扎实。
4.练基础,调代码的能力。
5.多写博客专题。
Day2:
T1:a
简化题目:
有一沓扑克牌,每张牌上1—n张牌,扣在桌面上,每次翻一张,求当翻到有相同牌的期望。
题解:
(在写这个题的时候,我几乎把所有考试时间都放在这个题上面了,然后就是没有推出来,还是没有刷够题,见这种题太少了,题解上说这是个高考范围的题目,艹,我好长时间没看期望啊,,(不是借口,就是不扎实)尽管当初学的时候很用心,可是做题的数目达不到就很没底了,还是好好滚回去刷提高训练营去,,,)
正解:
E
(
n
)
=
∑
k
=
1
n
+
1
(
n
−
1
)
!
k
(
k
−
1
)
n
k
−
1
(
n
−
k
+
1
)
!
E(n)=\sum^{n+1}_{k=1}\frac{(n-1)!k(k-1)}{n^{k-1}(n-k+1)!}
E(n)=k=1∑n+1nk−1(n−k+1)!(n−1)!k(k−1)
正解就这样结束了,,,(考场上我都推出来一半了,,,另一半挂了,,,)
解释一下吧,这个其实应该知道,求期望的时候,期望的和就是和的期望。在求每一张牌揭开后的概率就是
n
−
k
n
\frac{n-k}{n}
nn−k 就是,然后每翻一张牌的贡献是要乘起来的,那么就是:
P
(
k
)
=
1
×
n
−
1
n
×
n
−
2
n
×
…
×
n
−
(
k
−
2
)
n
×
k
−
1
n
P(k)=1\times \frac{n-1}{n}\times\frac{n-2}{n}\times…\times \frac{n-(k-2)}{n} \times \frac{k-1}{n}
P(k)=1×nn−1×nn−2×…×nn−(k−2)×nk−1
化简过后就是:
P
(
k
)
=
(
n
−
1
)
!
(
k
−
1
)
n
k
−
1
(
n
−
k
+
1
)
!
P
(
n
)
=
∑
k
=
1
n
+
1
(
n
−
1
)
!
(
k
−
1
)
n
k
−
1
(
n
−
k
+
1
)
!
P(k)=\frac{(n-1)!(k-1)}{n^{k-1}(n-k+1)!}\\P(n)=\sum^{n+1}_{k=1}\frac{(n-1)!(k-1)}{n^{k-1}(n-k+1)!}
P(k)=nk−1(n−k+1)!(n−1)!(k−1)P(n)=k=1∑n+1nk−1(n−k+1)!(n−1)!(k−1)
然后由于期望,每次再乘上k,即可。
#include<bits/stdc++.h>
#define int long long
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int sea=1e7;
const int mod=998244353;
int n,ma,jc[sea+10],inv[sea+10],in[sea+10];//以后要养成习惯,防止炸掉
//原来我是多开了个数组的,然后就莫名炸掉,空间一直MLE,所以就很疑惑,,减少sea就会发现一组数据RE,,然后就不断地调试才发现了原因。这里还是要感谢一下G-hsm的帮助,%%%%
int ksm(int a,int b)
{
int s=1;
while(b)
{
if(b&1) s=s*a%mod;
a=a*a%mod; b>>=1;
}
return s%mod;
}
signed main()
{
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
n=read(); over(i,1,n) a[i]=read();
jc[0]=1; over(i,1,sea) jc[i]=jc[i-1]*i%mod; inv[sea]=ksm(jc[sea],mod-2);
lver(i,sea-1,0) inv[i]=inv[i+1]*(i+1)%mod;
over(i,1,sea) in[i]=inv[i]*jc[i-1]%mod;
over(i,1,n)
{
int s=a[i],ans=0,xx=1;
over(k,1,s+1) ans=(ans+k*(k-1)%mod*inv[s-k+1]%mod*xx%mod)%mod,xx=xx*in[s]%mod;
ans=ans*jc[s-1]%mod;
printf("%lld\n",ans);
}
return 0;
}
至于T2和T3的反演,严重超过能力范围,,,就不订正了,等之后会写了在补坑。。。。
Day2总结:
这场,,打的,,,一点都不自闭,,,因为都不会,所以不自闭,,觉得现在就是好好复习,捡起来之前学的,保证自己的期望得分和自己的是实际得分越来越接近就好,这些神仙题就算了,能水几分是几分,然后就是尽量拿到自己能拿到的分数!小目标。
总结:
根据这次的模拟赛,我的心态很好这不用说,没有太大的波澜起伏,这都是与源于之前的几次打击,习惯了,然后这次是递推数论专练吧,,,觉得真有趣,联赛这样出就死人了,,,,还是要好好刷基础,数论和 递推这些东西也要捡起来的!!