2018 Multi-University Training Contest 4__全部题解+标程

本文精选了算法竞赛中的典型题目并提供了详细的解答思路与代码实现,涵盖了从简单到复杂的多种算法问题,包括数学推导、数据结构应用及复杂度优化等方面。

​​​​​这里写图片描述
6道题排名:29~79

出题人预计难度分布:   Easy - DJKL,      Medium - ABCEG,      Hard - FHI
题解较长,可查看右方目录

##A. Integers Exhibition

不难发现非 KKK-magic 数是非常少的,考虑先预处理出来,二分回答询问。

以下我们讨论如何求出非 KKK-magic 数,为方便描述,我们称一个正整数是良好的当且仅当其是非 KKK-magic 的。

对于一个质数 ppp,我们考虑所有仅包含小于 ppp 的质因子的正整数集 GGG。不难发现:

  • x∈Gx \in GxG,且在 GGG 中已经有超过 KKK 个小于 xxx 的整数约数个数多于 xxx,即 xxx 一定不是良好的,则 xpcx p ^ cxpc (c≥0)(c \ge 0)(c0) 也一定不可能是良好的。

这样我们就可以得到一个初步的想法。开始我们认为仅有 111 是良好的,枚举质因子 ppp,对于每一个原来认为是良好的数 xxx,将 xpcx p ^ cxpc (c≥0)(c \ge 0)(c0) 加入候选列表,接着将候选列表排序,除去已经可以确定不是良好的数,进入下一轮迭代。容易证明,在这个算法中,筛去一个不是良好的数 xxx,是不会在后续过程中令一个原本不是良好的数,变成一个良好的数的,故筛去良好的数的过程是合法的剪枝。

然而枚举的质因子的范围有多大呢?联想 K=0K = 0K=0 这一经典问题,我们知道对于 101810 ^ {18}1018 的范围,考虑前 202020 个质因子都绰绰有余了,因为将更大的质因子加入是非常不优的。在 KKK 更大的时候,我们采用“迭代至稳定”的思想,每一轮迭代后检查答案是否变化,如果在较长一段迭代后答案无任何变化,我们就认为质因子 ppp 的上界已经达到。经过实践,在 K=233K = 233K=233 时,ppp 的最大值取到 293293293 即可。

我们考虑如何在一轮迭代中除去确定不是良好的数。考虑维护前 K+1K + 1K+1 大值,从小到大枚举候选列表中的数 xxx,若 xxx 小于第 K+1K + 1K+1 大值,我们就把这个数除去。否则更新前 K+1K + 1K+1 大值。根据上述描述可以大致估算复杂度。设 K=233K = 233K=233 时,101810 ^ {18}1018 内良好的数的数量为 NNN,经过实践,可以知道 NNN 约为 500005000050000。每次扩展最多把一个数扩展成 log⁡M\log MlogM 个数,在剪枝完毕后,列表大小又回归到 NNN 以下,故时间复杂度可以估算为 O(NKMax(p)log⁡M)O(NK Max(p) \log M)O(NKMax(p)logM),常数较小。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL, int> pii;

int pr[305], chk[305], cnt;

const int maxM = (int)(1e6);
const int maxK = 240;
const LL MAX = (LL)(1e18) + maxM;
const LL K = 233;

pii b[maxM], c[maxM];
int mx[maxK], ps[maxK];
LL va[maxM * 10];
int tot;

void upd(int x) {
	int p = K; while(p && mx[p - 1] < x) --p;
	for (int i = K; i > p; --i) mx[i] = mx[i - 1]; 
	mx[p] = x;
 }

bool valid(LL a, LL b) {
	LL ret = 0;
	for (; b; b >>= 1) {
		if (b & 1) {
			ret += a;
			if (a < 0 || ret >= MAX) return 0;
		}
		a += a; if (a >= MAX) a = -1;
	} return 1;
}

void gen() {
	int M = 293;
	for (int i = 2; i <= M; ++i) if (!chk[i]) {
		pr[++cnt] = i;
		for (int j = i + i; j <= M; j += i) chk[j] = 1;
	}
	tot = 0; b[++tot] = make_pair(1, 1);
	for (int i = 1; i <= cnt; ++i) {
		LL now = pr[i]; int idx = 0;
		for (int j = 1; j <= tot; ++j) {
			c[++idx] = b[j]; int w = 0;
			LL va = b[j].first; int di = b[j].second;
			while (valid(va, now)) {
				va *= now; ++w;
				c[++idx] = make_pair(va, di * (w + 1));
			}
		}
		sort(c + 1, c + idx + 1); tot = 0; 
		memset(mx, 0, sizeof(mx));
		for (int j = 1; j <= idx; ++j) {
			if (c[j].second >= mx[K]) {
				upd(c[j].second); b[++tot] = c[j];
			}
		}
	}
	int cur = 0;
	for (int i = 0; i <= K; ++i) {
		int cnt = 0;
		ps[i] = cur + 1;
		memset(mx, 0, sizeof(mx));
		for (int j = 1; j <= tot; ++j) {
			if (b[j].second >= mx[i]) {
				upd(b[j].second); va[++cur] = b[j].first - (cnt++);
			}
		}
	}
	ps[K + 1] = cur + 1;
}

void solve () {
	LL n; int m;
	cin >> n >> m;
	int p = upper_bound(va + ps[m], va + ps[m + 1], n) - va - ps[m];
	cout << n + p << endl;
}

int main () {
	ios::sync_with_stdio(false);
	int T;
	cin >> T; gen();
	while (T--) solve();
	return 0;
}

B. Harvest of Apples

定义 S(n,m)=∑i=0m(ni)S(n, m) = \sum_{i = 0} ^ {m} {n \choose i}S(n,m)=i=0m(in),不难发现 S(n,m)=S(n,m−1)+(nm),S(n,m)=2S(n−1,m)−(n−1m)S(n, m) = S(n, m - 1) + {n \choose m}, S(n, m) = 2S(n - 1, m) - {n - 1 \choose m}S(n,m)=S(n,m1)+(mn),S(n,m)=2S(n1,m)(mn1)。也就是说,如果我们知道 S(n,m)S(n, m)S(n,m),就能以 O(1)O(1)O(1) 的代价计算出 S(n−1,m),S(n,m−1),S(n+1,m),S(n,m+1)S(n - 1, m), S(n, m - 1), S(n + 1, m), S(n, m + 1)S(n1,m),S(n,m1),S(n+1,m),S(n,m+1),可以采用莫队算法。

时间复杂度 O(TMAX)O(T \sqrt{MAX})O(TMAX)

#include <bits/stdc++.h>
using namespace std;

const int N = 200000;
const int MOD = 1000000007;
struct query
{
    int n, k, i;
} Q[N];
int T;
vector <query> lst[N];
int cnt, mx, chunk;
int fac[N], inv[N], res[N], in_chunk[N];
int powi(int a, int b)
{
    int c = 1;
    for (; b; b >>= 1, a = 1ll * a * a % MOD)
        if (b & 1) c = 1ll * c * a % MOD;
    return c;
}
int C(int a, int b)
{
    return 1ll * fac[a] * inv[b] % MOD * inv[a - b] % MOD;
}
int comp(query a, query b)
{
    return a.n < b.n;
}
int main()
{
    mx = 100000;
    fac[0] = 1; for (int i = 1; i <= mx; ++ i) fac[i] = 1ll * fac[i - 1] * i % MOD;
    inv[mx] = powi(fac[mx], MOD - 2); for (int i = mx - 1; ~i; -- i) inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD;
    chunk = sqrt(mx);
    cnt = 1;
    for (int i = 1; i <= mx; i += chunk, ++ cnt)
        for (int j = i; j < i + chunk && j <= mx; ++ j)
            in_chunk[j] = cnt;
    cnt --;
    scanf("%d", &T);
    for (int i = 1; i <= T; ++ i)
    {
        scanf("%d%d", &Q[i].n, &Q[i].k), Q[i].i = i;
        lst[in_chunk[Q[i].k]].push_back(Q[i]);
    }
    for (int i = 1; i <= cnt; ++ i) if (lst[i].size())
    {
        sort(lst[i].begin(), lst[i].end(), comp);
        int val = 0, in = lst[i][0].n, ik = -1;
        for (int j = 0; j < lst[i].size(); ++ j)
        {
            while (in < lst[i][j].n) val = (0ll + val + val + MOD - C(in ++, ik)) % MOD;
            while (ik < lst[i][j].k) val = (val + C(in, ++ ik)) % MOD;
            while (ik > lst[i][j].k) val = (val + MOD - C(in, ik --)) % MOD;
            res[lst[i][j].i] = val;
        }
    }
    for (int i = 1; i <= T; ++ i) printf("%d\n", res[i]);
}

C. Problems on a Tree

用并查集维护两种连通块 —— Easy + Medium 题的连通块,维护大小;Easy 题的连通块,维护大小以及与此连通块只隔一个 Hard 题的 Easy + Medium 连通块大小之和即可。

#include<bits/stdc++.h>
using namespace std;

int CASES;
int n,T;

