2020 Multi-University Training Contest 6(2020杭电多校训练赛第六场)
| 题号 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 赛中 | 🎈 | 🎈 | 💭 | 🎈 | 🎈 | 🎈 | 🎈 | ||||
| 赛后 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
1001 - Road To The 3rd Building
题意
给出一个数列\(s_1,s_2,\cdots,s_n\),有序对\((i,j)\)(\(1\le i \le j \le n\))的值为\(\frac{1}{j-i+1}\sum_{k=i}^j s_k\)(即\(s_i,s_{i+1},\cdots,s_j\)的平均值),在所有有序对中等概率随机取一个,问其值的数学期望为多少?(\(1\le n \le 2\times 10^5,1\le s_i \le 10^9, \sum n \le 10^6\))
分析
为方便计算平均值,先预处理出\(s[i]\)的前缀和\(\text{sum}[i]\);
一共有\(\binom{n}{2}+n\)个有序对,等概率取值,考虑枚举段长求和,则数学期望为:
发现对\(j\)求和那段\(\text{sum}[]\)的取值是连续的,故再处理出\(\text{sum}[i]\)的前缀和\(\text{pre}[i]\),进一步化简上式得:
可以\(O(n)\)预处理出所有逆元,最终时间复杂度\(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int maxn = 2e5 + 10;
int n, s[maxn];
LL sum[maxn], pre[maxn];
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;
}
LL inv[maxn];
void get_inv()
{
inv[1] = 1;
for(int i = 2; i <= 200000; i++)
inv[i] = (MOD - MOD / i) * inv[MOD%i] % MOD;
}
int main()
{
get_inv();
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
sum[0] = pre[0] = 0;
for(int i = 1; i <= n; i++)
{
scanf("%d", &s[i]);
sum[i] = (sum[i-1] + s[i]) % MOD;
pre[i] = (pre[i-1] + sum[i]) % MOD;
}
LL ans = 0;
for(int i = 1; i <= n; i++)
{
LL tot = 0;
tot = (tot + pre[n] - pre[i-1]) % MOD;
tot = (tot - pre[n-i]) % MOD;
ans = (ans + tot * inv[i] % MOD) % MOD;
}
ans = (ans % MOD + MOD) % MOD;
ans = ans * qpow(1LL * n * (n - 1) % MOD * qpow(2, MOD - 2) % MOD + n, MOD - 2) % MOD;
printf("%lld\n", ans);
}
return 0;
}
1002 - Little Rabbit's Equation
题意
给出一个简单等式,判断进制。
分析
直接模拟即可。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const int maxn = 5e5 + 10;
char s[maxn], op;
vector<int> a, b, c;
void preprocess()
{
a.clear();
b.clear();
c.clear();
int i = 0;
while(true)
{
if(isdigit(s[i]))
a.push_back(s[i++] - '0');
else if(isupper(s[i]))
a.push_back(s[i++] - 'A' + 10);
else
break;
}
op = s[i++];
while(true)
{
if(isdigit(s[i]))
b.push_back(s[i++] - '0');
else if(isupper(s[i]))
b.push_back(s[i++] - 'A' + 10);
else
break;
}
i++;
while(true)
{
if(isdigit(s[i]))
c.push_back(s[i++] - '0');
else if(isupper(s[i]))
c.push_back(s[i++] - 'A' + 10);
else
break;
}
}
LL change(vector<int> &v, int r)
{
LL res = 0;
for(auto x : v)
{
if(x >= r)
return -1;
res = res * r + x;
}
return res;
}
int main()
{
while(~scanf("%s", s))
{
preprocess();
int ans = -1;
for(int r = 2; r <= 16; r++)
{
LL A = change(a, r), B = change(b, r), C = change(c, r);
if(A == -1 || B == -1 || C == -1)
continue;
if(op == '+' && A + B == C)
{
ans = r;
break;
}
if(op == '-' && A - B == C)
{
ans = r;
break;
}
if(op == '*' && A * B == C)
{
ans = r;
break;
}
if(op == '/' && A % B == 0 && A / B == C)
{
ans = r;
break;
}
}
printf("%d\n", ans);
}
return 0;
}
1003 - Borrow
题意
分析
代码
1004 - Asteroid in Love
题意
分析
代码
1005 - Fragrant numbers
题意
给出一个由\(1145141919\)循环组成的无限长字符串\(S\),取\(S\)的前缀字符串\(T\),允许任意位置插入 ‘\((\)’ , ‘\()\) ’, ‘\(+\)’ 和 ‘\(*\)’,得到合法表达式\(T'\),问可以令\(val(T')=N\)的最短前缀字符串\(T\)的长度为多少?(\(1\le T\le 30,1\le N \le 5000\))
分析
队友猜测取前两个周期就行,但是并没有去尝试... 结果正解就是打表...
考虑区间dp,设\(dp[l][r][val]\) 为 \(s_l, s_{l+1}, \cdots, s_r\) 能否表示出\(val\),只搜前两个周期的话时间复杂度为\(O(20^3 N^2)\),实测本地\(17s\)跑完。
发现前两个周期内只有\(3\)和\(7\)无法表示出,显然再往后搜这两个数也不可能被表示,以下为打表代码。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const int MOD = 998244353;
const int maxn = 5000 + 10;
int a[] = {0, 1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 1, 1, 4, 5, 1, 4, 1, 9, 1, 9};
int ans[maxn];
bool dp[25][25][5005];
int main()
{
for(int i = 1; i <= 20; i++)
dp[i][i][a[i]] = true;
for(int len = 2; len <= 20; len++)
{
for(int l = 1; l <= 20 - len + 1; l++)
{
int r = l + len - 1;
int t = 0;
for(int i = l; i <= r; i++)
{
t = t * 10 + a[i];
if(t > 5000)
break;
}
if(t <= 5000)
dp[l][r][t] = true;
for(int mid = l; mid <= r - 1; mid++)
{
for(int i = 1; i <= 5000; i++)
{
if(!dp[l][mid][i])
continue;
for(int j = 1; j <= 5000; j++)
{
if(!dp[mid+1][r][j])
continue;
if(i + j <= 5000)
dp[l][r][i+j] = true;
if(i * j <= 5000)
dp[l][r][i*j] = true;
}
}
}
}
}
memset(ans, -1, sizeof(ans));
printf("Numbers can't be constructed in 2 periods: ");
for(int i = 1; i <= 5000; i++)
{
bool flag = false;
for(int r = 1; r <= 20; r++)
{
if(dp[1][r][i])
{
ans[i] = r;
flag = true;
break;
}
}
if(!flag)
printf("%d ", i);
}
freopen("output.txt", "w", stdout);
printf("int ans[] = {0, ");
for(int i = 1; i <= 5000; i++)
{
if(i > 1 && (i - 1) % 50 == 0)
printf("\n");
printf("%d", ans[i]);
if(i < 5000)
printf(", ");
}
printf("};");
return 0;
}
1006 - A Very Easy Graph Problem
题意
有一个带边权的无向图,其中第 i 条边的边权为 \(2^i\),点有点权,点权为 {0,1},求 \(\displaystyle\sum_{i = 1}^n\sum_{j = 1}^nd(i,j)[a[i] == 1 ,a[j] == 0]\),其中 \(d(i,j)\) 表示 \(i\) 和 \(j\) 的最短距离。
分析
\(n,m\) 都特别大,肯定不能去跑多源最短路,观察到边权的特殊性,前 \(i - 1\) 条边的边权之和小于第 \(i\) 条边,那么第 \(i\) 条边可能产生贡献,当且仅当前 \(i - 1\) 条边都考虑之后,第 \(i\) 条边连接的是两个不同的连通分量,注意到这和最小生成树一致,即第 \(i\) 条边如果能作为最短路径上的边,仅当它是最小生成树上的边。
按kruscal算法将最小生成树建出来,然后计算每条边的贡献可以用换根 dp,或者朴素计算每条边左右两边的 0,1点的个数。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int maxn = 2e5 + 10;
#define pii pair<int,int>
#define fir first
#define sec second
int t, n, m, a[maxn] ,p[maxn], son[maxn];
int find(int x) {
return p[x] == x ? x : p[x] = find(p[x]);
}
vector<pii> g[maxn];
ll dp[maxn], pw[maxn];
void dfs1(int u,int fa) {
dp[u] = 0; son[u] = (a[u] == 0);
for (auto it : g[u]) {
if (it.fir == fa) continue;
dfs1(it.fir,u);
son[u] += son[it.fir];
dp[u] = (dp[u] + dp[it.fir] + 1ll * it.sec * son[it.fir] % mod) % mod;
}
}
void dfs2(int u,int fa) {
for (auto it : g[u]) {
if (it.fir == fa) continue;
int num = son[u] - son[it.fir];
ll tv = dp[u] - 1ll * it.sec * son[it.fir] % mod - dp[it.fir];
tv = (tv % mod + mod) % mod;
son[it.fir] += num;
dp[it.fir] = (dp[it.fir] + 1ll * num * it.sec % mod + tv) % mod;
dfs2(it.fir,u);
}
}
int main() {
pw[0] = 1;
for (int i = 1; i <= 200000; i++)
pw[i] = 1ll * pw[i - 1] * 2 % mod;
scanf("%d",&t);
while (t--) {
scanf("%d%d",&n,&m);
for (int i = 1; i <= n; i++)
scanf("%d",&a[i]);
for (int i = 1; i <= n; i++)
p[i] = i, g[i].clear();
for (int i = 1; i <= m; i++) {
int u, v; scanf("%d%d",&u,&v);
int fx = find(u), fy = find(v);
if (fx != fy) {
g[u].push_back(pii(v,pw[i]));
g[v].push_back(pii(u,pw[i]));
p[fx] = fy;
}
}
ll ans = 0;
dfs1(1,0); dfs2(1,0);
for (int i = 1; i <= n; i++) {
if (a[i] == 1) {
ans = (ans + dp[i]) % mod;
}
}
printf("%lld\n",ans);
}
return 0;
}
1007 - A Very Easy Math Problem
题意

