8.4 2020 Multi-University Training Contest 5题解及补题
8.4 2020 Multi-University Training Contest 5题解及补题
比赛过程
1001求逆元的题,在前面的补题中用到了一种很好的逆元方法,所以找到规律可以很快的过了。1009找到折叠纸的规律,最后求到期望就可以
题解
1001 Tetrahedron
题意
给定直角四面体的三个直角边长,求高线平方的倒数 \(\frac{1}{h^2}\)
解法
维护一下平方逆元的前缀和
代码
#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(0), cin.tie(0)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 6e6 + 5;
const int inf = ~0u >> 1;
typedef pair<int, int> P;
#define REP(i, a, n) for (int i = a; i < (n); ++i)
#define PER(i, a, n) for (int i = (n)-1; i >= a; --i)
ll sum[maxn];
ll mod = 998244353;
ll inv[maxn];
void getInv(int n){
inv[1] = 1;
for (int i = 2; i <= n; i++) inv[i] = inv[mod % i] * (mod - mod / i) % mod;
}
ll init() {
sum[1] = 1;
REP(i, 2, maxn) { sum[i] = (sum[i - 1] + inv[i] * inv[i] % mod) % mod; }
}
int main() {
int t;
scanf("%d", &t);
getInv(maxn);
init();
while (t--) {
ll n;
scanf("%lld", &n);
ll ans = sum[n] * inv[n] % mod * 3ll % mod;
printf("%lld\n", ans);
}
return 0;
}
1003 Boring Game
题意
解法
代码
//将内容替换成代码
1007 Tree
题意
在一个无根树上选择一个的子图,要求子图全联通且度数大于k 的点最多只有1个。问该子图最大的权值。
解法
树形DP,sum1[v]表示以v为根节点选k-1个子节点并且全部满足度数不超过k的最大连通块的边权和,sum2[v]就是包含一条连向父亲的节点,也就是sum1[v]+e[u][v].l
需要dfs处理,然后排序取最大k-1个子节点,然后dfs求把父节点fa当做子树到u的满足条件的最大连通块的边权和,然后把g[u]中所有的子节点的l加起来,更新max值,最后枚举子节点v,即可的出结果
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxl=2e5+10;
int n,k;
ll ans;
struct ed
{
int to;ll l;
bool operator < (const ed &b)const
{
return l>b.l;
}
};
vector<ed> e[maxl],g[maxl];
ll sum1[maxl],sum2[maxl];
inline void prework()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
e[i].clear(),g[i].clear();
int u,v,l;
for(int i=1;i<=n-1;i++)
{
scanf("%d%d%d",&u,&v,&l);
e[u].push_back(ed{v,l});
e[v].push_back(ed{u,l});
}
}
inline void dfs1(int u,int fa)
{
int v;
for(ed ee:e[u])
{
v=ee.to;
if(v==fa) continue;
dfs1(v,u);
sum2[v]=ee.l+sum1[v];
g[u].push_back(ed{v,sum2[v]});
}
sort(g[u].begin(),g[u].end());
int len=g[u].size();len=min(len,k-1);
sum1[u]=0;
for(int i=0;i<len;i++)
sum1[u]+=g[u][i].l;
}
inline void dfs2(int u,int fa,ll fmx)
{
if(fa!=0)
g[u].push_back(ed{fa,fmx});
sort(g[u].begin(),g[u].end());
int v,len=g[u].size(),up=min(len,k);
ll tmp=0,nfmx;
for(int i=0;i<len;i++)
tmp+=g[u][i].l;
ans=max(ans,tmp);
tmp=0;
for(int i=0;i<up;i++)
tmp+=g[u][i].l;
for(int i=0;i<len;i++)
{
v=g[u][i].to;
if(v==fa) continue;
if(i<up)
nfmx=tmp-sum2[v]+(sum2[v]-sum1[v]);
else
nfmx=tmp-g[u][k-1].l+(sum2[v]-sum1[v]);
dfs2(v,u,nfmx);
}
}
inline void mainwork()
{
ans=0;
if(k==0) return;
dfs1(1,0);
dfs2(1,0,0);
}
inline void print()
{
printf("%lld\n",ans);
}
int main()
{
int t;
scanf("%d",&t);
for(int i=1;i<=t;i++)
{
prework();
mainwork();
print();
}
return 0;
}
1008 Set2
题意
现在有一个1~n的排列,每次会先拿走最小的数,然后再拿走随机k个数,直到留下小于等于k个数为止,问每个数被留下的概率。
解法
本题是一个DP,用dp[i][j]表示剩下i个数的时候,当前的数是第j个的时候的概率。dp[i][j]可以从dp[i-k-1]推过来,假设先放k个数,再放一个最小的数反着推出dp[i],于是可以
写出状态转移方程,复杂度为n^2。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e3+5;
const ll mod=998244353;
ll dp[N][N],fac[N],inv[N];
ll qpow(ll a,ll b){ll ans=1;for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;return ans;}
ll c[N][N],invc[N][N];
int main()
{
fac[0]=inv[0]=1;
for(ll i=1;i<N;i++)fac[i]=fac[i-1]*i%mod;
inv[N-1]=qpow(fac[N-1],mod-2);
for(ll i=N-2;i;i--)inv[i]=inv[i+1]*(i+1)%mod;
for(int i=0;i<N;i++)
for(int j=0;j<=i;j++)
c[i][j]=fac[i]*inv[j]%mod*inv[i-j]%mod,invc[i][j]=fac[i-j]*fac[j]%mod*inv[i]%mod;
int t;
scanf("%d",&t);
while(t--){
int n,k;
scanf("%d%d",&n,&k);
int res=n%(k+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dp[i][j]=0;
for(int i=1;i<=res;i++)dp[res][i]=1;
for(int i=res+k+1;i<=n;i+=k+1)
for(int j=1;j<=i;j++)
for(int l=max(0,k-i+j);l<=k&&l<=j-2;l++)
dp[i][j]=(dp[i][j]+c[j-2][l]*c[i-j][k-l]%mod*dp[i-k-1][j-l-1]%mod*invc[i-1][k])%mod;
for(int i=1;i<=n;i++)
printf("%lld%c",dp[n][i]," \n"[i==n]);
}
return 0;
}
1009 Paperfolding
题意
给你一张纸,可以进行四种操作,向上向下向左向右对折,实际上向上向下为一种,即竖直对折;向左向右一种,即水平对折。对折完后可以进行横竖一刀切的操作,将纸张分成 \(s\) 张。
现在给定一个$ n$,问经过 \(n\)次四种操作后,期望 $E(s) \(,即最终切割后得到的纸张数\) s$的期望是多少。
解法
如果进行了 \(x\)次水平对折和$ y$次竖直对折。
此时相当于对于一张纸张进行了 \(2^x\)次水平切割,\(2^y\)次竖直切割。
最终获得的纸张数量是:\((2^x+1)*(2^y+1)\)。
代码
#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(0), cin.tie(0)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e5 + 5;
const int inf = ~0u >> 1;
typedef pair<int, int> P;
#define REP(i, a, n) for (int i = a; i < (n); ++i)
#define PER(i, a, n) for (int i = (n)-1; i >= a; --i)
ll mod = 998244353;
ll qkpow(ll a, ll p) {
ll t = 1, tt = a % mod;
while (p) {
if (p & 1) t = t * tt % mod;
tt = tt * tt % mod;
p >>= 1;
}
return t;
}
ll getInv(ll a) { return qkpow(a, mod - 2); }
int main() {
int t;
scanf("%d", &t);
while (t--) {
ll n;
scanf("%lld", &n);
ll fenzi =
((qkpow(3, n % (mod - 1)) * 2ll % mod + qkpow(2, n % (mod - 1))) %
mod +
qkpow(2, 2ll * n % (mod - 1))) %
mod;
ll fenmu = qkpow(2, n % (mod - 1));
ll ans = fenzi * getInv(fenmu) % mod;
printf("%lld\n", ans);
}
return 0;
}
1012 Set1
题意
现在有一个1~n的排列,每次会先拿走最小的数,然后再拿走随机1个数,直到留下小于等于1个数为止,问第i个数被留下的概率。
解法
考虑元素\(i\)被留下来的方案数,前面有\(i-1\)个元素,后面有\(n-i\)个元素。当且仅当\(n-i<=i-1\)的时候,i才可能被留下。
每次删除最小的元素为操作1,随即删除一个元素为操作2,大于元素i的\(n-i\)个元素一定是被操作2给删除的,\(i\)被留下来的情况,
一定是后面所有的元素\((n-i)\)个,全部被前面\(i-1\)个元素中的某个1对1的选择,那么对后\(n-i\)的元素,他们被选择的方案数是\((n-1)!C(i-1,n-i)\)。然后剩下的\((i-1)-(n-i)=(x*i-n-1)\)个元素两两删除,那么\(i\)被留下来的方案数就可以求了。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e6+10,mod=998244353;
int t,n,fac[N],finv[N],ans[N],pw[N],two;
int modpow(int x,int n,int mod){
int res=1;
for(;n;n>>=1,x=1ll*x*x%mod){
if(n&1)res=1ll*res*x%mod;
}
return res;
}
int C(int n,int m){
if(n-m<0 || n<0 || m<0)return 0;
return 1ll*fac[n]*finv[m]%mod*finv[n-m]%mod;
}
int main(){
pw[0]=fac[0]=finv[0]=1;
two=modpow(2,mod-2,mod);
for(int i=1;i<N;++i){
fac[i]=1ll*fac[i-1]*i%mod;
pw[i]=1ll*pw[i-1]*two%mod;
}
finv[N-1]=modpow(fac[N-1],mod-2,mod);
for(int i=N-2;i>=1;--i){
finv[i]=1ll*finv[i+1]*(i+1)%mod;
}
scanf("%d",&t);
while(t--){
scanf("%d",&n);
int sum=0;
for(int i=1;i<=n;++i){
if(i-1<n-i){
ans[i]=0;
continue;
}
int left=2*i-n-1,half=left/2;
ans[i]=1ll*C(i-1,n-i)*fac[n-i]%mod*fac[left]%mod*pw[half]%mod*finv[half]%mod;
sum=(sum+ans[i])%mod;
}
sum=modpow(sum,mod-2,mod);
for(int i=1;i<=n;++i){
printf("%d%c",1ll*ans[i]*sum%mod," \n"[i==n]);
}
}
return 0;
}

浙公网安备 33010602011771号