struct R{
	int to,val,nex;
}r[222222];
int hea[111111]={0},cnt=1,fa[111111],dep[111111];
int f1[111111],f2[111111],sch[111111],sz2[111111];

int GF1(int x){
	return x==f1[x]? x: f1[x]=GF1(f1[x]);
}
int GF2(int x){
	return x==f2[x]? x: f2[x]=GF2(f2[x]);
}
void uni1(int y,int x){
	x=GF1(x);y=GF1(y);
	f1[y]=x;
	sch[x]+=sch[y];
}
void uni2(int y,int x){
	x=GF2(x);y=GF2(y);
	f2[y]=x;
	sz2[x]+=sz2[y];
	sch[GF1(fa[y])]-=sz2[y];
	if (fa[x]) sch[GF1(fa[x])]+=sz2[y];
}

void dfs(int x,int fff){
	fa[x]=fff;dep[x]=dep[fff]+1;
	for (int y,i=hea[x];i;i=r[i].nex){
		y=r[i].to;if (y==fff) continue;
		sch[x]++;
		dfs(y,x);
	}
	for (int y,i=hea[x];i;i=r[i].nex){
		y=r[i].to;if (y==fff) continue;
		if (r[i].val==1){
			uni2(y,x);uni1(y,x);
		}else if (r[i].val==2){
			uni2(y,x);
		}
	}
}

void doit(int x,int y){
	if (GF1(x)==GF1(y)) return;
	for (int z;(x=GF1(x))!=(y=GF1(y));x=z){
		if (dep[x]<dep[y]) swap(x,y);
		z=GF1(fa[x]);
		if (GF2(x)==GF2(z)){
			uni1(x,z);
		}else{
			uni2(x,z);
		}
	}
}

void __main(){
	scanf("%d%d",&n,&T);
	int u,v,w;
	for (int i=1;i<n;i++){
		scanf("%d%d%d",&u,&v,&w);w=max(1,w);
		r[++cnt]=(R){v,w,hea[u]};hea[u]=cnt;
		r[++cnt]=(R){u,w,hea[v]};hea[v]=cnt;
	}
	for (int i=1;i<=n;i++){
		f1[i]=f2[i]=i;
		sch[i]=0;sz2[i]=1;
	}
	dep[0]=0;
	dfs(1,0);
	int times=1;
	for (int st,en,fl,ans;T--;times++){
		scanf("%d%d%d%d",&u,&v,&en,&st);
		doit(u,v);
		fl=0;
		if (GF2(st)==GF2(en)) fl=1;
		if (GF1(fa[GF2(st)])==GF1(en)) fl=1;
		if (GF2(fa[GF1(en)])==GF2(st)) fl=1;
		sz2[0]=0;
		ans=sz2[GF2(en)]+sch[GF1(en)]\
			+( GF2(fa[GF1(en)])==GF2(en)? 0: sz2[GF2(fa[GF1(en)])] );
		printf("%d %d\n",fl,ans);
	}

    cnt = 1;
    for (int i = 1; i <= n; ++ i)
        hea[i] = 0;
    for (int i = 1; i <= n; ++ i)
        f1[i] = f2[i] = sch[i] = sz2[i] = fa[i] = dep[i] = 0;
}

int main()
{
    scanf("%d", &CASES);
    while (CASES --)
        __main();
}

D. Nothing is Impossible

如果仅有 1 道题,至少有一个人做对这题需要有 错误答案个数 + 1 个人。

那么容易发现在每道题正确答案只有一个的情况下,如果 nnn 道题中存在 sss 道题,使得学生人数 mmm 不少于每道题 错误答案个数 + 1 相乘的结果,那么一定有人能够得到 sss 分。故我们将题目按错误答案个数从小到大排序,找到最大的 ppp 满足 ∏i≤p(bi+1)≤m\prod_{i \le p} {(b_i + 1)} \le mip(bi+1)m 就是答案。

#include <bits/stdc++.h>
using namespace std;
const int N = 500000;
int n, m;
int ai[N];
void solve()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i)
        scanf("%*d%d", &ai[i]);
    sort(ai + 1, ai + n + 1);
    for (int i = 1, j = 1; i <= n + 1; ++ i)
        if (i == n + 1 || 1ll * j * (ai[i] + 1) > m)
        {
            printf("%d\n", i - 1);
            break;
        }
        else j *= (ai[i] + 1);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T --) solve();
}

E. Matrix from Arrays

简单推导可得

M[i][j]=A[((i+j)(i+j+1)2+i)mod  L]=A[(3i2+j2+i22+j22+ij)mod  L]=M[i+2L][j]=M[i][j+2L]M[i][j] = A[(\frac{(i + j)(i + j + 1)}{2} + i) \mod L] = A[(\frac{3i}{2} + \frac{j}{2} + \frac{i ^ 2} {2} + \frac{j ^ 2}{2} + ij) \mod L] = M[i + 2L][j] = M[i][j + 2L]M[i][j]=A[(2(i+j)(i+j+1)+i)modL]=A[(23i+2j+2i2+2j2+ij)modL]=M[i+2L][j]=M[i][j+2L]

预处理左上角 2L×2L2L \times 2L2L×2L 的矩阵的二维前缀和,O(1)O(1)O(1) 回答询问。时间复杂度 O(L2+Q)O(L ^ 2 + Q)O(L2+Q)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

int A[10], M[400][400];
int L;

LL calc (int n, int m) {
    if (n < 0 || m < 0) return 0;
    return 1LL * M[L - 1][L - 1] * (n / L) * (m / L) + \
           1LL * M[n % L][L - 1] * (m / L) + \
           1LL * M[L - 1][m % L] * (n / L) + \
           1LL * M[n % L][m % L];
}

void solve () {
    cin >> L;
    for (int i = 0; i < L; ++i) cin >> A[i];
    int k = 0;
    for (int i = 0; i < 4 * L; ++i) {
        for (int j = 0; j <= i; ++j) {
            if (j < 2 * L && i - j < 2 * L) M[j][i - j] = A[k];
            k = (k + 1) % L;
        }
    }
    L *= 2;
    for (int i = 0; i < L; ++i) for (int j = 0; j < L; ++j) {
        if (i) M[i][j] += M[i - 1][j];
        if (j) M[i][j] += M[i][j - 1];
        if (i && j) M[i][j] -= M[i - 1][j - 1];
    }
    int Q; cin >> Q;
    while (Q--) {
        int xL, yL, xR, yR;
        cin >> xL >> yL >> xR >> yR;
        cout << calc(xR, yR) - calc(xL - 1, yR) - calc(xR, yL - 1) + calc(xL - 1, yL - 1) << endl;
    }
}

int main () {
    ios::sync_with_stdio(false);
    int T; cin >> T;
    while (T--) solve();
    return 0;
}

F. Travel Through Time

由于可持久化的存在,直接维护哪些位置有棋子十分困难。考虑维护一些全是棋子的线段,这些线段可以有重叠部分,但是需要保证这些线段覆盖了所有的棋子。

注意到如果我们只维护了线段的左右边界,甚至不用知道某个左边界具体对应的是哪个右边界,就可以知道某个位置上有没有棋子。因此只维护左右边界,把左边界看成左括号,右边界看成右括号,那么这就是一个括号序列。

比如说对于 0111011110 这样一串格子(1表示有棋子,0表示没有),我们可以用这样的括号序列来维护:0(111)0(1111)0。由于一个局面并不对应了唯一的括号序列,因此这些括号序列也是可以的:0(111)0(11)(11)0,0(1(1(1)))0(((11(11))))0。

