2020 Multi-University Training Contest 5(2020杭电多校训练赛第五场)

题号 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
赛中 🎈 🎈 🎈 🎈 🎈
赛后


1001 - Tetrahedron

题意

整数\(a,b,c\)相互独立地在\([1,n]\)内等概率取值,构成直角四面体的三条直角边,设\(h\)为直角顶点到斜面的欧几里得距离,求\(\frac{1}{h^2}\)的期望值。

分析

直角四面体体积\(V=\frac{1}{6}abc\),对于四面体又有\(V=\frac{1}{3}S_{斜面}h\),于是可以计算\(h\)

赛中猜了一下结果是\(a,b,c\)一直取相同值时算得\(\frac{1}{h^2}\)的平均值,然后就过了...

依据题解是要套用海伦公式或向量叉积推得结果:\(\frac{1}{h^2}=\frac{1}{a^2}+\frac{1}{b^2}+\frac{1}{c^2}\),由于\(a,b,c\)相互独立,则有\(E(\frac{1}{h^2})=3E(\frac{1}{a^2})\),因此赛中把\(a,b,c\)当成一直取相同值来算是可以得到正确结果的。

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD = 998244353;
const int maxn = 6e6 + 10;
LL qpow(LL a, LL b)
{
    LL res = 1;
    a %= MOD;
    while(b)
    {
        if(b & 1)
            res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}
int n;
LL res[maxn];
void preprocess()
{
    LL ans = 0;
    for(int i = 1; i < maxn; i++)
    {
        ans = (ans + 3 * qpow((long long)i * i % MOD, MOD - 2) % MOD) % MOD;
        res[i] = ans * qpow(i, MOD - 2) % MOD;
    }
}
int main()
{
    preprocess();
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        printf("%lld\n", res[n]);
    }
    return 0;
}

1002 - Funny String

题意
分析
代码

1003 - Boring Game

题意

将一摞纸(\(n\)张)从左向右折叠\(k\)次,然后从上向下对每一面编号,要求输出依次最后每张纸上每一面的编号。(\(1\le n\le 200,1\le k\le 10,\sum 2\times n\times 2^k\le 10^6\)

分析

用栈模拟过程即可,一开始,将一摞未折叠的纸每张每面的要填标号的的\(2\times n\times 2^k\)个位置从上向下依次压入对应的\(2^k\)个栈中,每次折叠,将左半边的栈中内容弹出压入右半边的对应栈中,\(k\)次后只剩下一个栈,依次弹出将编号赋给对应位置。

时间复杂度\(O(nk2^k)\)

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD = 998244353;
const int maxn = 1e6 + 10;
int n, k, m;
int t[20], a[maxn], b[maxn];
void preprocess()
{
    t[0] = 1;
    for(int i = 1; i <= 10; i++)
        t[i] = t[i-1] * 2;
}
stack<int> s[2000];
int main()
{
    preprocess();
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d %d", &n, &k);
        m = 2 * n * t[k];
        for(int i = 1; i <= m; i++)
            scanf("%d", &a[i]);
        for(int i = 2 * n - 1; i >= 0; i--)
            for(int j = 0; j < t[k]; j++)
                s[j+1].push(i * t[k] + j);
        for(int i = 1, mid; i < t[k]; i = mid + 1)
        {
            mid = (i + t[k]) / 2;
            for(int j = 1; j <= mid - i + 1; j++)
            {
                while(!s[i+j-1].empty())
                {
                    s[t[k]-j+1].push(s[i+j-1].top());
                    s[i+j-1].pop();
                }
            }
        }
        int cur = 0;
        while(!s[t[k]].empty())
        {
            b[s[t[k]].top()] = a[++cur];
            s[t[k]].pop();
        }
        for(int i = 0; i < m; i++)
        {
            if(i > 0)
                printf(" ");
            printf("%d", b[i]);
        }
        printf("\n");
    }
    return 0;
}

1004 - Expression

题意
分析
代码

1005 - Array Repairing

题意
分析
代码d

1006 - Alice and Bob

题意
分析
代码

1007 - Tree

题意

一棵带边权树,要从中选出一个连通子图,至多只能有一个节点的度大于 k,要求选出的连通子图边权和最大。

分析

