其他分享
首页 > 其他分享> > 来自学长的馈赠2--(集训Day 3)

来自学长的馈赠2--(集训Day 3)

作者:互联网

A. 随

本题的难点在于有两个模数,这就导致数据信息的丢失。于是,我们的贺俊鹏大佬发明了一种高效、易懂的魔改快速幂算法,直接暴切(虽然赛时没有memset)。 本题的目标答案:$ans=\frac{(\sum\limits_{i=1}^{n}a[i])^{m}}{n^{m}}$ n有1e5,但是mod只有1000,这就引导我们在mod上下功夫,把a中相同项合并,我们就把枚举范围降为了mod。 m次操作之间相互独立,互不影响,也就意味着快速幂或倍增是正确的,第一次我们选出2个数相乘,第二次用上一次的再选出两个,而此时实际上是4个,以此类推,8,16……m。 之所以把乘法和加法分开,是因为乘法以mod为底,加法以1e9+7为底。记录数组a[],表示若干次操作相乘%mod后为i的方案数。显然它的贡献就是i*a[i]。累加起来就是分子,分母直接普通快速幂。
 #define sandom signed
#include <cstdio>
#include <iostream>
#define re register int
#define int long long 
using namespace std;
typedef long long LL;
const int Z = 1e6 + 100;
const int mod = 1e9 + 7;

int wrt[50];
inline int read()
{
    int f = 0; char ch = getchar(); int x = 0;
    while (!isdigit(ch)) f |= ch == '-', ch = getchar();
    while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return f ? -x : x;
}
inline void write(int x)
{
    int top = 0;
    if (x < 0) putchar('-'), x = -x;
    while (x >= 10) wrt[++top] = x % 10, x /= 10; wrt[++top] = x;
    while (top) putchar(wrt[top--] | 48); putchar(' ');
}

int n, m, p, fz, fm;
struct node
{
    int a[1100];//a[i]:多次相乘后为i的个数
    node () { for (re i = 0; i < p; i++) a[i] = 0; }
    inline friend node operator *(const node& A, const node& B)
    {
        node res;
        for (re i = 0; i < p; i++)
            for (re j = 0; j < p; j++)
                (res.a[i * j % p] += A.a[i] * B.a[j] % mod) %= mod;
        return res;
    }
}; node ans, base;

inline void qpnd(int b)
{
    while (b)
    {
        if (b & 1) ans = ans * base;
        base = base * base;
        b >>= 1;
    }
}
inline int qpow(int a, int b, int c)
{
    int res = 1;
    while (b)
    {
        if (b & 1) res = res * a % c;
        a = a * a % c;
        b >>= 1;
    }
    return res;
}

sandom main()
{
    n = read(), m = read(), p = read();
    for (re i = 1; i <= n; i++) base.a[read()]++;
    ans.a[1] = 1;
    qpnd(m);
    for (re i = 0; i < p; i++)//得数为0的没有贡献
        (fz += i * ans.a[i] % mod) %= mod;
    fm = qpow(n, m, mod);
    write(fz * qpow(fm, mod - 2, mod) % mod);
    return 0;
}

B. 单

首先,因为边权都是1,所以钦定顶点每移动一格,对应的a[]系数加1或减1。

容易得到:b[son]=b[rt]+(sum-sz[son])-sz[son];即该边两端子树的差值

t=0:只需要暴力计算出b[1],然后依次向下转移。

t=1:转化一下式子:b[son]-b[rt]=sum-2*sz[son];把所有的n-1条边都累加上,得到了tmp=(n-1)*sum-2*$\sum{}sz[son]$。不难发现每一个节点的a会被计算dis(1, x)次,这些的∑不就是b[1]嘛,所以sum=(tmp+2*b[1])/(n-1)。sum都有了,其他的就可以按照定理推导了。

细节:结点1一定要初始化啊,因为dfs只修改儿子。虽然题目说答案不会超int,但是过程超了啊喂。

#define sandom signed
#include <cstdio>
#include <iostream>
#define re register int
#define int long long 
using namespace std;
const int Z = 1e5 + 100;

