1001
思路:
先来个链接:(www.cnblogs.com/czc1999/p/11682068.html)
清晰的、好的资源实在不好找。
回到题目,一般来说,面积体积问题用向量的叉积要好的多;并且,面积体积往往是固定的,因此,依托面积体积往往能较为容易的建立等式。
设底面▲ABC与顶点S的距离为h,底面三角形的面积为M,则四面体体积为(1/3)Mh;而三个棱边两两垂直,也能很好的算出体积(事实上,设三棱边长分别为a、b、c,则体积为 (1/3)(1/2)abc )。两体积相等即可。
另外,上述计算中,并没有算出底面三角形面积。而三角形面积等于任意两边的叉积,又三角形任意一边的向量表示可以用三棱锥的某两条棱边的差来表示,计算出底面面积后带入即可。
还有一个前缀问题,这个之前选修课的那本书里有一道题涉及了,题目是PTA甲级1046。
通过代码:
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 6e6 + 5;
const ll MOD = 998244353;
const ll INF = ~0LLU>>1;
int T, n;
vector<int> inv, sum;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
inv.resize(N);
sum.resize(N);
inv[1] = 1;
for (int i = 2; i < N; ++i)
{//MOD是质数,线性递推求n个逆元,证明见链接
inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
}
for (int i = 1; i < N; ++i)
{//前缀和
sum[i] = (sum[i - 1] + (ll)inv[i] * inv[i] % MOD) % MOD;
}
cin >> T;
while (T--)
{
cin >> n;
ll ans = 3 * (ll)sum[n] * inv[n] % MOD;
cout << ans << '\n';
}
return 0;
}
1009
思路:
还是先来个链接,与上一个链接属于同一系列的文章:(www.cnblogs.com/czc1999/p/11681996.html)
可以想象,竖方向不折,横方向上每折一次,若从中间切开,两边缘上各加1,所以是加2;而折痕附近部分只加1;另外,除了第一次对折,之后的每一次对折都会带来2的指数倍的折痕,这些新带来的折痕都会贡献一个小块。
所以,如果对折n次,则有小块数=1 + 2^(n-1) + 2,其中,第一项为中央折痕,第三项为边缘的两折痕,第二项为第一次对折之后新的折痕数(每个新折痕贡献1个小块)。
化简一下,结果为2^n+1。但是,刚刚讨论的只是横方向上的对折,我们假设的是竖方向不做任何操作。如果我们在竖方向上做出相同的操作,即可得到最终小块的数量。
设横方向、竖方向各折叠a、b次,则小块数量=(2^a+1)(2^b+1)。
接下来用到了个费马小定理,那个链接系列里也有详细的介绍。
代码:
#include<iostream>
using namespace std;
typedef long long ll;
const ll MOD = 998244353;
int T;
ll n;
ll Pow(ll x, ll n)
{
ll ret = 1;
while (n)
{
if (n & 1) ret = ((ret % MOD) * (x % MOD)) % MOD;
x = ((x % MOD) * (x % MOD)) % MOD;
n >>= 1;
}
return ret;
}
int main()
{
cin >> T;
while (T--)
{
int ans;
cin >> n;
ll t = 3 * (ll)Pow(2, MOD - 2) % MOD;//费马小定理
ans = (1 + Pow(2, n) + 2 * Pow(t, n) % MOD) % MOD;
cout << ans << '\n';
}
return 0;
}
1012
思路:
涉及到了阶乘递推求逆元,也可以在前面贴出的链接中找到详细的解释。
对于序列[1~n]中的一个数i,如果想要该数在最后被保留下来,则需要保证在前面任意次删除中,i始终不为序列的最小值。
所以,i前有 i-1 个元素(A),i后有 n-i 个元素(B),一定有|A|>=|B|,我们假设最开始删A中的一个元素时对应地删除B中的一个元素,直到B被删完为止。这可以看作是找A、B中两两配对的总数。然后,再在A中两两删除元素。把两个相加就等于元素i被留下的总情况数。
最后,将i被留下的总情况数乘以所有情况数的逆元即可。
通过代码:
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 5e6 + 5;
const int MOD = 998244353;
int T, n;
vector<int> fac, inv, ans;
int Pow(int x, int n)
{
int ret = 1;
while (n)
{
if (n & 1)ret = (ll)ret * x % MOD;
x = (ll)x * x % MOD;
n >>= 1;
}
return ret;
}
int comb(int n, int m)
{
return (ll)fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
int func(int i)
{
int a = i - 1, b = n - i;
int ret = comb(a, b);
ret = (ll)ret * fac[b] % MOD;
if (a > b)
{
int cc = Pow(2, MOD - 2);//费马小定理
cc = Pow(cc, (a - b) / 2);
ret = (ll)ret * cc % MOD * fac[a - b] % MOD;
ret = (ll)ret * inv[(a - b) / 2] % MOD;
}
return ret;
}
int main()
{
fac.resize(N);
inv.resize(N);
fac[0] = 1;
inv[0] = 1;
for (int i = 1; i < N; ++i)
{//阶乘预处理
fac[i] = (ll)fac[i - 1] * i % MOD;
}
inv[N - 1] = Pow(fac[N - 1], MOD - 2);
for (int i = N - 2; i >= 1; --i)
{//阶乘递推求逆元,具体详见那个链接
inv[i] = (ll)inv[i + 1] * (i + 1) % MOD;
}
cin >> T;
while (T--)
{
cin >> n;
ans.resize(n + 1);
ll sum = 0;
for (int i = (n + 1) / 2; i <= n; ++i)
{
ans[i] = func(i);
sum = (ans[i] + sum) % MOD;
}
for (int i = 1; i <= n; ++i)
{
ans[i] = (ll)ans[i] * Pow(sum, MOD - 2) % MOD;//MOD>5e6+5,sum不能直接查找
}
for (int i = 1; i <= n; ++i)
{
cout << ans[i];
if (i == n) cout << '\n';
else cout << ' ';
}
}
return 0;
}