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;
}

posted @ 2020-08-16 22:38  cugbacm03  阅读(153)  评论(0)    收藏  举报