先转为有根树,一遍 dp 出每个节点选 k - 1 个儿子的最大答案,这个显然要贪心,将儿子的答案排序选前 k - 1 个即可。枚举一个点,它能选全部的儿子,这显然可以换根,并在换根的时候统计该点作为根的答案,不用 set 是不会卡常的,换根的时候要重新维护 dp。

比赛的时候因为没有特判 k = 0 的情况,以及每个点选 k - 1 个儿子写成了选 k 个,硬调了1个小时。

代码
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define fir first
#define sec second
typedef long long ll;
const int maxn = 2e5 + 10;
struct node {
    int v;
    ll val;
    node() {}
    node(int vi,ll tv) {
        v = vi;
        val = tv;
    }
    bool operator < (const node &rhs) const {
        if (val == rhs.val) return v < rhs.v;
        return val > rhs.val;
    }
};
ll dp[maxn], ans, res[maxn];
vector<node> h[maxn];
vector<pii> g[maxn];
int q, n, k, vis[maxn];
void dfs1(int u,int fa) {
    dp[u] = 0;
    for (auto it : g[u]) {
        if (it.fir == fa) continue;
        dfs1(it.fir,u);
        h[u].push_back(node(it.fir,it.sec + dp[it.fir]));
    }
    sort(h[u].begin(), h[u].end());
    for (int i = 0; i < h[u].size() && i < k - 1; i++)
        dp[u] = dp[u] + h[u][i].val;
}
void dfs2(int u,int fa) {
    vis[u] = res[u] = dp[u] = 0;
    int tot = h[u].size(), sz = 0;
    sort(h[u].begin(),h[u].end());
    for (int i = 0; i < h[u].size() && i < k - 1; i++) {
        vis[h[u][i].v] = 1;
        dp[u] = dp[u] + h[u][i].val;
    }
    for (auto it : h[u])
        res[u] += it.val;
    ans = max(ans,res[u]);
    for (auto it : g[u]) {
        if (it.fir == fa) continue;
        if (tot < k) {
            ll tv = dp[u] - dp[it.fir] - it.sec;
            h[it.fir].push_back(node(u,it.sec + tv));
        } else {
            ll tv = dp[u];
            if (vis[it.fir] == 1) {
                tv -= (dp[it.fir] + it.sec);
                tv += h[u][k - 1].val;
            }
            h[it.fir].push_back(node(u,it.sec + tv));
        }
        dfs2(it.fir,u);
    }
}
int main() {
    scanf("%d",&q);
    while (q--) {
        scanf("%d%d",&n,&k);
        ans = 0;
        for (int i = 1; i <= n; i++)
            vis[i] = res[i] = 0, g[i].clear(), h[i].clear();
        for (int i = 1; i < n; i++) {
            int u, v, w; scanf("%d%d%d",&u,&v,&w);
            g[u].push_back(pii(v,w));
            g[v].push_back(pii(u,w));
        }
        if (k == 0) {
            puts("0"); continue;
        }
        dfs1(1,0); dfs2(1,0);
        printf("%lld\n",ans);
    }
    return 0;
}

1008 - Set2

题意

set1 的 hard 版本,每次可以删 k 个数字。

分析

这题感觉思维细节比较多,要一步一步理清思路。

首先最后只会剩下 \(n \% (k + 1)\) 个数字,令 \(r = n \% (k + 1)\),如果 \(r = 0\) ,则所有数字留下的概率都为 0,如果 \(n \leq k\),所有数字留下的概率都为 \(1\)

考虑这 \(r\) 个数字的最小值,如果这 \(r\) 个数字的最小值是 \(i\),那么前 \(i - 1\) 个数字一定要删除,后 \(n - i\) 个数字种还要删除 \(n - i - r + 1\) 个,并且只能通过第二种操作来删除(如果后面还有数字是被第一种操作删除的,那么 \(i\) 是不可能留下的)。

考虑如何求得 前 \(i - 1\) 个数字被删除,第二种操作 还剩 \(n - i - r + 1\) 个的方案数:
\(dp[i][j]\) 表示前 \(i\) 个数字均被删除,还剩 \(j\) 次第二种操作的方案数,转移通过枚举第 i 个数字是被第一种操作删除还是第二种操作删除,有:
\(i + 1\) 被第一种操作删除,则 \(dp[i + 1][j + k] += dp[i][j]\)
\(i + 1\) 被第二种操作删除,则 \(dp[i + 1][j - 1] += j * dp[i][j]\)