int wrt[50];
inline int read()
{
    int f = 0; char ch = getchar(); int x = 0;
    while (!isdigit(ch)) f |= ch == '-', ch = getchar();
    while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return f ? -x : x;
}
inline void write(int x)
{
    int top = 0;
    if (x < 0) putchar('-'), x = -x;
    while (x >= 10) wrt[++top] = x % 10, x /= 10; wrt[++top] = x;
    while (top) putchar(wrt[top--] | 48); putchar(' ');
}

int n, m, ans;
struct edge
{
    int v, ne;
    edge () {}
    edge (int A, int C) { v = A, ne = C; }
};edge e[Z << 1];
int head[Z], cnt;
inline void add(int x, int y)
{
    e[++cnt] = edge(y, head[x]);
    head[x] = cnt;
}

int a[Z], b[Z], sz[Z], sum;
int dis[Z];
void dfs1(int rt, int fa)
{
    sz[rt] = a[rt];
    dis[rt] = dis[fa] + 1;
    for (re i = head[rt]; i; i = e[i].ne)
    {
        int son = e[i].v;
        if (son == fa) continue;
        dfs1(son, rt);
        sz[rt] += sz[son];
    }
}
void getb(int rt, int fa)
{
    for (re i = head[rt]; i; i = e[i].ne)
    {
        int son = e[i].v;
        if (son == fa) continue;
        b[son] = b[rt] + (sum - sz[son]) - sz[son];
        getb(son, rt);
    }
}
void dfs2(int rt, int fa)
{
    for (re i = head[rt]; i; i = e[i].ne)
    {
        int son = e[i].v;
        if (son == fa) continue;
        sum += b[son] - b[rt];
        dfs2(son, rt);
    }
}
void geta(int rt, int fa)
{
    int tmp = 0;
    for (re i = head[rt]; i; i = e[i].ne)
    {
        int son = e[i].v;
        if (son == fa) continue;
        sz[son] = (b[rt] - b[son] + sum) / 2;
        tmp += sz[son];
        geta(son, rt);
    }
    a[rt] = sz[rt] - tmp;
}

sandom main()
{
    dis[0] = -1;
    int T = read();
    while (T--)
    {
        sum = 0;
        n = read();
        for (re i = 1; i < n; i++)
        {
            int u = read(), v = read();
            add(u, v), add(v, u);
        }
        int t = read();
        if (t == 0)
        {
            for (re i = 1; i <= n; i++) a[i] = read(), sum += a[i];
            dfs1(1, 0);
            b[1] = 0;
            for (re i = 1; i <= n; i++) b[1] += a[i] * dis[i];
            getb(1, 0);
            for (re i = 1; i <= n; i++) write(b[i]); putchar('\n');
        }
        else
        {
            for (re i = 1; i <= n; i++) b[i] = read();
            dfs2(1, 0);
            sum = (sum + 2 * b[1]) / (n - 1);
            sz[1] = sum;
            geta(1, 0);
            for (re i = 1; i <= n; i++) write(a[i]); putchar('\n');
        }
        for (re i = 1; i <= n; i++) head[i] = 0; cnt = 0;
    }
    return 0;
}

C. 题

一道组合计数综合题(组合数,卡特兰数,dp)

0.枚举n步中哪些是沿着横轴走的,这其中一半向左,一半向右;剩下的沿着纵轴的同理

C(n, i) * C(i, i / 2) * C((n - i), (n - i) / 2)

1.向右走,且保证每一步的前缀向左走的步数不大于向右走的步数。catalan(n / 2)

2.定义dp[i]走i步后回到(0,0)的方案数,枚举第一次回到原点的步数j,这一段同1用catalan,后一段直接dp转移 dp[i] += dp[i - j] * catalan(j / 2 - 1) * 4

3.0和1的结合,枚举n步中哪些是沿着横轴走的,保证每一步的前缀向左走的步数不大于向右走的步数;纵轴同理。C(n, i) * catalan(i / 2) * catalan((n - i) / 2)

 #define sandom signed
#include <cstdio>
#include <iostream>
#define re register int
#define int long long 
using namespace std;
const int Z = 1e5 + 100;
const int mod = 1e9 + 7;

int wrt[50];
inline int read()
{
    int f = 0; char ch = getchar(); int x = 0;
    while (!isdigit(ch)) f |= ch == '-', ch = getchar();
    while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return f ? -x : x;
}
inline void write(int x)
{
    int top = 0;
    if (x < 0) putchar('-'), x = -x;
    while (x >= 10) wrt[++top] = x % 10, x /= 10; wrt[++top] = x;
    while (top) putchar(wrt[top--] | 48); putchar(' ');
}

