来自学长的馈赠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