对于每个数字 \(x\) ,枚举最后留下的集合中的最小值 \(i\)
\(i < x\)\(x\) 留下有 \(dp[i][n - i - r + 1] * C(n - i - 1,n - i - r + 1) * (n - i - r + 1)!\) 种方案
\(x = i\)\(x\) 留下有 \(dp[i][n - i - r + 1] * C(n - i,n - i - r + 1) * (n - i - r + 1)!\) 种方案

将每个值留下的方案数加起来作为总方案数,每个值的方案数除总方案数就得到每个值留下的概率


另一种思路借鉴于 njust "跃迁引擎" 的一位 dalao:

代码
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
const int maxn = 5e3 + 5;
typedef long long ll;
int t, n, k;
ll dp[maxn][maxn], cnt[maxn], sum[maxn], f[maxn], fac[maxn], comb[maxn][maxn], ans[maxn];
//dp[i][j] 表示前 i 个已经被删除,第二种操作还要做 j 次的方案数 
ll fpow(ll a,ll b) {
	ll r = 1;
	while (b) {
		if (b & 1) r = 1ll * r * a % mod;
		b >>= 1;
		a = 1ll * a * a % mod;
	}
	return r;
}
int main() {
	fac[0] = 1;
	for (int i = 1; i <= 5000; i++)
		fac[i] = fac[i - 1] * i % mod;
	comb[0][0] = 1;
	for (int i = 1; i <= 5000; i++) {
		comb[i][0] = 1;
		for (int j = 1; j <= i; j++)
			comb[i][j] = (comb[i - 1][j] + comb[i - 1][j - 1]) % mod;
	}
	scanf("%d",&t);
	while (t--) {
		scanf("%d%d",&n,&k);
		int r = n % (k + 1);
		
		dp[0][0] = 1;
		for (int i = 1; i <= n; i++)
			for (int j = 0; j <= n; j++)
				dp[i][j] = 0;
		for (int i = 0; i <= n; i++)		
			ans[i] = f[i] = sum[i] = cnt[i] = 0;
			
		if (r == 0) {
			for (int i = 1; i <= n; i++)
				printf("0%s",i == n ? "\n" : " ");
			continue;
		} else if (n <= k) {
			for (int i = 1; i <= n; i++)
				printf("1%s",i == n ? "\n" : " ");
			continue;
		} else {
			for (int i = 0; i <= n; i++) {
				for (int j = 0; j <= n; j++) {
					if (j + k <= n - i - 1)
						dp[i + 1][j + k] = (dp[i + 1][j + k] + dp[i][j]) % mod;
					if (j > 0)
						dp[i + 1][j - 1] = (dp[i + 1][j - 1] + 1ll * j * dp[i][j] % mod) % mod;
				}
			}
			for (int i = 1; i <= n; i++) {
				if (n - i + 1 >= r)
					f[i] = 1ll * dp[i - 1][n - i - r + 1] * comb[n - i][n - i - r + 1] % mod * fac[n - i - r + 1] % mod;
				else
					f[i] = 0;
			}
			for (int i = 1; i < n; i++) {
				if (n - i + 1 >= r) {
					cnt[i] = 1ll * dp[i - 1][n - i - r + 1] * comb[n - i - 1][n - i - r + 1] % mod * fac[n - i - r + 1] % mod;
				} else {
					cnt[i] = 0;
				}
				sum[i] = (sum[i - 1] + cnt[i]) % mod;
			}
			ll Sum = 0;
			for (int i = 1; i <= n; i++)
				ans[i] = (f[i] + sum[i - 1]) % mod, Sum = (Sum + f[i]) % mod;
			ll inv = fpow(Sum,mod - 2);
			for (int i = 1; i <= n; i++)
				printf("%lld%s",1ll * ans[i] * inv % mod,i == n ? "\n" : " ");
		}
	}
	return 0;
}

1009 - Paperfolding

题意

对一张纸,随机产生 n 个操作,操作有四种类型:从左往右折,从右往左折,从上往下折,从下往上折,对最后的纸横竖各切一刀,求纸的期望数量。

分析