int n, m, ans;
inline int qpow(int a, int b, int c)
{
    int res = 1;
    while (b)
    {
        if (b & 1) res = res * a % c;
        a = a * a % c;
        b >>= 1;
    }
    return res;
}
inline int inv(int x) { return qpow(x, mod - 2, mod); }
int fac[Z], ny[Z];
inline void init()
{
    fac[0] = ny[0] = 1;
    for (re i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % mod;
    ny[n] = inv(fac[n]);
    for (re i = n - 1; i >= 1; i--) ny[i] = ny[i + 1] * (i + 1) % mod;
}
inline int C(int n, int m) { return fac[n] * ny[m] % mod * ny[n - m] % mod; }
inline int ctl(int n) { return C(2 * n, n) * inv(n + 1) % mod; }

int dp[Z];
void solve0()
{
    for (re i = 0; i <= n; i += 2)//枚举横向走i步
        (ans += C(n, i) * C(i, i / 2) % mod * C((n - i), (n - i) / 2) % mod) %= mod;
}
void solve1()
{
    ans = ctl(n / 2);
}
void solve2()
{
    dp[0] = 1;
    for (re i = 2; i <= n; i += 2)//再次回到原点总共移动了i步
        for (re j = 2; j <= i; j += 2)//第一次回到原点前在某个半轴上移动了j步
            (dp[i] += dp[i - j] * ctl(j / 2 - 1) * 4 % mod) %= mod;//有四个方向(-1是为了预留进出的位置,避免j之前回到原点)
    ans = dp[n];
}
void solve3()
{
    for (re i = 0; i <= n; i += 2)
        (ans += C(n, i) * ctl(i / 2) % mod * ctl((n - i) / 2) % mod) %= mod;
}

sandom main()
{
    n = read(), m = read();
    init();
    switch (m)
    {
        case 0: solve0(); break;
        case 1: solve1(); break;
        case 2: solve2(); break;
        case 3: solve3(); break;
    }
    write(ans);
    return 0;
}

D. DP搬运工1

定义dp[i][j][k]:当前考虑到第i个数,有j个空位(j+1个连续段),max和为k的方案数。

注意到空位的存在是重点,空位旁边的数与空位不会产生贡献,因为空位之后插进来的数一定比旁边大。

dp转移见代码

DP搬运工1
 #define sandom signed
#include <cstdio>
#include <iostream>
#define re register int
#define int long long 
using namespace std;
const int Z = 51;
const int mod = 998244353;

int wrt[50];
inline int read()
{
    int f = 0; char ch = getchar(); int x = 0;
    while (!isdigit(ch)) f |= ch == '-', ch = getchar();
    while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
    return f ? -x : x;
}
inline void write(int x)
{
    int top = 0;
    if (x < 0) putchar('-'), x = -x;
    while (x >= 10) wrt[++top] = x % 10, x /= 10; wrt[++top] = x;
    while (top) putchar(wrt[top--] | 48); putchar(' ');
}

int n, m, ans;
int dp[Z][Z][Z * Z];

sandom main()
{
    n = read(), m = read();
    dp[1][0][0] = 1;
    for (re i = 2; i <= n; i++)//当前选第几个数
        for (re j = 0; j < i; j++)//空位的个数
            for (re k = 0; k <= m; k++)//总贡献
            {
                if (j) dp[i][j][k] += (j + 1) * dp[i - 1][j - 1][k];//旁边为空位
                if (k >= 2 * i) dp[i][j][k] += (j + 1) * dp[i - 1][j + 1][k - 2 * i];//插入空位且两端都有数
                if (k >= i) dp[i][j][k] += 2 * (j + 1) * dp[i - 1][j][k - i];//插入两端空白
                dp[i][j][k] %= mod;
            }
    for (re k = 0; k <= m; k++) (ans += dp[n][0][k]) %= mod;
    write(ans);
    return 0;
}

标签:ch,--,top,学长,int,wrt,Day,dp,mod
来源: https://www.cnblogs.com/zsj11337/p/16503473.html