牛客挑战赛30 简要题解

牛客挑战赛30

T1

随便枚举三个位置,然后二维前缀和即可。

# include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
int sum[505][505];
int n, a[505];
ll ans;
 
int main() {
    int i, j, k;
    scanf("%d", &n);
    for (i = 1; i <= n; ++i) scanf("%d", &a[i]);
    for (i = n; i; --i) {
        for (j = 1; j <= n; ++j) sum[i][j] = sum[i + 1][j];
        for (j = 1; j <= a[i]; ++j) ++sum[i][j];
    }
    for (i = 1; i <= n; ++i)
        for (j = i + 1; j <= n; ++j)
            for (k = j + 1; k <= n; ++k)
                if (a[i] < a[k] && a[i] < a[j] && a[k] < a[j]) ans += sum[k + 1][a[j] + 1];
    printf("%lld\n", ans);
    return 0;
}

T2

枚举中位数,把小的设为 \(-1\),大的设为 \(1\),然后前缀和找为 \(0\) 的区间。
被肥鸡卡常了

//time limit exceeded
# include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
const int maxn(10005);
const int mod(1e9 + 7);
 
inline void Inc(int &x, const int y) {
    x = x + y >= mod ? x + y - mod : x + y;
}
 
inline void Dec(int &x, const int y) {
    x = x - y < 0 ? x - y + mod : x - y;
}
 
inline int Add(int x, const int y) {
    return x + y >= mod ? x + y - mod : x + y;
}
 
inline int Sub(int x, const int y) {
    return x - y < 0 ? x - y + mod : x - y;
}
 
inline int Pow(ll x, int y) {
    ll ret = 1;
    for (; y; y >>= 1, x = x * x % mod)
        if (y & 1) ret = ret * x % mod;
    return ret;
}
 
int n, p[maxn], pw[maxn], pw2[maxn], t[maxn << 1], ans, s[maxn];
 
int main() {
    int i, j, ret;
    scanf("%d%d", &n, &pw[1]), pw[0] = 1;
    for (i = 1; i <= n; ++i) scanf("%d", &p[i]);
    for (i = 2; i <= n; ++i) pw[i] = (ll)pw[i - 1] * pw[1] % mod;
    for (i = 0; i <= n; ++i) pw2[i] = Pow(pw[i], n);
    for (i = 1; i <= n + n; ++i) {
        for (j = 1; j <= n; ++j)
            if (p[j] + p[j] < i) s[j] = -1;
            else if (p[j] + p[j] > i) s[j] = 1;
            else s[j] = 0;
        t[maxn] = 1, ret = 0;
        for (j = 1; j <= n; ++j) {
            s[j] += s[j - 1];
            Inc(ret, (ll)t[maxn + s[j]] * pw[j] % mod);
            Inc(t[s[j] + maxn], pw2[j - 1]);
        }
        Inc(ans, (ll)ret * i % mod);
        for (j = 1; j <= n; ++j) t[s[j] + maxn] = 0;
    }
    printf("%d\n", ans);
    return 0;
}

T3

枚举一个根然后树形 \(DP\),转移是一个组合数。
优化的话就是换根 \(DP\)

# include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
const int maxn(1e5 + 5);
const int mod(998244353);
 
inline void Inc(int &x, const int y) {
    x = x + y >= mod ? x + y - mod : x + y;
}
 
inline void Dec(int &x, const int y) {
    x = x - y < 0 ? x - y + mod : x - y;
}
 
inline int Add(int x, const int y) {
    return x + y >= mod ? x + y - mod : x + y;
}
 
inline int Sub(int x, const int y) {
    return x - y < 0 ? x - y + mod : x - y;
}
 
inline int Pow(ll x, int y) {
    ll ret = 1;
    for (; y; y >>= 1, x = x * x % mod)
        if (y & 1) ret = ret * x % mod;
    return ret;
}
 
struct Edge {  int to, next;  };
 
int n, first[maxn], size[maxn], fac[maxn], inv[maxn], f[maxn], ans, cnt;
Edge edge[maxn << 1];
 
inline int C(int x, int y) {
    if (x < y) return 0;
    return (ll)fac[x] * inv[y] % mod * inv[x - y] % mod;
}
 
inline void AddEdge(int u, int v) {
    edge[cnt] = (Edge){v, first[u]}, first[u] = cnt++;
    edge[cnt] = (Edge){u, first[v]}, first[v] = cnt++;
}
 
void Dfs1(int u, int ff) {
    int e, v;
    size[u] = f[u] = 1;
    for (e = first[u]; ~e; e = edge[e].next)
        if ((v = edge[e].to) ^ ff) {
            Dfs1(v, u);
            f[u] = (ll)f[u] * C(size[u] + size[v] - 1, size[v]) % mod * f[v] % mod;
            size[u] += size[v];
        }
}
 
