Tetrahedron(数学推导,逆元)
题目传送门
思路
公式的推导,显然可以得到
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、b、c是等价的,所以可以直接计算
1
a
2
\frac{1}{a^2}
a21的期望然后乘以3
而
1
a
2
\frac{1}{a^2}
a21的期望:
( 1 1 2 ∗ 1 n + 1 2 2 ∗ 1 n + . . . . . . + 1 ( n − 1 ) 2 ∗ 1 n + 1 n 2 ∗ 1 n ) (\frac{1}{1^2}*\frac{1}{n}+\frac{1}{2^2}*\frac{1}{n}+......+\frac{1}{(n-1)^2}*\frac{1}{n}+\frac{1}{n^2}*\frac{1}{n}) (121∗n1+221∗n1+......+(n−1)21∗n1+n21∗n1)
所以我们可以将 1 n \frac{1}{n} n1提出来,可以先对期望打表
AC Code
#include<cstdio>
#include<iostream>
using namespace std;
#define ll long long
// #define TDS_ACM_LOCAL
const int mod=998244353;
const int N=6e6;
ll inv[N+9], a[N+9];
int n;
ll quick_pow(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void init(){
inv[1]=a[1]=1;
for(int i=2; i<=N; i++) inv[i]=(mod - mod/i) *inv[mod%i]%mod;
for(int i=2; i<=N; i++) a[i]=(a[i-1]+(inv[i]*inv[i])%mod)%mod;
}
void solve(){
cin>>n;
cout<<(a[n]*quick_pow(n,mod-2)*3)%mod<<endl;
return ;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#ifdef TDS_ACM_LOCAL
freopen("D:\\VS code\\.vscode\\testall\\in.txt", "r", stdin);
freopen("D:\\VS code\\.vscode\\testall\\out.txt", "w", stdout);
#endif
init();
int T;
cin>>T;
while(T--) solve();
return 0;
}
Boring Game
题目传送门
题目大意
有n张纸,将其连续的从左向右折叠 k 次,然后上到下标号
求将纸重新展开后的序号序列
思路
直接模拟展开的过程即可,采取vector存序号
每次将前一半的序号放到后一半的序号的前面,注意到翻转会使得上一半的序号反过来,所以需要提前翻转一下
AC Code
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<math.h>
using namespace std;
#define INF 0x3f3f3f3f
// #define TDS_ACM_LOCAL
const int N=5e5 +9;
int n, k, mx, x, mid;
vector<int> z[N];
void solve(){
cin>>n>>k;
mx=2*n*pow(2,k);
for(int i=1; i<=mx; i++) z[i].clear();
for(int i=1; i<=mx; i++) cin>>x, z[i].push_back(x);
mid=1;
for(int i=1; i<=k; i++){
mid=(mid+mx)>>1;
for(int j=mid+1; j<=mx; j++){
int p=mid-(j-mid-1); //上半部分的下标,从中间往上走
reverse(z[p].begin(), z[p].end()); //提前翻转上半部分对应的值,因为展开后值会反转
z[j].insert(z[j].begin(), z[p].begin(), z[p].end()); //将上半部分对应的序列插入下半部分相应的位置的前面
z[p].clear(); //清空上半部分的翻转的序列
}
}
for(int i=mx-2*n+1; i<=mx; i++){
for(auto t:z[i]) cout<<(t==z[mx-2*n+1].front() ? "":" ")<<t; //最后一个数字后面没有空格
}
cout<<endl;
return ;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#ifdef TDS_ACM_LOCAL
freopen("D:\\VS code\\.vscode\\testall\\in.txt", "r", stdin);
freopen("D:\\VS code\\.vscode\\testall\\out.txt", "w", stdout);
#endif
int T;
cin>>T;
while(T--) solve();
return 0;
}
Paperfolding
题目传送门
题目大意
给你n次操作机会,你可以将一张纸向上下左右任意一方向折叠,总共可折叠n次,求折叠完后十字切割能切出来的纸片的数量
思路
官方题解:
模拟一下可以看出水平对折和垂直对折的答案相对独立
对于
x
x
x次水平对折和
y
y
y垂直对折答案是
(
2
x
+
1
)
(
2
y
+
1
)
(2^x+1)(2^y+1)
(2x+1)(2y+1),
这个公式你可以试着模拟一下就会发现左右对折影响的是竖向折痕,每折叠一次
∗
2
*2
∗2,上下对折同理
因为通过反向逐操作还原,可以看到刀的痕迹的数量变化是每次在某一维倍增的
因此,相当于一张纸,水平和竖直分别切了
2
x
,
2
y
2^x, 2^y
2x,2y刀
所以数学期望为
1 2 n ∑ i = 0 n C n i ( 2 i + 1 ) ( 2 n − i + 1 ) \frac{1}{2^n} ∑^n_{i=0} C_n^i (2^i +1)(2^{n−i} +1) 2n1∑i=0nCni(2i+1)(2n−i+1)
这个公式很容易就可以得出来,但是在这里复杂度仍然很高
O
(
n
×
l
o
g
(
n
)
)
O(n×log(n))
O(n×log(n)),所以还需要化简
可以先将括号打开
1 2 n ∑ i = 0 n [ C n i ( 2 n + 1 ) + C n i ( 2 n − i + 2 i ) ] \frac{1}{2^n} ∑^n_{i=0}[C_n^i (2^n +1)+C_n^i(2^{n−i} +2^i)] 2n1∑i=0n[Cni(2n+1)+Cni(2n−i+2i)]
然后想到二项式定理
C n 0 + C n 1 + C n 2 + . . . . . . + C n n − 1 + C n n C_n^0+C_n^1+C_n^2+......+C_n^{n-1}+C_n^n Cn0+Cn1+Cn2+......+Cnn−1+Cnn
所以可以得到
∑ i = 0 n C n i = 2 n ∑^n_{i=0}C_n^i=2^n ∑i=0nCni=2n
3 n = ( 2 + 1 ) n = ∑ i = 0 n C n i 2 i 或 者 = ∑ i = 0 n C n i 2 n − i 3^n=(2+1)^n=∑^n_{i=0}C_n^i2^i或者=∑^n_{i=0}C_n^i2^{n-i} 3n=(2+1)n=∑i=0nCni2i或者=∑i=0nCni2n−i
根据上面两个公式,我们可以将原来的公式换成
1 2 n × ( 2 n × ( 2 n + 1 ) + 2 × 3 n ) \frac{1}{2^n}×(2^n×(2^n+1)+2×3^n) 2n1×(2n×(2n+1)+2×3n)
所以最终的公式就是
2 n + 1 + 2 × 3 n 2 n 2^n+1+\frac{2×3^n}{2^n} 2n+1+2n2×3n
AC Code
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define ll long long
// #define TDS_ACM_LOCAL
const int mod=998244353;
ll quick_pow(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void solve(){
ll n;
cin>>n;
cout<<((quick_pow(2,n)+1)%mod +((2*quick_pow(3,n))%mod*quick_pow(quick_pow(2,n),mod-2))%mod)%mod<<endl;
return ;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#ifdef TDS_ACM_LOCAL
freopen("D:\\VS code\\.vscode\\testall\\in.txt", "r", stdin);
freopen("D:\\VS code\\.vscode\\testall\\out.txt", "w", stdout);
#endif
int T;
cin>>T;
while(T--) solve();
return 0;
}
Set1
题目传送门
题目大意
给你一个n(保证n为奇数),在{1,2,…,n}的集合S中,每次删除当前集合中最小的元素,再随机删掉1个元素
每个元素最后被留下来的概率
思路
推公式没什么好说的,看官方题解吧
算了,我也看不太懂官方的,写一下
前
n
/
2
n/2
n/2个元素肯定是0的,因为他们会被操作一(删除最小值)的操作删除,所以只需要判断后面的部分(
n
−
i
n-i
n−i)
后面
n
−
i
n−i
n−i个数与前面
i
−
1
i−1
i−1中的
n
−
i
n−i
n−i匹配对应删除就行。(在
i
−
1
i−1
i−1中选
n
−
i
n−i
n−i个数出来,再排列)也就是
C n − i i − 1 ∗ A n − i n − i = ( i − 1 ) ! ( 2 i − n − 1 ) ! C^{i−1}_{n−i}∗A^{n−i}_{n−i} = \frac{(i−1)!}{(2i−n−1)!} Cn−ii−1∗An−in−i=(2i−n−1)!(i−1)!
然后前面剩下的 ( 2 i − n − 1 ) (2i−n−1) (2i−n−1)两两匹配,也就是
( 2 i − n − 1 ) ! ( 2 ! ) 2 i − n − 1 2 \frac{(2i−n−1)!}{(2!)^{\frac{2i-n-1}{2}}} (2!)22i−n−1(2i−n−1)!
由于最小的约束,方案可能会有重复的,也就是(1,2)(3,4)和(3,4)(1,2)方案重复,所以需要去除 ( 2 i − n − 1 2 ) ! (\frac{2i-n-1}{2})! (22i−n−1)!,最后的公式也就是
( i − 1 ) ! ( 2 ! ) 2 i − n − 1 2 ∗ ( 2 i − n − 1 2 ) ! \frac{(i−1)!}{(2!)^{\frac{2i-n-1}{2}}*(\frac{2i-n-1}{2})!} (2!)22i−n−1∗(22i−n−1)!(i−1)!
最后概率即为本身的可能数除以总的方案数
注意一下PE,最后一个元素的末尾没有空格
AC Code
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define INF 0x3f3f3f3f
#define ll long long
// #define TDS_ACM_LOCAL
const int N=5e6 +9;
const ll mod=998244353;
int n;
ll ans;
ll jc[N], inv[N], a[N], invs[N];
ll quick_pow(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
// 打表阶乘和逆元
void init(){
jc[0]=jc[1]=1;
inv[0]=inv[1]=1;
invs[0]=invs[1]=1;
for(int i=2; i<N; i++){
jc[i]=jc[i-1]*i%mod;
invs[i]=(mod-mod/i)*invs[mod%i]%mod;
inv[i]=inv[i-1]*invs[i]%mod;
}
return ;
}
void solve(){
ans=0;
cin>>n;
if(n==1) {cout<<"1"<<endl; return ;}
cout<<"0";
for(int i=2; i<=n; i++){
if(i<=n/2) {cout<<" "<<"0"; continue;}
a[i]=((jc[i-1]*quick_pow(inv[2], (2*i-n-1)/2)) %mod *inv[(2*i-n-1)/2]) %mod;
ans=(ans+a[i])%mod;
}
ans=quick_pow(ans, mod-2);
for(int i=n/2+1; i<=n; i++) cout<<" "<<a[i]*ans%mod;
cout<<endl;
return ;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#ifdef TDS_ACM_LOCAL
freopen("D:\\VS code\\.vscode\\testall\\in.txt", "r", stdin);
freopen("D:\\VS code\\.vscode\\testall\\out.txt", "w", stdout);
#endif
init();
int T;
cin>>T;
while(T--) solve();
return 0;
}