对于每一个操作,都可以用括号序列维护:

  • 操作一:在 xxx 前加入一对括号。

  • 操作二:将所有左括号向左移动 xxx,将所有右括号向右移动 xxx

  • 操作三:在 lll 的前面与 rrr 的后面加入形如 ... )))((( ... 的括号使得没有线段穿过 lllrrr。然后将 l,rl, rl,r之间的括号直接翻转并反转。比如说对于 0(111)0(11(1)1)0,如果要翻转 [3,8][3,8][3,8],首先补充括号,变成:0(1] [11)0(11(1]] [[1)1)0(为了区分,[]是新加入的括号),然后翻转,得到:0(1] [[1)11)0(11] [[)1)0。

对于左括号与右括号,分别开一棵可持久化 Treap 维护即可。时间复杂度 O(nlog⁡n)O(n \log n)O(nlogn)

#include <bits/stdc++.h>
using namespace std;

#define LL long long
const int N = 300000;
const int M = 5000000;
struct point
{
    int left, right, stamp, size;
    int rev; LL add, pos;
    int val, sum;
} tr[M];

int now, tot, key, n;
int rtL[N], rtR[N];

int check(int t)
{
    if (tr[t].stamp != now)
    {
        int p = ++ tot;
        tr[p] = tr[t];
        tr[p].stamp = now;
        return p;
    }
    else return t;
}
int upd(int t)
{
    if (!t) return 0;
    tr[t].sum = tr[tr[t].left].sum + tr[tr[t].right].sum + tr[t].val;
    tr[t].size = tr[tr[t].left].size + tr[tr[t].right].size + 1;
    return t;
}
int rev(int t)
{
    if (!t) return 0;
    t = check(t);
    tr[t].rev ^= 1;
    tr[t].add *= -1;
    tr[t].pos *= -1;
    swap(tr[t].left, tr[t].right);
    return t;
}
int add(int t, LL v)
{
    if (!t) return 0;
    t = check(t);
    tr[t].add += v;
    tr[t].pos += v;
    return t;
}
int psdw(int t)
{
    if (!t) return 0;
    t = check(t);
    if (tr[t].rev)
    {
        if (tr[t].left) tr[t].left = rev(tr[t].left);
        if (tr[t].right) tr[t].right = rev(tr[t].right);
        tr[t].rev = 0;
    }
    if (tr[t].add)
    {
        if (tr[t].left) tr[t].left = add(tr[t].left, tr[t].add);
        if (tr[t].right) tr[t].right = add(tr[t].right, tr[t].add);
        tr[t].add = 0;
    }
    return t;
}
void split(int t, LL p, int &l, int &r)
{
    if (!t) {l = r = 0; return;}
    t = psdw(t);
    if (p <= tr[t].pos)
    {
        split(tr[t].left, p, l, r);
        tr[t].left = r; r = upd(t);
    }
    else
    {
        split(tr[t].right, p, l, r);
        tr[t].right = l; l = upd(t);
    }
}
int merge(int a, int b)
{
    if (!a || !b) return a | b;
    if (1ll * rand() * (tr[a].size + tr[b].size) < 1ll * tr[a].size * RAND_MAX)
    {
        a = psdw(a); tr[a].right = merge(tr[a].right, b); return upd(a);
    }
    else
    {
        b = psdw(b); tr[b].left = merge(a, tr[b].left); return upd(b);
    }
}
void init(LL pos, int &l, int &r, int v)
{
    l = ++ tot; r = ++ tot;
    tr[l].stamp = tr[r].stamp = now;
    tr[l].pos = tr[r].pos = pos;
    tr[l].val = tr[r].val = v;
    upd(l); upd(r);
}
void solve(istream &in, ostream &os, int online = 1)
{
    for (int i = 1; i <= n; ++ i) rtL[i] = rtR[i] = 0;
    for (int i = 1; i <= tot; ++ i) tr[i] = tr[0]; key = tot = now = 0;
    in >> n;
    init(0, rtL[0], rtR[0], 1);
    for (int i = 1; i <= n; ++ i)
    {
        int opt; LL x, l, r; now = i;
        int Ll, Lr, Lm, Rl, Rr, Rm;
        in >> opt;
        rtL[i] = rtL[i - 1]; rtR[i] = rtR[i - 1];
        if (opt == 1)
        {
            in >> x; x ^= key;
            split(rtL[i], x, Ll, Lr);
            split(rtR[i], x, Rl, Rr);
            init(x, Lm, Rm, 1);
            rtL[i] = merge(merge(Ll, Lm), Lr);
            rtR[i] = merge(merge(Rl, Rm), Rr);
        }
        else if (opt == 2)
        {
            in >> x; x ^= key;
            rtL[i] = add(rtL[i], -x);
            rtR[i] = add(rtR[i], x);
        }
        else if (opt == 3)
        {
            in >> l >> r; l ^= key; r ^= key;
            split(rtL[i], l, Ll, Lr); split(Lr, r + 1, Lm, Lr);
            split(rtR[i], l, Rl, Rr); split(Rr, r + 1, Rm, Rr);
            int Lx, Ly, Rx, Ry;
            init(0, Lx, Ly, tr[Rr].sum - tr[Lr].sum);
            tr[Lx].pos = l; tr[Ly].pos = r + 1;
            init(0, Rx, Ry, tr[Ll].sum - tr[Rl].sum);
            tr[Rx].pos = l - 1; tr[Ry].pos = r;
            rtL[i] = merge(merge(merge(Ll, Lx), add(rev(Rm), l + r)), merge(Ly, Lr));
            rtR[i] = merge(merge(merge(Rl, Rx), add(rev(Lm), l + r)), merge(Ry, Rr));
        }
        else if (opt == 4)
        {
            in >> x; x ^= key;
            rtL[i] = rtL[x]; rtR[i] = rtR[x];
        }
        else
        {
            in >> x; x ^= key;
            split(rtL[i], x + 1, Ll, Lr);
            split(rtR[i], x, Rl, Rr);
            if (tr[Ll].sum ^ tr[Rl].sum)
                os << "Yes" << endl, key += online;
                else os << "No" << endl;
            rtL[i] = rtL[i - 1]; rtR[i] = rtR[i - 1];
        }
    }
    // cerr << key << endl;
}

int main()
{
    ios::sync_with_stdio(0);
    int T;
    for (cin >> T; T; T --) solve(cin, cout);
}

G. Depth-First Search

由于题目和字典序有关,不妨运用逐位确定的思想。

首先,我们要求第一位小于 B1B_1B1 的序列总数,即我们需要快速求出以每个点为根时的DFS序列总数。对于有根树,设 f(i)f(i)f(i) 为以 iii 为根的子树的DFS序列总数,有

f(u)=∣son(u)∣!∏v∈son(u)f(v)f(u) = |son(u)|! \prod_{v \in son(u)} {f(v)}f(u)=son(u)!vson(u)f(v)

我们可以先任选一个根 DFS 求出 fff,在第二遍 DFS 考虑祖先的贡献即可将所有点的答案求出。

接着我们以 B1B_1B1 为根,逐位确定求出所有的答案。和上述方法类似,即如果当前在 BiB_iBi 点,要走到 Bi+1B_{i+1}Bi+1 点,需要求出所有第 i+1i + 1i+1 位小于 Bi+1B_{i+1}Bi+1 的方案数,简单计算即可。

需要注意的是,由于我们可能需要快速计算某个点下某个子树的名次,所以需要用树状数组或线段树来优化这个过程。

时间复杂度 O(nlog⁡n)O(n \log n)O(nlogn)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
  
#define lowbit(x) ((x) & (-x))
  
LL pow_mod(LL b, LL p, LL k) {
    LL ret = 1;
    for (; p; p >>= 1) {
        if (p & 1) ret = ret * b % k;
        b = b * b % k;
    } return ret;
}
  
const int maxn = (int)(2e6) + 5;
const int mo = (int)(1e9) + 7;
  
int b[maxn], son[maxn];
vector<int> nxt[maxn], C[maxn];
LL f[maxn], g[maxn], fac[maxn], ret;
  
void dfs(int x, int fa) {
    f[x] = 1;
    if (fa) nxt[x].erase(find(nxt[x].begin(), nxt[x].end(), fa));
    son[x] = nxt[x].size(); nxt[x].insert(nxt[x].begin(), 0); C[x].push_back(0);
    for (int i = 1; i <= son[x]; ++i) {
        dfs(nxt[x][i], x); f[x] = f[x] * f[nxt[x][i]] % mo; C[x].push_back(0);
    } LL w;
    w = 1; for (int i = 1; i <= son[x]; ++i) g[nxt[x][i]] = w, w = w * f[nxt[x][i]] % mo;
    w = 1; for (int i = son[x]; i >= 1; --i) (g[nxt[x][i]] *= w) %= mo, w = w * f[nxt[x][i]] % mo;
    f[x] = f[x] * fac[son[x]] % mo;
}
  
void calcPre(int x, LL v) {
    int cnt = son[x];
    if (x == b[1]) --cnt;
    else if (x < b[1]) (ret += (f[x] * v % mo) * (cnt + 1) % mo) %= mo;
    for (int i = 1; i <= son[x]; ++i) calcPre(nxt[x][i], (v * g[nxt[x][i]] % mo) * fac[cnt] % mo);
}
  
inline void add(int p, int x, int v) {
    for (; x <= son[p]; x += lowbit(x)) C[p][x] += v;
}
  
inline int sum(int p, int x) {
    int ret = 0; for (; x; x -= lowbit(x)) ret += C[p][x]; return ret;
}
  
inline int findNxt(int p, int x) {
    int ps = 0;
    for (int i = 20; i >= 0; --i) if (ps + (1 << i) <= son[p] && nxt[p][ps + (1 << i)] < x) ps += (1 << i);
    return ps;
}
  
int idx;
bool calcNow(int x, LL v) {
    LL now = 1; ++idx;
    for (int i = 1; i <= son[x]; ++i) now = now * f[nxt[x][i]] % mo, add(x, i, 1);
    for (int i = son[x] - 1; i >= 0; --i) {
        int s = findNxt(x, b[idx + 1]);
        (ret += ((fac[i] * now % mo) * sum(x, s) % mo) * v % mo) %= mo; ++s;
        if (s > son[x] || nxt[x][s] != b[idx + 1]) return 1;
        add(x, s, -1); now = now * pow_mod(f[nxt[x][s]], mo - 2, mo) % mo;
        if (calcNow(nxt[x][s], (v * fac[i] % mo) * now % mo)) return 1;
    } return 0;
}

void solve () {
    int n; idx = 0;
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> b[i], nxt[i].clear(), C[i].clear();
    for (int i = 2; i <= n; ++i) {
        int u, v; cin >> u >> v;
        nxt[u].push_back(v); nxt[v].push_back(u);
    }
    b[n + 1] = 0; g[0] = 1; ret = 0;
    for (int i = 1; i <= n; ++i) sort(nxt[i].begin(), nxt[i].end());
    dfs(b[1], 0); calcPre(b[1], 1); calcNow(b[1], 1);
    cout << ret << endl;
}

int main() {
    ios::sync_with_stdio(false);
    int MAX = (int)(1e6);
    fac[0] = 1;
    for (int i = 1; i <= MAX; ++i) fac[i] = fac[i - 1] * i % mo;
    int T; cin >> T;
    while (T--) solve();
    return 0;
}

H. Eat Cards, Have Fun

考虑如何计算某个特定排列 AAA 的 Value.

KaTeX parse error: Expected '}', got '&' at position 49: … - i)! \sum_{j &̲gt; i} {[A_j &l…

这启发我们对于每个 iii 分别计算贡献。考虑当第 iii 张卡片被吃掉的时候,我们需要知道这张卡片左边、右边分别已有多少卡片被吃掉(记为 l,rl, rl,r),才能确定第 iii 张卡片在 AAA 中的位置;我们还需要知道这张卡片左边、右边分别已有多少卡面数字小于 aia_iai 的卡片被吃掉(记为 l^,r^\hat{l}, \hat{r}l^,r^),才能确定第 iii 张卡片对答案的贡献,即 KaTeX parse error: Expected '}', got '&' at position 9: \sum_{j &̲gt; i} {[A_j &l…。如果知道了 l,r,l^,r^l, r, \hat{l}, \hat{r}l,r,l^,r^,那么答案就是

Ans=∑i=1n∑l=0i−1∑r=0n−i∑l^=0l∑r^=0r(n−l−r−1)!(ai−1−l^−r^)P(i,l,r,l^,r^)Ans = \sum_{i = 1} ^ {n} \sum_{l = 0} ^ {i - 1} \sum_{r = 0} ^ {n - i} \sum_{\hat{l} = 0} ^ {l} \sum_{\hat{r} = 0} ^ {r} {(n - l - r - 1)! (a_i - 1 - \hat{l} - \hat{r}) P(i, l, r, \hat{l}, \hat{r})}Ans=i=1nl=0i1r=0nil^=0lr^=0r(nlr1)!(ai1l^r^)P(i,l,r,l^,r^)

其中 P(i,l,r,l^,r^)P(i, l, r, \hat{l}, \hat{r})P(i,l,r,l^,r^) 是达到对应情况的概率。我们可以枚举第 iii 张卡片是在第 kkkKaTeX parse error: Expected 'EOF', got '&' at position 4: (k &̲gt; 0) 被吃掉的来计算概率:

P(i,l,r,l^,r^)=(bil^)(i−bi−1l−l^)(ai−bi−1r^)(n−i−ai+bi+1r−r^)∑k=1∞(1−(1−p)k)l((1−p)k)i−1−lp(1−p)k−1(1−(1−p)k−1)r((1−p)k−1)n−i−rP(i, l, r, \hat{l}, \hat{r}) = {b_i \choose \hat{l}} {i - b_i - 1 \choose l - \hat{l}} {a_i - b_i - 1 \choose \hat{r}} {n - i - a_i + b_i + 1 \choose r - \hat{r}} \sum_{k = 1} ^ {\infty} {(1 - (1 - p) ^ k) ^ l ((1 - p) ^ k) ^ {i - 1 - l} p (1 - p) ^ {k - 1} (1 - (1 - p) ^ {k - 1}) ^ r ((1 - p) ^ {k - 1}) ^ {n - i - r}}P(i,l,r,l^,r^)=(l^bi)(ll^ibi1)(r^aibi1)(rr^niai+bi+1)k=1(1(1p)k)l((1p)k)i1lp(1p)k1(1(1p)k1)r((1p)k1)nir

其中 KaTeX parse error: Expected '}', got '&' at position 15: b_i = \sum_{j &̲lt; i} {[a_j &l….

观察式子 (1−(1−p)x)y(1 - (1 - p) ^ x) ^ y(1(1p)x)y,可以用二项式定理展开:

(1−(1−p)x)y=∑i=0y(yi)(−1)i(1−p)xi(1 - (1 - p) ^ x) ^ y = \sum_{i = 0} ^ {y} {{y \choose i} (-1) ^ i (1 - p) ^ {xi}}(1(1p)x)y=i=0y(iy)(1)i(1p)xi

利用上述结论,进一步化简:

amp;∑k=1∞(1−(1−p)k)l((1−p)k)i−1−lp(1−p)k−1(1−(1−p)k−1)r((1−p)k−1)n−i−r=amp;p∑k=1∞(1−p)k(i−1−l)+k−1+(k−1)(n−i−r)∑x=0l(lx)(−1)x(1−p)xk∑y=0r(ry)(−1)y(1−p)y(k−1)=amp;p∑x=0l∑y=0r(−1)x+y(lx)(ry)∑k=1∞(1−p)k(i−1−l)+k−1+(k−1)(n−i−r)+xk+y(k−1)=amp;p∑x=0l∑y=0r(−1)x+y(lx)(ry)∑k=0∞(1−p)k(n−l−r+x+y)+(i−1−l+x)=amp;p∑x=0l∑y=0r(−1)x+y(lx)(ry)(1−p)i−1−l+x1−(1−p)n−l−r+x+y\begin{aligned} &amp; \sum_{k = 1} ^ {\infty} {(1 - (1 - p) ^ k) ^ l ((1 - p) ^ k) ^ {i - 1 - l} p (1 - p) ^ {k - 1} (1 - (1 - p) ^ {k - 1}) ^ r ((1 - p) ^ {k - 1}) ^ {n - i - r}} \\ = &amp; p \sum_{k = 1} ^ {\infty} {(1 - p) ^ {k(i - 1 - l) + k - 1 + (k - 1)(n - i - r)} \sum_{x = 0} ^ {l} {{l \choose x} (-1) ^ x (1 - p) ^ {xk}} \sum_{y = 0} ^ {r} {{r \choose y} (-1) ^ y (1 - p) ^ {y(k - 1)}}} \\ = &amp; p \sum_{x = 0} ^ {l} \sum_{y = 0} ^ {r} {(-1) ^ {x + y} {l \choose x} {r \choose y} \sum_{k = 1} ^ {\infty} {(1 - p) ^ {k(i - 1 - l) + k - 1 + (k - 1)(n - i - r) + xk + y(k - 1)}}} \\ = &amp; p \sum_{x = 0} ^ {l} \sum_{y = 0} ^ {r} {(-1) ^ {x + y} {l \choose x} {r \choose y} \sum_{k = 0} ^ {\infty} {(1 - p) ^ {k (n - l - r + x + y) + (i - 1 - l + x)}}} \\ = &amp; p \sum_{x = 0} ^ {l} \sum_{y = 0} ^ {r} {(-1) ^ {x + y} {l \choose x} {r \choose y} \frac{(1 - p) ^ {i - 1 - l + x}} {1 - (1 - p) ^ {n - l - r + x + y}}} \end{aligned}====amp;k=1(1(1p)k)l((1p)k)i1lp(1p)k1(1(1p)k1)r((1p)k1)niramp;pk=1(1p)k(i1l)+k1+(k1)(nir)x=0l(xl)(1)x(1p)xky=0r(yr)(1)y(1p)y(k1)amp;px=0ly=0r(1)x+y(xl)(yr)k=1(1p)k(i1l)+k1+(k1)(nir)+xk+y(k1)amp;px=0ly=0r(1)x+y(xl)(yr)k=0(1p)k(nlr+x+y)+(i1l+x)amp;px=0ly=0r(1)x+y(xl)(yr)1(1p)nlr+x+y(1p)i1l+x

至此,我们获得了一个时间复杂度为 O(n5)O(n ^ 5)O(n5) 的算法。上述公式显然有不少冗余,可以进一步优化。

回顾原式:

Ans=p∑i=1n∑l=0i−1∑r=0n−i(n−l−r−1)!(∑x=0l∑y=0r(−1)x+y(lx)(ry)(1−p)i−1−l+x1−(1−p)n−l−r+x+y)(∑l^=0l∑r^=0r(ai−1−l^−r^)(bil^)(i−bi−1l−l^)(ai−bi−1r^)(n−i−ai+bi+1r−r^))Ans = p \sum_{i = 1} ^ {n} \sum_{l = 0} ^ {i - 1} \sum_{r = 0} ^ {n - i} {(n - l - r - 1)! (\sum_{x = 0} ^ {l} \sum_{y = 0} ^ {r} {(-1) ^ {x + y} {l \choose x} {r \choose y} \frac{(1 - p) ^ {i - 1 - l + x}} {1 - (1 - p) ^ {n - l - r + x + y}}}) (\sum_{\hat{l} = 0} ^ {l} \sum_{\hat{r} = 0} ^ {r} {(a_i - 1 - \hat{l} - \hat{r}) {b_i \choose \hat{l}} {i - b_i - 1 \choose l - \hat{l}} {a_i - b_i - 1 \choose \hat{r}} {n - i - a_i + b_i + 1 \choose r - \hat{r}}})}Ans=pi=1nl=0i1r=0ni(nlr1)!(x=0ly=0r(1)x+y(xl)(yr)1(1p)nlr+x+y(1p)i1l+x)(l^=0lr^=0r(ai1l^r^)(l^bi)(ll^ibi1)(r^aibi1)(rr^niai+bi+1))

以下将定义若干辅助函数加速计算答案。


定义 FFF
F(i,l,r)=∑x=0l∑y=0r(−1)x+y(lx)(ry)(1−p)i−1−l+x1−(1−p)n−l−r+x+yF(i, l, r) = \sum_{x = 0} ^ {l} \sum_{y = 0} ^ {r} {(-1) ^ {x + y} {l \choose x} {r \choose y} \frac{(1 - p) ^ {i - 1 - l + x}} {1 - (1 - p) ^ {n - l - r + x + y}}}F(i,l,r)=x=0ly=0r(1)x+y(xl)(yr)1(1p)nlr+x+y(1p)i1l+x

考虑如何快速计算 FFF。不妨定义 FnF_nFn

Fn(l,r)=∑x=0l∑y=0r(−1)x+y(lx)(ry)(1−p)n−1−l+x1−(1−p)n−l−r+x+yF_n(l, r) = \sum_{x = 0} ^ {l} \sum_{y = 0} ^ {r} {(-1) ^ {x + y} {l \choose x} {r \choose y} \frac{(1 - p) ^ {n - 1 - l + x}} {1 - (1 - p) ^ {n - l - r + x + y}}}Fn(l,r)=x=0ly=0r(1)x+y(xl)(yr)1(1p)nlr+x+y(1p)n1l+x

显然 F(i,l,r)=Fn(l,r)(1−p)i−nF(i, l, r) = F_n(l, r) (1 - p) ^ {i - n}F(i,l,r)=Fn(l,r)(1p)in

Fn(l,r)=l!r!∑x=0l∑y=0r(−1)xx!(−1)yy!(1−p)n−1−(l−x)(l−x)!(r−y)!(1−(1−p)n−(l−x)−(r−y))F_n(l, r) = l! r! \sum_{x = 0} ^ {l} \sum_{y = 0} ^ {r} {\frac{(-1) ^ x} {x!} \frac{(-1) ^ y} {y!} \frac{(1 - p) ^ {n - 1 - (l - x)}} {(l - x)! (r - y)! (1 - (1 - p) ^ {n - (l - x) - (r - y)})}}Fn(l,r)=l!r!x=0ly=0rx!(1)xy!(1)y(lx)!(ry)!(1(1p)n(lx)(ry))(1p)n1(lx)

G(x,y)=(1−p)n−1−xx!y!(1−(1−p)n−x−y)G(x, y) = \frac{(1 - p) ^ {n - 1 - x}} {x! y! (1 - (1 - p) ^ {n - x - y})}G(x,y)=x!y!(1(1p)nxy)(1p)n1x

Fn(l,r)=l!r!∑x=0l(−1)xx!∑y=0r(−1)yy!G(l−x,r−y)F_n(l, r) = l! r! \sum_{x = 0} ^ {l} {\frac{(-1) ^ x} {x!} \sum_{y = 0} ^ {r} {\frac{(-1) ^ y} {y!} G(l - x, r - y)}}Fn(l,r)=l!r!x=0lx!(1)xy=0ry!(1)yG(lx,ry)

可以在 O(n3)O(n ^ 3)O(n3) 的时间计算出 FnF_nFn


定义 L,R,L+,R+,HL, R, L_{+}, R_{+}, HL,R,L+,R+,H

L(i,l)=∑l^=0l(bil^)(i−bi−1l−l^),R(i,r)=∑r^=0r(ai−bi−1r^)(n−i−ai+bi+1r−r^)L(i, l) = \sum_{\hat{l} = 0} ^ {l} {{b_i \choose \hat{l}} {i - b_i - 1 \choose l - \hat{l}}}, R(i, r) = \sum_{\hat{r} = 0} ^ {r} {{a_i - b_i - 1 \choose \hat{r}} {n - i - a_i + b_i + 1 \choose r - \hat{r}}}L(i,l)=l^=0l(l^bi)(ll^ibi1),R(i,r)=r^=0r(r^aibi1)(rr^niai+bi+1)

L+(i,l)=∑l^=0ll^(bil^)(i−bi−1l−l^),R+(i,r)=∑r^=0rr^(ai−bi−1r^)(n−i−ai+bi+1r−r^)L_{+}(i, l) = \sum_{\hat{l} = 0} ^ {l} {\hat{l} {b_i \choose \hat{l}} {i - b_i - 1 \choose l - \hat{l}}}, R_{+}(i, r) = \sum_{\hat{r} = 0} ^ {r} {\hat{r} {a_i - b_i - 1 \choose \hat{r}} {n - i - a_i + b_i + 1 \choose r - \hat{r}}}L+(i,l)=l^=0ll^(l^bi)(ll^ibi1),R+(i,r)=r^=0rr^(r^aibi1)(rr^niai+bi+1)

H(i,l,r)=∑l^=0l∑r^=0r(ai−1−l^−r^)(bil^)(i−bi−1l−l^)(ai−bi−1r^)(n−i−ai+bi+1r−r^)=(ai−1)L(i,l)R(i,r)−L+(i,l)R(i,r)−L(i,l)R+(i,r)H(i, l, r) = \sum_{\hat{l} = 0} ^ {l} \sum_{\hat{r} = 0} ^ {r} {(a_i - 1 - \hat{l} - \hat{r}) {b_i \choose \hat{l}} {i - b_i - 1 \choose l - \hat{l}} {a_i - b_i - 1 \choose \hat{r}} {n - i - a_i + b_i + 1 \choose r - \hat{r}}} = (a_i - 1) L(i, l) R(i, r) - L_{+}(i, l) R(i, r) - L(i, l) R_{+}(i, r)H(i,l,r)=l^=0lr^=0r(ai1l^r^)(l^bi)(ll^ibi1)(r^aibi1)(rr^niai+bi+1)=(ai1)L(i,l)R(i,r)L+(i,l)R(i,r)L(i,l)R+(i,r)

O(n3)O(n ^ 3)O(n3) 的代价预处理 L,R,L+,R+L, R, L_{+}, R_{+}L,R,L+,R+, 可以在 O(n3)O(n ^ 3)O(n3) 的时间计算出 HHH


现在 AnsAnsAns 就可以在 O(n3)O(n ^ 3)O(n3) 的时间计算出来啦。

Ans=p∑i=1n∑l=0i−1∑r=0n−i(n−l−r−1)!Fn(l,r)(1−p)i−nH(i,l,r)Ans = p \sum_{i = 1} ^ {n} \sum_{l = 0} ^ {i - 1} \sum_{r = 0} ^ {n - i} {(n - l - r - 1)! F_n(l, r) (1 - p) ^ {i - n} H(i, l, r)}Ans=pi=1nl=0i1r=0ni(nlr1)!Fn(l,r)(1p)inH(i,l,r)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

LL pow_mod(LL b, LL p, LL k) {
    LL ret = 1;
    for (; p; p >>= 1) {
        if (p & 1) ret = ret * b % k;
        b = b * b % k;
    }
    return ret;
}

const int maxn = 305;
const int mo = (int)(1e9) + 7;
const LL mo2 = 1LL * mo * mo;
int a[maxn], b[maxn];
LL fac[maxn], ivf[maxn], h[maxn];
LL C[maxn][maxn];
LL g1[maxn][maxn];
LL g2[maxn][maxn];
LL l0[maxn][maxn];
LL l1[maxn][maxn];
LL r0[maxn][maxn];
LL r1[maxn][maxn];
LL pw[maxn];

LL P;

void add(LL& x, LL v) {
    x += v; if (x >= mo2) x -= mo2;
}

void gen () {
    int M = 300;
    fac[0] = ivf[0] = 1;
    for (int i = 1; i <= M; ++i) fac[i] = fac[i - 1] * i % mo;
    ivf[M] = pow_mod(fac[M], mo - 2, mo);
    for (int i = M - 1; i >= 1; --i) ivf[i] = ivf[i + 1] * (i + 1) % mo;
    for (int i = 0; i <= M; ++i) C[i][0] = 1;
    for (int i = 1; i <= M; ++i) for (int j = 1; j <= i; ++j) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mo;

    for (int i = 0; i <= M; ++i) h[i] = ((i & 1) ? mo - 1 : 1) * ivf[i] % mo;
}

void solve () {
    int n, _p, _q;
    cin >> n;
    cin >> _p >> _q;
    P = (1 + (mo -_p) * pow_mod(_q, mo - 2, mo)) % mo;
    pw[0] = 1;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i]; b[i] = 0; pw[i] = pw[i - 1] * P % mo;
        for (int j = 1; j < i; ++j) if (a[j] < a[i]) ++b[i];
    }
    
    memset(g2, 0, sizeof(g2));
    for (int i = 0; i < n; ++i) for (int j = 0; j < n - i; ++j) {
        g1[i][j] = pow_mod(P, n - 1 - i, mo) * ivf[i] % mo * ivf[j] % mo * pow_mod((1 + mo - pow_mod(P, n - i - j, mo)), mo - 2, mo) % mo;
        for (int k = 0; k < n - i - j; ++k) add(g2[i + k][j], g1[i][j] * h[k]);
    }
    for (int i = 0; i < n; ++i) for (int j = 0; j < n - i; ++j) g2[i][j] %= mo;
    memset(g1, 0, sizeof(g1));
    for (int i = 0; i < n; ++i) for (int j = 0; j < n - i; ++j) {
        for (int k = 0; k < n - i - j; ++k) add(g1[i][j + k], g2[i][j] * h[k]);
    }
    for (int i = 0; i < n; ++i) for (int j = 0; j < n - i; ++j) g1[i][j] = g1[i][j] % mo * fac[i] % mo * fac[j] % mo;

    for (int i = 1; i <= n; ++i) for (int j = 0; j < n; ++j) {
        l0[i][j] = l1[i][j] = r0[i][j] = r1[i][j] = 0;
        for (int k = 0; k <= j; ++k) {
            LL v = C[b[i]][k] * C[i - b[i] - 1][j - k] % mo;
            add(l0[i][j], v); add(l1[i][j], v * k);
            v = C[a[i] - b[i] - 1][k] * C[n - i - a[i] + b[i] + 1][j - k] % mo;
            add(r0[i][j], v); add(r1[i][j], v * k);
        }
        l0[i][j] %= mo; l1[i][j] %= mo; r0[i][j] %= mo; r1[i][j] %= mo;
    }
    
    LL ret = 0;
    for (int i = 1; i <= n; ++i) for (int j = 0; j < i; ++j) for (int k = 0; k <= n - i; ++k) {
        LL w = (l0[i][j] * r0[i][k] % mo * (a[i] - 1) + (mo - l1[i][j]) * r0[i][k] + (mo - l0[i][j]) * r1[i][k]) % mo;
        add(ret, fac[n - j - k - 1] * g1[j][k] % mo * pw[i] % mo * w);
    }

    ret = ret % mo * pow_mod(pow_mod(P, n, mo), mo - 2, mo) % mo * (1 + mo - P) % mo;
    cout << (ret + 1) % mo << endl;
}

int main () {
    gen();
    ios::sync_with_stdio(false);
    int T; cin >> T;
    while (T--) solve();
    return 0;
}

I. Delighful Formulas

根据题意列出式子:

Ans=∑i=1N[gcd⁡(i,N)=1]∑j=1ijKAns = \sum_{i = 1} ^ {N} {[\gcd(i, N) = 1] \sum_{j = 1} ^ {i} {j ^ K}}Ans=i=1N[gcd(i,N)=1]j=1ijK

莫比乌斯反演:

Ansamp;=∑d∣Nμ(d)∑i=1N[d∣i]∑j=1ijKamp;=∑d∣Nμ(d)∑i=1Nd∑j=1idjK\begin{aligned} Ans &amp; = \sum_{d \mid N} {\mu(d) \sum_{i = 1} ^ {N} {[d \mid i] \sum_{j = 1} ^ {i} {j ^ K}}} \\ &amp; = \sum_{d \mid N} {\mu(d) \sum_{i = 1} ^ {\frac{N} {d}} \sum_{j = 1} ^ {id} {j ^ K}}\end{aligned}Ansamp;=dNμ(d)i=1N[di]j=1ijKamp;=dNμ(d)i=1dNj=1idjK

定义 FFF

Fp(N)=∑i=1NipF_p(N) = \sum_{i = 1} ^ {N} {i ^ p}Fp(N)=i=1Nip

显然 FpF_pFpp+1p + 1p+1 阶多项式:

Fp(N)=∑i=0p+1ap,iNiF_p(N) = \sum_{i = 0} ^ {p + 1} {a_{p, i} N ^ i}Fp(N)=i=0p+1ap,iNi

利用 FFF 化简原式:

Ansamp;=∑d∣Nμ(d)∑i=1NdFK(id)amp;=∑d∣Nμ(d)∑i=1Nd∑j=0K+1aK,j(id)jamp;=∑d∣Nμ(d)∑j=0K+1aK,jdj∑i=1Ndijamp;=∑d∣Nμ(d)∑j=0K+1aK,jdjFj(Nd)amp;=∑d∣Nμ(d)∑j=0K+1aK,jdj∑k=0j+1aj,k(Nd)kamp;=∑d∣Nμ(d)∑i=−1K+1di∑j=0K+1∑k=0j+1[j−k=i]aK,jaj,kNk\begin{aligned} Ans &amp; = \sum_{d \mid N} {\mu(d) \sum_{i = 1} ^ {\frac{N} {d}} {F_K(id)}} \\ &amp; = \sum_{d \mid N} {\mu(d) \sum_{i = 1} ^ {\frac{N} {d}} \sum_{j = 0} ^ {K + 1} {a_{K, j} (id) ^ j}} \\ &amp; = \sum_{d \mid N} {\mu(d) \sum_{j = 0} ^ {K + 1} {a_{K, j} d ^ j \sum_{i = 1} ^ {\frac{N} {d}} {i ^ j}}} \\ &amp; = \sum_{d \mid N} {\mu(d) \sum_{j = 0} ^ {K + 1} {a_{K, j} d ^ j F_j(\frac{N}{d})}} \\ &amp; = \sum_{d \mid N} {\mu(d) \sum_{j = 0} ^ {K + 1} {a_{K, j} d ^ j \sum_{k = 0} ^ {j + 1} {a_{j, k} (\frac{N} {d}) ^ k}}} \\ &amp; = \sum_{d \mid N} {\mu(d) \sum_{i = -1} ^ {K + 1} {d ^ i \sum_{j = 0} ^ {K + 1} \sum_{k = 0} ^ {j + 1} {[j - k = i] a_{K, j} a_{j, k} N ^ k}}} \end{aligned}Ansamp;=dNμ(d)i=1dNFK(id)amp;=dNμ(d)i=1dNj=0K+1aK,j(id)jamp;=dNμ(d)j=0K+1aK,jdji=1dNijamp;=dNμ(d)j=0K+1aK,jdjFj(dN)amp;=dNμ(d)j=0K+1aK,jdjk=0j+1aj,k(dN)kamp;=dNμ(d)i=1K+1dij=0K+1k=0j+1[jk=i]aK,jaj,kNk

定义 GGG

Gi=∑j=0K+1∑k=0j+1[j−k=i]aK,jaj,kNkG_i = \sum_{j = 0} ^ {K + 1} \sum_{k = 0} ^ {j + 1} {[j - k = i] a_{K, j} a_{j, k} N ^ k}Gi=j=0K+1k=0j+1[jk=i]aK,jaj,kNk

利用 GGG 化简原式:

Ansamp;=∑d∣Nμ(d)∑i=−1K+1diGiamp;=∑i=−1K+1Gi∑d∣Nμ(d)diamp;=∑i=−1K+1Gi∏p∣N(1−pi)\begin{aligned} Ans &amp; = \sum_{d \mid N} {\mu(d) \sum_{i = -1} ^ {K + 1} {d ^ i G_i}} \\ &amp; = \sum_{i = -1} ^ {K + 1} {G_i \sum_{d \mid N} {\mu(d) d ^ i}} \\ &amp; = \sum_{i = -1} ^ {K + 1} {G_i \prod_{p \mid N} {(1 - p ^ i)}} \end{aligned}Ansamp;=dNμ(d)i=1K+1diGiamp;=i=1K+1GidNμ(d)diamp;=i=1K+1GipN(1pi)

如果我们能快速计算出 GGG,就可以在 O(MK)O(MK)O(MK) 的时间计算答案,其中 MMM 为质因子个数。

GGG 用伯努利数展开,可以发现是卷积的形式,直接 NTT,时间复杂度 O(Klog⁡K)O(K \log K)O(KlogK)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

LL pow_mod(LL b, LL p, LL k) {
    LL ret = 1;
    for (; p; p >>= 1) {
        if (p & 1) ret = ret * b % k;
        b = b * b % k;
    }
    return ret;
}

const int maxn = (int)(3e5) + 5;
const int mo = 998244353;
const int rt = 3;
LL fac[maxn], ivf[maxn], B[maxn];
LL T[2][maxn], P[maxn], Q[maxn];
int K;

inline LL C(int n, int m) {
    return fac[n] * ivf[m] % mo * ivf[n - m] % mo;
}

void ntt(LL a[], int n, int d = 1) {
	for (int i = (n >> 1), j = 1; j < n; ++j) {
		if (i < j) swap(a[i], a[j]);
		int k; for (k = (n >> 1); i & k; i ^= k, k >>= 1); i ^= k;
	}
	for (int m = 2; m <= n; m <<= 1) {
		LL w = pow_mod(rt, 1LL * (mo - 1) / m * (mo - 1 + d), mo);
		for (int i = 0; i < n; i += m) {
			LL s = 1;
			for (int j = i; j < i + (m >> 1); ++j) {
				LL u = a[j], v = a[j + (m >> 1)] * s % mo;
				a[j] = u + v; a[j + (m >> 1)] = u - v; s = s * w % mo;
				if (a[j] >= mo) a[j] -= mo; 
				if (a[j + (m >> 1)] < 0) a[j + (m >> 1)] += mo;

			}
		}
	}
	if (d == -1) {
		LL xinv = pow_mod(n, mo - 2, mo);
		for (int i = 0; i < n; ++i) a[i] = a[i] * xinv % mo;
	}
}

void inverse(LL a[], LL b[], LL c[], int n) {
    int stk[22];
    for (stk[0] = 0; n > 1; stk[++stk[0]] = n, n = (n + 1) >> 1);
    b[0] = pow_mod(a[0], mo - 2, mo);
    for (int i = stk[0]; i >= 1; --i) {
        int m = stk[i], len = 0;
        while ((1 << len) < (m << 1)) ++len;
        for (int i = 0; i < m; ++i) c[i] = a[i];
        for (int i = m; i < (1 << len); ++i) c[i] = 0;
        ntt(b, 1 << len); ntt(c, 1 << len);
        for (int i = 0; i < (1 << len); ++i) b[i] = ((mo - c[i]) * b[i] + 2) % mo * b[i] % mo;
        ntt(b, 1 << len, -1);
        for (int i = m; i < (1 << len); ++i) b[i] = 0;
    }
}

void gen() {
    fac[0] = ivf[0] = 1;
    int M = (int)(1e5) + 1;
    for (int i = 1; i <= M; ++i) fac[i] = fac[i - 1] * i % mo;
    ivf[M] = pow_mod(fac[M], mo - 2, mo);
    for (int i = M - 1; i >= 1; --i) ivf[i] = ivf[i + 1] * (i + 1) % mo;
    for (int i = 0; i < M; ++i) T[0][i] = ivf[i + 1];
    inverse(T[0], B, T[1], M);
    for (int i = 0; i < M; ++i) B[i] = B[i] * fac[i] % mo;
    ++B[1];
}

int ai[25], bi[25];

void solve() {
    LL N = 1; int M;
    scanf("%d%d", &K, &M);
    for (int i = 1; i <= M; ++i) {
        scanf("%d%d", &ai[i], &bi[i]); N = N * pow_mod(ai[i], bi[i], mo) % mo;
    }
    memset(T, 0, sizeof(T));
    for (int i = 0; i <= K; ++i) T[0][i] = ivf[K - i] * B[K - i] % mo;
    for (int i = 0; i <= K + 1; ++i) T[1][i] = ivf[i + 1] * pow_mod(N, i + 1, mo) % mo;
    reverse(T[1], T[1] + K + 2);
    int len = 0;
    while ((1 << len) < ((K + 1) << 1)) ++len;
    ntt(T[0], 1 << len); ntt(T[1], 1 << len);
    for (int i = 0; i < (1 << len); ++i) T[0][i] = T[0][i] * T[1][i] % mo;
    ntt(T[0], 1 << len, -1);
    for (int i = K; i <= 2 * K + 1; ++i) {
        int dt = i - K; P[dt] = T[0][i] * fac[K] % mo * B[dt] % mo * ivf[dt] % mo;
    }
    for (int i = 0; i <= K + 1; ++i) Q[i] = 1;
    for (int i = 1; i <= M; ++i) {
        LL p = ai[i] % mo; LL now = pow_mod(p, mo - 2, mo);
        for (int j = 0; j <= K + 1; ++j) Q[j] = Q[j] * (1 + mo - now) % mo, now = now * p % mo;
    }
    LL ret = 0;
    for (int i = 0; i <= K + 1; ++i) (ret += P[i] * Q[i]) %= mo;
    printf("%lld\n", ret);
}

int main () {
    gen();
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

J. Let Sudoku Rotate

搜索加可行性剪枝即可通过。由于数独限制较强,剪枝效果良好。

#include <bits/stdc++.h>
using namespace std;

int ord(char c) {
    if (isdigit(c)) return c - '0';
    return c - 'A' + 10;
}

char str(int c) {
    if (c < 10) return c + '0';
    return c + 'A' - 10;
}

int r[16][16], c[16][16];
int a[16][16];
int b[4][4];
char s[16];
int ret;

void add(int ip, int jp, int v) {
    for (int i = ip * 4; i < (ip + 1) * 4; ++i) {
        for (int j = jp * 4; j < (jp + 1) * 4; ++j) {
            r[i][a[i][j]] += v; c[j][a[i][j]] += v;
        }
    }
}

bool rot(int ip, int jp) {
    for (int i = ip * 4; i < (ip + 1) * 4; ++i) {
        for (int j = jp * 4; j < (jp + 1) * 4; ++j) {
            --r[i][a[i][j]]; --c[j][a[i][j]];
            b[j - jp * 4][(ip + 1) * 4 - i - 1] = a[i][j];
        }
    }
    bool succ = true;
    for (int i = ip * 4; i < (ip + 1) * 4; ++i) {
        for (int j = jp * 4; j < (jp + 1) * 4; ++j) {
            a[i][j] = b[i - ip * 4][j - jp * 4];
            if ((++r[i][a[i][j]] > 1) || (++c[j][a[i][j]] > 1)) succ = false;
        }
    }
    return succ;
}

void dfs(int ip, int jp, int now) {
	cout<<ip<<" "<<jp<<" "<<now<<" "<<ret<<endl; 
    if (ip == 4 && jp == 0) {
        ret = min(ret, now);
        return;
    }
    add(ip, jp, 1); if (now >= ret) return;
    for (int i = 1; i <= 4; ++i) {
        if (rot(ip, jp)) dfs(jp == 3 ? ip + 1 : ip, jp == 3 ? 0 : jp + 1, now + (i & 3));
    }
    add(ip, jp, -1);
}

void solve () {
    for (int i = 0; i < 16; ++i) {
        scanf("%s", s);
        for (int j = 0; j < 16; ++j) a[i][j] = ord(s[j]);
    }
    memset(r, 0, sizeof(r)); memset(c, 0, sizeof(c));
    ret = 16 * 4; dfs(0, 0, 0);
    printf("%d\n", ret);
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

K. Expression in Memories

注意在类似 +0? 的情况下,? 须被替换为 +*,其余情况直接将 ? 替换为非零数字就好。替换完成后判断一下是否合法。

#include <bits/stdc++.h>
using namespace std;

int T; string res;
int main()
{
    ios :: sync_with_stdio(0);
    cin >> T;
    while (T --)
    {
        cin >> res;
        int flag = 1;
        for (int i = 1; i < res.size(); ++ i)
            if (res[i] == '?' && res[i - 1] == '0' && (i == 1 || res[i - 2] == '+' || res[i - 2] == '*'))
                res[i] = '+';
        for (int i = 0; i < res.size(); ++ i)
            if (res[i] == '?') res[i] = '1';
        if (res[0] == '+' || res[0] == '*') flag = 0;
        if (res[res.size() - 1] == '+' || res[res.size() - 1] == '*') flag = 0;
        for (int i = 0; i < res.size() - 1; ++ i)
            if ((res[i] == '+' || res[i] == '*') && (res[i + 1] == '+' || res[i + 1] == '*'))
                flag = 0;
        for (int i = 0; i < res.size() - 1; ++ i)
            if ((i == 0 || res[i - 1] == '+' || res[i - 1] == '*') && (res[i] == '0') && (res[i + 1] != '+' && res[i + 1] != '*'))
                flag = 0;
        if (flag) cout << res << endl;
        else cout << "IMPOSSIBLE" << endl;
    }
}

L. Graph Theory Homework

容易证明 ⌊a⌋+⌊b⌋≥⌊a+b⌋\lfloor \sqrt{a} \rfloor + \lfloor \sqrt{b} \rfloor \ge \lfloor \sqrt{a + b} \rfloora+ba+b,进而可以证明边权满足三角不等式,故直接从 111 走到 nnn 就是最优的。

#include <bits/stdc++.h>
using namespace std;

void solve () {
    int n, a; cin >> n;
    for (int i = 1; i <= n; ++i) {
        int x; cin >> x;
        if (i == 1) a = x;
        if (i == n) cout << (int)sqrt(fabs(x - a) + 1e-3) << endl;
    }
}

int main () {
    ios::sync_with_stdio(false);
    int T; cin >> T;
    while (T--) solve();
    return 0;
}
用户表: Users +-------------+---------+ | Column Name | Type | +-------------+---------+ | user_id | int | | user_name | varchar | +-------------+---------+ user_id 是该表的主键(具有唯一值的列)。 该表中的每行包括用户 ID 和用户名。 注册表: Register +-------------+---------+ | Column Name | Type | +-------------+---------+ | contest_id | int | | user_id | int | +-------------+---------+ (contest_id, user_id) 是该表的主键(具有唯一值的列的组合)。 该表中的每行包含用户的 ID 和他们注册的赛事。 编写解决方案统计出各赛事的用户注册百分率,保留两位小数。 返回的结果表按 percentage 的 降序 排序,若相同则按 contest_id 的 升序 排序。 返回结果如下示例所示。 示例 1: 输入: Users 表: +---------+-----------+ | user_id | user_name | +---------+-----------+ | 6 | Alice | | 2 | Bob | | 7 | Alex | +---------+-----------+ Register 表: +------------+---------+ | contest_id | user_id | +------------+---------+ | 215 | 6 | | 209 | 2 | | 208 | 2 | | 210 | 6 | | 208 | 6 | | 209 | 7 | | 209 | 6 | | 215 | 7 | | 208 | 7 | | 210 | 2 | | 207 | 2 | | 210 | 7 | +------------+---------+ 输出: +------------+------------+ | contest_id | percentage | +------------+------------+ | 208 | 100.0 | | 209 | 100.0 | | 210 | 100.0 | | 215 | 66.67 | | 207 | 33.33 | +------------+------------+ 解释: 所有用户都注册了 208、209 和 210 赛事,因此这些赛事的注册率为 100% ,我们按 contest_id 的降序排序加入结果表中。 Alice 和 Alex 注册了 215 赛事,注册率为 ((2/3) * 100) = 66.67% Bob 注册了 207 赛事,注册率为 ((1/3) * 100) = 33.33%
最新发布
03-18
<think>好的,我需要解决这个问题。首先,我得仔细理解题目要求。题目是要统计每个赛事的用户注册百分比,保留两位小数,然后按百分比降序,如果相同的话按contest_id升序排列。给出的示例可以帮助我验证思路是否正确。 首先,我需要确定如何计算每个赛事的百分比。百分比应该是该赛事注册的用户数除以总用户数,然后乘以100,保留两位小数。总用户数来自Users表,因为每个用户可能注册个赛事,但总用户数是固定的,不管他们注册了少赛事。比如示例中Users表有3个用户,所以每个赛事的注册用户数除以3,再乘以100得到百分比。 那首先,我需要从Users表中获取总用户数。可以用COUNT(DISTINCT user_id)来统计总人数。然后,对于Register表中的每个contest_id,计算该赛事的注册用户数,再除以总用户数,得到百分比。 接下来是SQL的写法。可能需要子查询或者JOIN来结合这两个表的数据。不过,实际上Register表中每个contest_id对应的用户数可以用GROUP BY contest_id来计算每个赛事的用户数量。然后将这个数量除以总用户数,乘以100,然后四舍五入到两位小数。 那步骤大概是: 1. 计算总用户数:SELECT COUNT(*) FROM Users; 2. 对Register表按contest_id分组,统计每个分组的用户数; 3. 将每个分组的用户数除以总用户数,得到百分比; 4. 按百分比降序和contest_id升序排序。 需要注意的是,总用户数可能需要作为子查询或者变量来处理。例如,在SELECT语句中可能需要用到子查询来获取总用户数。 比如,可以这样写: SELECT contest_id, ROUND( (COUNT(user_id) / (SELECT COUNT(*) FROM Users)) * 100, 2 ) AS percentage FROM Register GROUP BY contest_id ORDER BY percentage DESC, contest_id ASC; 但这里有个问题,是否需要考虑重复的user_id在同一个赛事中的情况?不过根据题目,Register表的主键是(contest_id, user_id),所以每个用户在一个赛事中只能注册一次。因此,每个分组的COUNT(user_id)就是该赛事的不同用户数。而总用户数是Users表中的总人数,不管他们是否注册了任何赛事。例如,如果有用户没有注册任何赛事,总用户数仍然包括他们,但该用户不会被统计在任何赛事的注册数中。 比如示例中的Users表有3个用户,每个赛事的注册用户数是该赛事有少不同的user_id记录,比如contest_id 207在Register表中只有user_id 2,所以注册数是1,总用户数是3,所以百分比是33.33%。 那上述的SQL是否正确? 测试一下示例的情况。示例中的Register表对于contest_id=208,有三个不同的user_id吗?根据输入: Register表中contest_id=208的有user_id=2、6、7?比如输入中的Register表: 208的条目有: 208 | 2 208 | 6 208 | 7 所以在contest_id=208的分组中,COUNT(user_id)是3。总用户数是3,所以3/3=1,即100%。和示例的输出一致。 那上述的SQL应该能正确计算。 但是,当使用COUNT(user_id)的时候,如果某个用户没有注册任何赛事,他们不会被统计进来,但总用户数仍然会包含他们。例如,如果Users表中有一个用户没有在Register表中出现,那总用户数是正确的,而每个赛事的百分比是注册用户数/总用户数,包括这个未被注册的用户。这种情况下,计算是正确的。 那现在,这个SQL是否正确? 是的。因为总用户数是来自Users表的总数,而每个赛事的注册用户数是该赛事中的用户数,所以除法是正确的。 然后,ROUND函数保留两位小数。比如在示例中的215赛事,注册用户数是2(用户6和7),总用户数是3,所以2/3=0.666666…,乘以100是66.666…,四舍五入到两位小数是66.67,符合示例。 那这个时候,这个SQL应该是正确的。然后需要将结果按percentage降序,contest_id升序排列。 但是示例的输出中,208、209、210都是100%,按contest_id降序排列吗?例如,输出中的顺序是208、209、210、215、207。但是contest_id的值是208,209,210的数值是递增的,所以按contest_id升序排列的话,这三个的排列顺序应该是208、209、210。这与示例的输出中的顺序一致。所以ORDER BY子句中的第二部分是contest_id ASC,这正确。 所以整个SQL应该是: SELECT contest_id, ROUND( (COUNT(*) * 100.0) / (SELECT COUNT(*) FROM Users), 2 ) AS percentage FROM Register GROUP BY contest_id ORDER BY percentage DESC, contest_id ASC; 这里需要注意,乘以100.0而不是100,以确保得到浮点数的结果,避免整数除法的问题。例如,在MySQL中,如果分母是整数,那么除法会得到整数部分。所以需要使用浮点数运算。 比如,在示例中的207赛事,注册用户数是1,总用户数是3。1/3是0.333…,乘以100是33.333…。保留两位小数是33.33。如果使用整数运算,可能得到33.00或者错误的数值。所以必须确保计算时使用浮点数。 因此,在SQL中,将分子或分母中的一个转换为浮点数,例如乘以100.0,或者将COUNT(*)转换为小数。 所以,正确的写法应该是: ROUND( (COUNT(user_id) * 100.0) / total_users, 2 ) 其中total_users是用户总数。或者,在MySQL中,可能不需要,但为了保险,乘以100.0是正确的。 现在,在编写这个SQL时,是否应该使用COUNT(*),或者COUNT(user_id)?在GROUP BY contest_id之后,每个分组的行数等于该赛事中的注册用户数目,因为每个用户只能注册一次同一赛事。所以COUNT(*)和COUNT(user_id)的结果是一样的。所以两种写法都可以。 因此,最终的SQL语句应该是: SELECT contest_id, ROUND( (COUNT(*) * 100.0) / (SELECT COUNT(*) FROM Users), 2 ) AS percentage FROM Register GROUP BY contest_id ORDER BY percentage DESC, contest_id; 这样应该就能得到正确的结果。然后,按照示例的输出,验证是否正确。 测试示例中的输入: Users表有3个用户。Register表中的数据: contest_id 215有用户6和7,即COUNT(*)为2。2*100/3=66.666...,四舍五入到两位小数是66.67。符合示例的输出。 contest_id 207有一个用户,计算1*100/3=33.33,正确。 其他三个赛事208、209、210的注册用户数都是3,所以3/3=100%正确。 所以SQL的输出应该与示例一致。 那这个应该就是正确的解决方案了。</think>要解决这个问题,我们需要统计每个赛事的用户注册百分比,并按特定规则排序结果。以下是详细的步骤说明和解决方案: ### 方法思路 1. **计算总用户数**:从 `Users` 表中获取所有用户的总数。 2. **统计各赛事注册用户数**:在 `Register` 表中按 `contest_id` 分组,统计每个赛事的注册用户数。 3. **计算百分比**:将每个赛事的注册用户数除以总用户数,乘以 100 并保留两位小数。 4. **排序结果**:按百分比降序排序,若百分比相同则按赛事 ID 升序排序。 ### 解决代码 ```sql SELECT contest_id, ROUND( (COUNT(*) * 100.0) / (SELECT COUNT(*) FROM Users), 2 ) AS percentage FROM Register GROUP BY contest_id ORDER BY percentage DESC, contest_id ASC; ``` ### 代码解释 1. **总用户数计算**:通过子查询 `(SELECT COUNT(*) FROM Users)` 获取 `Users` 表中的总用户数。 2. **分组统计注册用户数**:使用 `GROUP BY contest_id` 对 `Register` 表按赛事分组,`COUNT(*)` 统计每个赛事的注册用户数。 3. **计算百分比**:将每个分组的注册用户数乘以 100.0(确保浮点数运算),除以总用户数,并用 `ROUND` 函数保留两位小数。 4. **排序规则**:结果首先按百分比降序 (`percentage DESC`) 排列,若百分比相同则按赛事 ID 升序 (`contest_id ASC`) 排列。 这种方法确保准确计算各赛事的注册率,并按要求排序结果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值