void Dfs2(int u, int ff) {
    int e, v;
    Inc(ans, f[u]);
    for (e = first[u]; ~e; e = edge[e].next)
        if ((v = edge[e].to) ^ ff) {
            f[u] = (ll)f[u] * Pow((ll)C(size[u] - 1, size[v]) * f[v] % mod, mod - 2) % mod;
            size[u] -= size[v], size[v] += size[u];
            f[v] = (ll)f[v] * C(size[v] - 1, size[u]) % mod * f[u] % mod;
            Dfs2(v, u);
            f[v] = (ll)f[v] * Pow((ll)C(size[v] - 1, size[u]) * f[u] % mod, mod - 2) % mod;
            size[v] -= size[u], size[u] += size[v];
            f[u] = (ll)f[u] * C(size[u] - 1, size[v]) % mod * f[v] % mod;
        }
}
 
int main() {
    int i, u, v;
    memset(first, -1, sizeof(first));
    scanf("%d", &n);
    for (i = 1; i < n; ++i) scanf("%d%d", &u, &v), AddEdge(u, v);
    fac[0] = fac[1] = inv[0] = inv[1] = 1;
    for (i = 2; i <= n; ++i) inv[i] = (ll)inv[mod % i] * (mod - mod / i) % mod;
    for (i = 2; i <= n; ++i) fac[i] = (ll)fac[i - 1] * i % mod, inv[i] = (ll)inv[i] * inv[i - 1] % mod;
    Dfs1(1, 0), Dfs2(1, 0);
    printf("%d\n", ans);
    return 0;
}

T4

枚举最终的牌和第一种的数目

\[\sum_{i=0}^{S}\sum_{j=l}^{r}\binom{n+j-1}{j}\binom{m+i-j-1}{i-j} \]

运用交换求和顺序以及组合恒等式可以得到

\[\sum_{i=l}^{r}\binom{n+i-1}{n-1}\binom{S-i+m}{m} \]

考虑求

\[\sum_{i=0}^{x}\binom{n+i-1}{n-1}\binom{S-i+m}{m} \]

可以看成是枚举一个点 \((i,n-1),i\in [0,x]\) 作为中转站,从 \((0,0)\)\((i,n-1)\) 再到 \((i,n)\) 再到 \((S,n+m)\) 的格路问题。
也就是 \((0,0)\)\((S,n+m)\) 必须经过端点为 \((0,n),(x,n)\) 的线段。
这个可以用总方案数减去经过 \((x,i)\)\((x+1,i)\) 之间的边的方案数,其中 \(i\in[1,n-1]\)
注意这里是枚举 \(n\),所以可以直接算。

# include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
const int maxn(2e7 + 5);
const int mod(998244353);
 
inline void Inc(int &x, const int y) {   x = x + y >= mod ? x + y - mod : x + y;  }
 
inline void Dec(int &x, const int y) {  x = x - y < 0 ? x - y + mod : x - y;  }
 
inline int Add(const int x, const int y) {  return x + y >= mod ? x + y - mod : x + y;  }
 
inline int Sub(const int x, const int y) {  return x - y < 0 ? x - y + mod : x - y;  }
 
inline int Pow(ll x, int y) {
    ll ret = 1;
    for (; y; y >>= 1, x = x * x % mod)
        if (y & 1) ret = ret * x % mod;
    return ret;
}
 
int n, m, s, l, r, f[maxn], g[maxn], inv[maxn];
 
inline int Calc(int x) {
    int i, ret = 0, y = s - x - 1;
    if (y < 0) return 0;
    f[0] = g[0] = 1;
    for (i = 1; i < n; ++i) f[i] = (ll)f[i - 1] * inv[i] % mod * (x + i) % mod;
    for (i = 1; i <= m + n; ++i) g[i] = (ll)g[i - 1] * inv[i] % mod * (y + i) % mod;
    for (i = 0; i < n; ++i) Dec(ret, (ll)f[i] * g[m + n - i] % mod);
    return ret;
}
 
int main() {
    int i;
    scanf("%d%d%d%d%d", &n, &m, &s, &l, &r);
    inv[0] = inv[1] = 1;
    for (i = 2; i <= n + m; ++i) inv[i] = (ll)inv[mod % i] * (mod - mod / i) % mod;
    printf("%d\n", Sub(Calc(r), Calc(l - 1)));
    return 0;
}

T5

实际就是仙人掌上的所有路径的总长度对 \(Q\) 取模的值。
建立圆方树,树 \(dp\),处理出每个点子树内到它的路径数目。
树边贡献直接算,环边贡献都是一样的,可以考虑枚举端点,也很好算。
两遍树 \(dp\) 即可。

主要代码:头文件太长了

int mod;

inline void inc(int &x, const int y) {  x = x + y >= mod ? x + y - mod : x + y;  }

inline void dec(int &x, const int y) {  x = x - y < 0 ? x - y + mod : x - y;  }

inline int add(const int x, const int y) {  return x + y >= mod ? x + y - mod : x + y;  }