拿个纸折一下会发现,横向折会影响竖刀口的数量,竖着折会影响横刀口的数量,若横刀口为 x,竖刀口为 y,纸显然为 \((2^{x} + 1) * (2^y + 1)\) 张,再观察一下可以发现折纸的顺序,不影响对刀口数量的影响。
所有操作的可能数为 \(4^n\),考虑枚举 n 个操作有几个横折操作,设有 \(x\) 个,这种方案的概率为:\(\frac{C(n,x)*2^x*2^{n-x}}{4^n}\),贡献为 \((2^{x} + 1) * (2^{n-x} + 1)\),拆开得到 \(2^n + 1 + 2^x + 2^{n-x}\),后面两项求和就是 \(2*3^n\),因此答案就是 \(2^n + 1 + \frac{2 * 3^n}{2^n}\)

代码
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
const int maxn = 5e5 + 10;
typedef long long ll;
int t;
ll fpow(ll a,ll b) {
    ll r = 1;
    while (b) {
        if (b & 1) r = r * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return r;
}
int main() {
    scanf("%d",&t);
    while (t--) {
        ll n;
        scanf("%lld",&n);
        ll t1 = 2 * fpow(3,n) % mod, t2 = fpow(2,n);
        ll res = t1 * fpow(t2,mod - 2) % mod;
        printf("%lld\n",(res + t2 + 1) % mod);
    }
    return 0;
}

1010 - Function

题意
分析
代码

1011 - Exam

题意
分析
代码

1012 - Set1

题意

有一个集合有 n 个数字 {1...n},n为奇数,每次删掉最小值,然后再随机删掉一个值,求每个数字最后留下的概率。

分析

观察一下发现小于 \(\frac{n}{2}\) 的数字一定无法留下,而大于 \(\frac{n}{2}\) 的数字要想留下,那么它的左边的数作为最小值时,随机删的数要带上它右边的数,若左边的数比右边的多,剩下的数两两匹配即可。
具体来说,对于数字 i,比它小的数字有 \(i - 1\) 个,比它大的数字有 \(n - i\) 个,若 \(i - 1 < n - i\) ,i 是无法留下的,反之要从 \(i - 1\) 个数中选出 \(n - i\) 个数和比它大的 \(n - i\) 个数进行匹配,匹配的方案数为 \((n - i)!\),对于左边剩下的数,可以两两任意匹配,这个可以通过 dp 来预处理,由于剩下的数一定是偶数,只需要处理偶数情况,通过枚举哪个数和第 n 个数匹配进行转移,转移方程为:\(dp[n] = (n - 2) * dp[n - 2]\)。由于删数的过程是每次先删最小值,匹配方案一经确定,那么删数的序列是确定的。
那么可以得出 \(i\) 要留下有 \(C(i - 1,n - i) * (n - i)! * dp[i - 1 - n + i]\) 种方案,把所有的方案加起来,就可以求出每个数留下的概率。

代码
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
const int maxn = 5e6 + 10;
const int N = 5e6;
typedef long long ll;
ll fac[maxn], ifac[maxn], dp[maxn], ans[maxn];
int t, n;
ll fpow(ll a,ll b) {
    ll r = 1;
    while (b) {
        if (b & 1) r = r * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return r;
}
ll C(int x,int y) {
    if (x < y || y < 0) return 0;
    return fac[x] * ifac[y] % mod * ifac[x - y] % mod;
}
int main() {
    fac[0] = 1;
    for (int i = 1; i <= N; i++)
        fac[i] = fac[i - 1] * i % mod;
    ifac[N] = fpow(fac[N],mod - 2);
    for (int i = N - 1; i >= 0; i--)
        ifac[i] = ifac[i + 1] * (i + 1) % mod;
    
    dp[0] = 1;
    for (int i = 2; i <= N; i += 2) {
        dp[i] = (i - 1) * dp[i - 2] % mod;
    }    
    scanf("%d",&t);
    while (t--) {
        ll n, sum = 0;
        scanf("%d",&n); 
        for (int i = 1; i <= n; i++) {
            ans[i] = C(i - 1,n - i) * fac[n - i] % mod * dp[i - 1 - n + i] % mod;
            sum = (sum + ans[i]) % mod;
        }
        ll inv = fpow(sum,mod - 2);
        for (int i = 1; i <= n; i++) {
            //printf("%lld %lld\n",i,ans[i]);
            printf("%lld%s",ans[i] * inv % mod,i == n ? "\n" : " ");
        }
    }
    return 0;
}

1013 - An Easy Matrix Problem

题意
分析
代码
posted @ 2020-08-06 09:27  ncu_supernova  阅读(228)  评论(0)    收藏  举报