一句话题意:计算那个式子的答案
分析
莫比乌斯反演
通过枚举 \(d = gcd\),式子可以化成:
后面那一串看起来很吓人,但其实就是 \(\displaystyle(\sum_{i = 1}^ni^k)^x\) ,可以化简成:
由于 \(n\) 其实只有 \(2e5\),很多东西可以暴力预处理,比如最后一项和式,以及前两项和式的前缀和,然后就有了一个 \(O(T*n^{\frac{3}{4}})\) 的做法,但是由于常数较大,被卡掉了(如果不是看到有人跑了 2200ms,我也不会冲的),式子还要进一步化简:
其中 \(g(T) = \displaystyle(\sum_{i = 1}^Ti^k)^x\)
令 \(f(T) = \displaystyle\sum_{d|T}d^{kx+1}*f(d)*\mu(\frac{T}{d})*(\frac{T}{d})^{kx}\)
由于 \(n\) 比较小, \(f(T)\) 可以在 \(n\log n\) 时间内预处理,并可以\(O(n)\) 预处理 \(f(T)\) 的前缀和 \(h(T)\)
而 \(g(\lfloor\frac{n}{T}\rfloor)\) 可以分块,单组询问复杂度为 \(O(\sqrt{n})\)
总的复杂度为 \(O(n \log n+ T\sqrt{n})\),由于不知名原因代码出了BUG,交的是 \(O(n\sqrt n+T\sqrt n)\) 的
(其实式子并不难推,两三步就可以推完,但是比赛的时候推得特别缓慢,每一步都要反复检查是否等价,怕错~)
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
const int N = 2e5;
const int mod = 1e9 + 7;
typedef long long ll;
int t, k, x, n;
int f[maxn], pw1[maxn], pw2[maxn],pw3[maxn],sum1[maxn],sum2[maxn],sum3[maxn];
int h[maxn],h1[maxn],h2[maxn], ans[maxn], g[maxn], sum[maxn];
int ispri[maxn], pri[maxn], mu[maxn];
inline int add(int x, int y) {
x += y;
if (x >= mod)
x -= mod;
return x;
}
inline int sub(int x, int y) {
x -= y;
if (x < 0)
x += mod;
return x;
}
inline int mul(int x, int y) {
return (long long) x * y % mod;
}
int fpow(int a,ll b) {
int r = 1;
while (b) {
if (b & 1) r = mul(r,a);
b >>= 1;
a = mul(a,a);
}
return r;
}
void sieve() {
ispri[0] = ispri[1] = 1; mu[1] = 1;
for (int i = 2; i <= N; i++) {
if (!ispri[i]) pri[++pri[0]] = i, mu[i] = -1;
for (int j = 1; j <= pri[0] && i * pri[j] <= N; j++) {
ispri[i * pri[j]] = 1;
if (i % pri[j] == 0) {
mu[i * pri[j]] = 0;
break;
} else {
mu[i * pri[j]] = -1 * mu[i];
}
}
}
}
void init() {
pw1[1] = pw2[1] = pw3[1] = 1;
for (int i = 1; i <= N; i++) {
pw2[i] = fpow(i,1ll * k * x);
pw1[i] = mul(pw2[i], i);
pw3[i] = fpow(i,k);
if (mu[i] == 0) f[i] = 0;
else f[i] = 1;
h1[i] = mul(pw1[i],f[i]);
h2[i] = mul(pw2[i],mu[i]);
if (h1[i] < mod) h1[i] += mod;
if (h2[i] < mod) h2[i] += mod;
sum3[i] = add(sum3[i - 1],pw3[i]);
}
}
int main() {
scanf("%d%d%d",&t,&k,&x);
sieve(); init();
for (int i = 1; i <= N; i++) {
g[i] = fpow(sum3[i],x); h[i] = 0;
for (int j = 1; j * j <= i; j++) {
if (i % j == 0) {
h[i] = (h[i] + 1ll * h1[j] * h2[i / j] % mod) % mod;
//h[i] = add(h[i],mul(h1[j],h2[i / j]));
if (i / j != j) {
//h[i] = add(h[i],mul(h1[i / j],h2[j]));
h[i] = (h[i] + 1ll * h1[i / j] * h2[j] % mod) % mod;
}
}
}
sum[i] = (sum[i - 1] + h[i]) % mod;
//sum[i] = add(sum[i - 1],h[i]);
}
while (t--) {
scanf("%d",&n);
int ans = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
ans = (ans + 1ll * (sum[r] - sum[l - 1] + mod) % mod * g[n / l] % mod) % mod;
//ans = add(ans,mul(sub(sum[r],sum[l - 1]),g[n / l]));
}
printf("%d\n",ans);
}
return 0;
}
1008 - Yukikaze and Smooth numbers
题意
分析
代码
1009 - Divisibility
题意
给定 b , x,判断命题:任意一个 b 进制数,如果它是 x 的倍数,那么对它反复进行数位求和,最后的结果也是 \(x\) 的倍数。
分析
结论是 \(b \% x = 1\),命题成立,否则不成立,但在比赛中并没有想到这个结论。
实际在比赛中,设法去证明一个 \(x\) 的倍数,对它反复进行数位求和,最后这个和应等于 \(x\),因为 \(x\) 是最小单位了。
因此若 \(b <= x\) 一定是不可行的,而 \(b > x\) 的情况,x 的倍数有 \(x, 2*x, 3*x,...,k*x\),设 \(i * x >= b\),如果 \(i * x\) 反复求数位和后等于 \(x\),那么显然 \((i + 1) * x = 2 * x\) ,这是一个循环,当满足这个条件时命题成立,否则不成立。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
ll solve(ll x,ll v) { //把 x 转成 v 进制
ll cnt = 0;
while (x) {
cnt += x % v;
x /= v;
}
return cnt;
}
int main() {
scanf("%d",&t);
while (t--) {
ll b , x; scanf("%lld%lld",&b,&x);
ll v = b / x + (b % x > 0);
ll ans = solve(v * x,b);
while (ans >= b)
ans = solve(ans,b);
if (ans != x) puts("F");
else puts("T");
}
return 0;
}
1010 - Expectation
题意
给出一张\(n\)个点\(m\)条带权边的无向图(可能有重边,但保证无自环),定义生成树的权值为其所有边的边权按位与,问等概率随机取该无向图中的一棵生成树,其权值的数学期望为多少?(\(1\le T\le 10,2\le n \le 100, 1 \le m \le 10^4, 1\le w_i \le 10^9\))
分析
由于生成树的权值为按位与,考虑按计算每一位的期望,设\(p_i\)为得到的生成树的边权第\(i\)位均为\(1\)的概率,则生成树的权值数学期望为:
由于等概率随机取一棵生成树,则有:
图中的生成树个数可利用矩阵树定理\(O(n^3)\)求得,最终时间复杂度\(O(T\cdot31n^3)\)
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const int MOD = 998244353;
const int maxn = 1e4 + 10;
int n, m;
struct edge
{
int u, v, w;
}e[maxn];
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 g[105][105], d[105][105];
void clear()
{
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
g[i][j] = 0;
d[i][j] = 0;
}
}
}
LL t[105][105];
void get_matrix(int k)
{
clear();
for(int i = 1; i <= m; i++)
{
int u = e[i].u, v = e[i].v, w = e[i].w;
if(k == -1 || ((w >> k) & 1) == 1)
{
g[u][v]++;
g[v][u]++;
d[u][u]++;
d[v][v]++;
}
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
t[i][j] = ((d[i][j] - g[i][j]) % MOD + MOD) % MOD;
}
LL det_matrix()
{
LL ans = 1;
int i, j, k;
for(i = 1; i <= n - 1; i++)
{
for(j = i; j <= n - 1; j++)
if(t[i][j]) break;
if(i != j)
swap(t[i], t[j]);
ans = ans * t[i][i] % MOD;
for(j = i + 1; j <= n - 1; j++)
{
LL d = t[j][i] * qpow(t[i][i], MOD - 2) % MOD;
for(k = i; k <= n - 1; k++)
t[j][k] = ((t[j][k] - d * t[i][k] % MOD) % MOD + MOD) % MOD;
}
}
return (ans % MOD + MOD) % MOD;
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d %d", &n, &m);
for(int i = 1; i <= m; i++)
scanf("%d %d %d", &e[i].u, &e[i].v, &e[i].w);
get_matrix(-1);
LL tot = det_matrix(), sum = 0;
for(int i = 0; i <= 30; i++)
{
get_matrix(i);
sum = (sum + (1LL << i) % MOD * det_matrix() % MOD) % MOD;
}
printf("%lld\n", sum * qpow(tot, MOD - 2) % MOD);
}
return 0;
}

浙公网安备 33010602011771号