inline int sub(const int x, const int y) {  return x - y < 0 ? x - y + mod : x - y;  }

inline int fpow(ll x, int y) {
	ll ret = 1;
	for (; y; y >>= 1, x = x * x % mod)
		if (y & 1) ret = ret * x % mod;
	return ret;
}

const int maxn(2e5 + 5);

int n, m, tot, dfn[maxn], low[maxn], idx, sta[maxn], top, ans, bel[maxn];
int sum[maxn], fa[maxn], deep[maxn], type[maxn], cir[maxn], f[maxn];
vii graph[maxn];
vi tree[maxn];

inline void addgraph(int u, int v, int w) {  graph[u].pb(mp(v, w)), graph[v].pb(mp(u, w));  }

inline void addtree(int u, int v) {  tree[u].pb(v), tree[v].pb(u);  }

void dfs(int u) {
	int v, s;
	dfn[u] = low[u] = ++idx, sta[++top] = u;
	for (auto cur : graph[u])
		if (!dfn[v = cur.first]) {
			fa[v] = u, deep[v] = add(deep[u], cur.second);
			dfs(v), cmin(low[u], low[v]);
			if (low[v] >= dfn[u]) {
				++tot, s = 0;
				do {
					++s, addtree(tot, sta[top--]);
					bel[sta[top + 1]] = tot;
				} while (sta[top + 1] ^ v);
				addtree(tot, u);
				if (s == 1) type[tot] = 1, sum[tot] = cur.second;
			}
		}
		else {
			cmin(low[u], dfn[v]);
			if (low[v] == dfn[u]) sum[bel[v]] = add(cur.second, sub(deep[v], deep[u]));
		}
}

void dfs1(int u, int ff) {
	int val = type[u] ? 1 : 2;
	f[u] = u <= n;
	for (int v : tree[u]) if (v ^ ff) dfs1(v, u), inc(f[u], (ll)f[v] * val % mod);
}

void dfs2(int u, int ff) {
	int val = type[u] ? 1 : 2, ret = 0, s = 0;
	if (u > n) {
		for (int v : tree[u]) inc(s, (ll)ret * f[v] % mod), inc(ret, f[v]);
		inc(ans, (ll)s * sum[u] % mod);
	}
	for (int v : tree[u])
		if (v ^ ff) {
			dec(f[u], (ll)f[v] * val % mod);
			inc(f[v], (ll)f[u] * (type[v] ? 1 : 2) % mod);
			dfs2(v, u);
			dec(f[v], (ll)f[u] * (type[v] ? 1 : 2) % mod);
			inc(f[u], (ll)f[v] * val % mod);
		}
}

int main() {
	int i, u, v, w;
	read(n, m, mod), tot = n;
	for (i = 1; i <= n; ++i) type[i] = 1;
	for (i = 1; i <= m; ++i) read(u, v, w), addgraph(u, v, w);
	dfs(1), dfs1(1, 0), dfs2(1, 0), print(ans);
	return 0;
}

T6

如果我们能够构造一个多项式 \(H(x)=\prod_{i有二次剩余}(x−i)^k\),那么再将该多项式与 \(F(x)\)\(Gcd\) 可以得到答案多项式。
二次剩余有一个判断式 \(x^{\frac{p−1}{2}}−1\equiv 0(mod~p)\)
所以 \(x^{\frac{p−1}{2}}−1=\prod_{i有二次剩余}(x-i)(mod~p)\)
只需要先将 \((x^{\frac{p−1}{2}}−1)^k\)\(F(x)\) 取模,接下来两个多项式的次数就都是 \(O(n)\) 级别的。
可以利用快速幂算出 \(x^{\frac{p−1}{2}}\)\((x^{\frac{p−1}{2}}−1)^k\)

主要代码:

Poly Gcd(Poly x, Poly y) {
    if (!y.size()) return x;
    Poly z = x % y;
    while (z.size() && !z[z.size() - 1]) z.pop_back();
    return Gcd(y, z);
}
 
int n, k;
Poly f, g, pw;
 
int main() {
    int i, x, inv;
    scanf("%d%d", &n, &k), f.resize(n + 1);
    for (i = 0; i <= n; ++i) scanf("%d", &f[i]);
    g.resize(2), g[1] = 1, pw.resize(1), pw[0] = 1;
    for (x = (mod - 1) >> 1; x; g = g * g % f, x >>= 1)
        if (x & 1) pw = pw * g % f;
    Dec(pw[0], 1), g.resize(1), g[0] = 1;
    for (x = k; x; pw = pw * pw % f, x >>= 1)
        if (x & 1) g = g * pw % f;
    g = Gcd(f, g);
    printf("%d\n", x = (int)g.size() - 1);
    inv = Pow(g[x], mod - 2), g = g * inv;
    for (i = 0; i <= x; ++i) printf("%d ", g[i]);
    return puts(""), 0;
}
posted @ 2019-03-25 19:59  Cyhlnj  阅读(263)  评论(0编辑  收藏  举报