ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-07-21 20:36:26  阅读:161  来源: 互联网

标签:ch -- top 学长 int wrt Day dp mod


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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有