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\)个有序对,等概率取值,考虑枚举段长求和,则数学期望为:

\[\frac{\sum_{i=1}^{n} \frac{1}{i}\sum_{j=1}^{n-i+1}(\text{sum}[j+i-1]-\text{sum}[j-1])}{\binom{n}{2}+n} \]

发现对\(j\)求和那段\(\text{sum}[]\)的取值是连续的,故再处理出\(\text{sum}[i]\)的前缀和\(\text{pre}[i]\),进一步化简上式得:

\[\frac{\sum_{i=1}^{n} \frac{1}{i}(\text{pre}[n]-\text{pre}[i]-\text{pre}[n-i])}{\binom{n}{2}+n} \]

可以\(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_{d=1}^nd*f(d)\sum_{a_1=1}^{\lfloor\frac{n}{d}\rfloor}...\sum_{a_x=1}^{\lfloor\frac{n}{d}\rfloor}\prod_{j=1}^n(a_j*d)^k*[gcd == 1] \]

\[\displaystyle\sum_{d=1}^nd*f(d)\sum_{a_1=1}^{\lfloor\frac{n}{d}\rfloor}...\sum_{a_x=1}^{\lfloor\frac{n}{d}\rfloor}\prod_{j=1}^n(a_j*d)^k*\sum_{t|gcd}\mu(t) \]

\[\displaystyle\sum_{d=1}^nd*f(d)\sum_{t=1}^{\lfloor\frac{n}{d}\rfloor}\mu(t)\sum_{a_1=1}^{\lfloor\frac{n}{td}\rfloor}...\sum_{a_x=1}^{\lfloor\frac{n}{td}\rfloor}\prod_{j=1}^n(a_j*d*t)^k \]

后面那一串看起来很吓人,但其实就是 \(\displaystyle(\sum_{i = 1}^ni^k)^x\) ,可以化简成:

\[\displaystyle\sum_{d=1}^nd*f(d)*d^{kx}\sum_{t=1}^{\lfloor\frac{n}{d}\rfloor}\mu(t)*t^{kx}*\displaystyle(\sum_{i = 1}^{\lfloor\frac{n}{td}\rfloor}i^k)^x \]

由于 \(n\) 其实只有 \(2e5\),很多东西可以暴力预处理,比如最后一项和式,以及前两项和式的前缀和,然后就有了一个 \(O(T*n^{\frac{3}{4}})\) 的做法,但是由于常数较大,被卡掉了(如果不是看到有人跑了 2200ms,我也不会冲的),式子还要进一步化简:

\[\displaystyle\sum_{T=1}^ng(\lfloor\frac{n}{T}\rfloor)\sum_{d|T}d^{kx+1}*f(d)*\mu(\frac{T}{d})*(\frac{T}{d})^{kx} \]

其中 \(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\)的概率,则生成树的权值数学期望为:

\[\sum_{i=0}^{30}2^i \cdot p_i \]

由于等概率随机取一棵生成树,则有:

\[p_i=\frac{图中的边权第i位均为1的生成树个数}{图中的生成树总数}=\frac{边权第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;
}

1011 - Kirakira

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