一、Tetrahedron(1001)
题目链接:Tetrahedron
输入:
3
1
2
3
输出:
3
124780546
194103070
题目大意: 如图所示,存在这样一个四面体:a,b,c三条边两两垂直,并且边长取值为1~n的整数。h是以ABC为底面而形成的个高,然后问你1/h2的期望为多少?
解题思路: 给出的条件是a,b,c的取值范围,求的是1/h2的取值范围,那么我们就需要找出h与a,b,c之间存在的关系,通过体积法我们可以发现存在如下一个等式关系。
1
h
2
=
1
a
2
+
1
b
2
+
1
c
2
\frac{1}{h^2}=\frac{1}{a^2}+\frac{1}{b^2}+\frac{1}{c^2}
h21=a21+b21+c21
那么我们可以枚举发现当a=1的时候b,c存在n2种取值,那么也就是说在一共的n3种情况中,a的任意一种取值都有n2个,也就是:
E
(
1
a
2
)
=
n
2
∑
i
=
1
n
1
i
2
n
3
E(\frac{1}{a^2})=\frac{n^2\sum_{i=1}^{n}\frac{1}{i^2}}{n^3}
E(a21)=n3n2∑i=1ni21
同理可得E(1/b2)和E(1/c2),因此E(1/h2)=3*E(1/a2)。
代码:(因为t比较大,因此直接预处理整个范围,最后直接得出结果)
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<string>
#include<sstream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<vector>
#define ll long long
using namespace std;
const ll mod = 998244353;
const ll N=6000005;
ll kuai(ll a,ll b,ll mod)
{
ll ans=1;
while(b>0)
{
if(b&1)
ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
ll FMinv(ll a,ll p) //a关于p的逆元为 a^(p-2) (mod p)
{
return kuai(a,p-2,p);
}
ll sum[N];
int main()
{
int t;
int n;
ll ans;
cin>>t;
sum[1]=1;
for(ll i=2;i<=N;i++)
{
sum[i]=(sum[i-1]+(1*FMinv((i*i)%mod,mod)))%mod;
}
while(t--)
{
scanf("%d",&n);
ans = (sum[n]*3)%mod*FMinv(n,mod);
printf("%lld\n",ans%mod);
}
}
二、 Paperfolding(1009)
题目链接: Paperfolding
输入:
2
0
1
输出:
4
6
**题目大意:**给你一张纸,和一个指令字符串(长度为n),字符串中的每一个字符都只有四种取值,分别是L,R,U,D分别代表不同的对纸的操作,L表示从左往右对折(其余同理),n次操作过后,用剪刀沿着中心水平剪一刀,垂直剪一刀,能有多少张纸。现在每个字符的操作(L,R,U,D)随机选取,问最后纸张的数目的期望为多少。
**解题思路:**求解期望问题,首先我们知道有4n种情况,那么我们就需要求出所有情况的结果之和,然后除以4n即为期望。首先先提出一个结论:假设字符串S中存在x个水平折叠操作(L,R),n-x个竖直折叠操作(U,D),那么这种情况就会产生(2x+1)(2n-x+1)张纸。那么首先就存在
C
n
x
C_{n}^{x}
Cnx个不同的字符串,又因为每个操作位都有两种可能(水平操作存在L,R,竖直操作存在U,D)那么就有2n
C
n
x
C_{n}^{x}
Cnx种情况是可以产生(2x+1)*(2n-x+1)张纸的。那么我们只需要把x从0取值到n的所有的结果加起来,就是所有情况的总和。最后的结果就是下面这个式子:
∑
x
=
0
n
(
2
x
+
1
)
(
2
n
−
x
+
1
)
∗
C
n
x
∗
2
n
4
n
\frac{\sum_{x=0}^{n}(2^x+1)(2^{n-x}+1)*C_{n}^{x}*2^n}{4^n}
4n∑x=0n(2x+1)(2n−x+1)∗Cnx∗2n
式子化简:可以先把分子的2n与分母的4n约掉,然后把括号拆开,每项乘以
C
n
x
C_{n}^{x}
Cnx,然后用二项式展开的逆方向即可(3n=(2+1)n)。如下式:
∑
x
=
0
n
(
C
n
x
2
x
1
n
−
x
+
C
n
x
2
n
−
x
1
x
+
C
n
x
2
n
+
C
n
x
)
2
n
=
2
∗
3
n
+
4
n
+
2
n
2
n
\frac{\sum_{x=0}^{n}(C_{n}^{x}2^x1^{n-x}+C_{n}^{x}2^{n-x}1^{x}+C_{n}^{x}2^n+C_{n}^{x})}{2^n}=\frac{2*3^n+4^n+2^n}{2^n}
2n∑x=0n(Cnx2x1n−x+Cnx2n−x1x+Cnx2n+Cnx)=2n2∗3n+4n+2n
还有就是前面那个结论为什么是对的呢,因为你水平折叠一次,那么竖直方向的刀痕就会倍增一次,对水平方向的刀痕没有影响(你可以把刀痕画一下),同理竖直折叠也是一样的。因此结论是对的。
代码:
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<string>
#include<sstream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<vector>
#define ll long long
using namespace std;
const ll mod = 998244353;
const int N=6000005;
ll kuai(ll a,ll b,ll mod)
{
ll ans=1;
while(b>0)
{
if(b&1)
ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
ll FMinv(ll a,ll p) //a关于p的逆元为 a^(p-2) (mod p)
{
return kuai(a,p-2,p);
}
ll sum[N];
int main()
{
int t;
ll n;
scanf("%d",&t);
ll ans=0;
while(t--)
{
scanf("%lld",&n);
ans=(kuai(4,n,mod)+2*kuai(3,n,mod)+kuai(2,n,mod))%mod;
ans=(ans*FMinv(kuai(2,n,mod)%mod,mod))%mod;
printf("%lld\n",ans);
}
}
三、Boring Game(1003)
题目链接:Boring Game
输入:
1
2 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
输出:
12 5 4 13 11 6 3 14 10 7 2 15 9 8 1 16
题目大意: 现在有n张纸叠在一起,现在让你对这些纸进行k次从左向右的折叠操作,然后对每一层的上下两个面依次赋值,然后再恢复原状(未折叠之前),问你这些数字是怎么排列的。
解题思路: 我们把最上面的那张纸记为1,最下面这张纸记为n,我们可以发现赋值的顺序是n~1~1~n这样的循环结构,那么我们可以把每张纸上面的赋值顺序记录下来,然后对每张纸进行还原即可。有人可能就要问了,每张纸正反面都有数字,这如何处理呢,其实我们可以把正反面看做不同的纸,也就是转化为2n张纸。现在的问题就转变成,在已知某一张纸的赋值情况,如何进行还原。
把这些数字看做是在一张纸上的(因为我们现在只考虑解决一张纸上的顺序),依照这样的转换规则,一直转换到一行,那么这一行就是最后的顺序。(当时其实知道这样转换,但是没敢模拟,以为有其他方法,害),现在我们已经知道一张纸怎么转化,那么2n张纸也是一样的道理。具体细节看代码吧。
代码:
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<string>
#include<sstream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<vector>
#define ll long long
using namespace std;
const ll mod = 998244353;
const int N=1e6+5;
ll num[N];
vector<int> V[500];
int moni[1200][1200];
int main()
{
int t,n,k,flag;
scanf("%d",&t);
while(t--)
{
flag=1;
scanf("%d%d",&n,&k);
int len=2*n*pow(2,k);
for(int i=1;i<=len;i++)
{
scanf("%d",&num[i]);
}
for(int i=1;i<=2*n;i++)//把n张纸看做2n张,然后每张纸里面的数字挑选出来
{
V[i].clear();//记得初始化
for(int j=2*n-i+1;j<=len;j+=4*n)
{
V[i].push_back(num[j]);
V[i].push_back(num[j+(2*i)-1]);
}
}
len=V[1].size();
for(int i=1;i<=2*n;i++)//里面就是对每一张纸的模拟过程(在数组中是一行变成一列)
{
int L=1,R=len/2;
for(int j=1;j<=len;j++)
{
moni[1][j]=V[i][j-1];
}
for(int j=1;j<=k;j++) //从一列转化到一行需要k次
{
int tmp=pow(2,j-1);
for(int l=1;l<=tmp;l++)
{
for(int m=L;m<=R;m++)
{
moni[2*tmp-l+1][R+m-L+1]=moni[l][R-m+L];
}
}
L=R;
R+=(len-R)/2;
}
for(int k=len;k>=1;k--)
{
if(flag==1)
{
printf("%d",moni[k][len]);
flag=0;
}
else
{
printf(" %d",moni[k][len]);
}
}
}
printf("\n");
}
}
四、Set1 (1012)
题目链接: Set1
输入:
1
3
输出:
0 499122177 499122177
题目大意: 给你一个1~n的序列(n确保是奇数),现在你可以执行这样的操作:先删除序列中最小的数字(记作小操作1),然后再任意删除其中一个数字(小操作2)。重复这样的操作,直到整个序列就剩一个数字。现在问你对于序列中的每一个数字来说,留下的概率为多少。
解题思路: 首先呢,我们知道1~
n
2
\frac{n}{2}
2n这些数字是不可能留下来的。那么我们怎么求出剩下数字留下的概率呢。那么对于数字i来说我们知道概率就是留下数字i对应的方案数除以总方案数。也就是我我们要求出留下i的所有情况。那么数字i前面的数有i-1个,i后面的数有n-i个。对于后面的数来说,一定是被小操作2带走的。一次操作带走两个数,那么我们先从前面i-1个数中挑选出n-i个数进行匹配。也就是
C
i
−
1
n
−
i
∗
(
n
−
i
)
!
C_{i-1}^{n-i}*(n-i)!
Ci−1n−i∗(n−i)!种情况,后面乘以(n-i)!是因为在选出数之后,还要匹配。(类似于高中排列组合,选同学出来坐位置,位置有编号),那么后面n-i个数处理好了,现在现在前面剩下(i-1)-(n-i)个数还没匹配。这些剩下的数字两两匹配,也就是
C
2
i
−
n
−
1
2
∗
C
2
i
−
n
−
3
2
∗
.
.
.
∗
C
2
2
A
(
2
i
−
n
−
1
)
/
2
(
2
i
−
n
−
1
)
/
2
\frac{C_{2i-n-1}^{2}*C_{2i-n-3}^{2}*...*C_{2}^{2}}{A_{(2i-n-1)/2}^{(2i-n-1)/2}}
A(2i−n−1)/2(2i−n−1)/2C2i−n−12∗C2i−n−32∗...∗C22(这里也就是高中的平均分组问题)。现在我们已经把所有的数(除了i)都匹配好了,现在有人可能会问接下来是不是要对这些匹配好的二元组进行排序呢?答案是不用的,因为在每一个二元组中,有一个数字是被小操作1带走的,那么这些被小操作1带走的数自然就形成了一种顺序(按照这个数字从小到大排序)。那么对于数字i来说方案数就是如下这个式子:
C
i
−
1
n
−
i
∗
(
n
−
i
)
!
∗
C
2
i
−
n
−
1
2
∗
C
2
i
−
n
−
3
2
∗
.
.
.
∗
C
2
2
A
(
2
i
−
n
−
1
)
/
2
(
2
i
−
n
−
1
)
/
2
C_{i-1}^{n-i}*(n-i)!*\frac{C_{2i-n-1}^{2}*C_{2i-n-3}^{2}*...*C_{2}^{2}}{A_{(2i-n-1)/2}^{(2i-n-1)/2}}
Ci−1n−i∗(n−i)!∗A(2i−n−1)/2(2i−n−1)/2C2i−n−12∗C2i−n−32∗...∗C22
提示:
C
2
i
−
n
−
1
2
∗
C
2
i
−
n
−
3
2
∗
.
.
.
∗
C
2
2
=
(
2
i
−
n
−
1
)
!
2
(
2
i
−
n
−
1
)
/
2
C_{2i-n-1}^{2}*C_{2i-n-3}^{2}*...*C_{2}^{2}=\frac{(2i-n-1)!}{2^{(2i-n-1)/2}}
C2i−n−12∗C2i−n−32∗...∗C22=2(2i−n−1)/2(2i−n−1)!,具体的细节看代码吧。
代码:
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<string>
#include<sstream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<vector>
#define ll long long
using namespace std;
const ll mod = 998244353;
ll kuai(ll a,ll b,ll mod)
{
ll ans=1;
while(b>0)
{
if(b&1)
ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
ll FMinv(ll a,ll p) //a关于p的逆元为 a^(p-2) (mod p)
{
return kuai(a,p-2,p);
}
ll num[4000005];
ll D[4000005];
ll J2[4000005];
int main()
{
int t;
int n;
ll sum=0;
scanf("%d",&t);
J2[0]=1;
J2[1]=1;
for(int i=2;i<=4000005;i++) //预处理出阶乘
{
J2[i]=(J2[i-1]*i)%mod;
}
while(t--)
{
sum=0;
scanf("%d",&n);
if(n==1)
{
printf("1\n");
continue;
}
//预处理出前面这一部分
D[n/2+1]=1;
for(int i=1;i<=n-(n/2+1);i++)
{
D[n/2+1]=(D[n/2+1]*(n/2+1-i))%mod;
}
//cout<<D[n/2+1]<<endl;
for(int i=n/2+2;i<=n;i++)
{
D[i]=D[i-1]*(i-1)%mod;
D[i]=(((D[i]*FMinv(2*i-n-2,mod))%mod)*FMinv(2*i-n-1,mod))%mod;
//cout<<D[i]<<endl;
}
for(int i=1;i<=n;i++)
{
ll tmp=1;
if(i<=n/2)
num[i]=0;
else
{
tmp=tmp*D[i]%mod;
tmp=tmp*J2[2*i-n-1]%mod;
ll L=kuai(2,(2*i-n-1)/2,mod);
//cout<<L<<endl;
tmp=tmp*FMinv(L,mod)%mod;
tmp=tmp*FMinv(J2[(2*i-n-1)/2],mod)%mod;
num[i]=tmp;
//cout<<tmp<<endl;
sum=(sum+tmp)%mod;
}
}
ll Ni =FMinv(sum,mod);
//cout<<Ni<<endl;
for(int i=1;i<=n;i++)
{
if(i==1)
printf("%lld",(num[i]*Ni)%mod);
else
printf(" %lld",(num[i]*Ni)%mod);
}
printf("\n");
}
}