多校省选模拟6
高中数列题
题意
给你一个 \(m\) 次的函数 \(f(x)=\sum_{i=0}^m p_i x ^ i\),然后给你一些 \(a\) 的递推式满足:
- \(a_0=A\)
- \(a_i=a_{i-1}+f(i)\times a_{\lfloor \frac{i+B}{C}\rfloor}\)
数据范围,满足 \(1\le n \le 10^{18},1\le m\le 20,B<C-1\)
求 \(a_n\)。
题解
记 \(p(i) = \lfloor\frac{i+B}{C}\rfloor\),同时记 \(q(i)\) 为最小的 \(p(x)=i\) 的 \(x\),然后有 \(q(i)=iC-B\)。
发现,实际上就是求 \(\sum_{i=1}^n a_{p(i)} f(i)\),然后稍微推一下式子:
发现就是
\[\begin{aligned} &\sum_{i=1}^na_{p(i)}f(i)\\ =&\sum_{i=1}^nf(i)\left(A+\sum_{j=1}^{p(i)}f(j)a_{p(j)}\right)\\ =&\sum_{i=1}^nAf(i)+\sum_{i=1}^nf(i)\sum_{j=1}^{p(i)}f(j)a_{p(j)}\\ =&\sum_{i=1}^nAf(i)+\sum_{j=1}^{p(n)}f(j)a_{p(j)}\sum_{i=q(j)}^nf(i) \end{aligned} \]然后,发现,我们可以先算出来这个 \(\sum_{i=1}^nAf(i)\),然后后边的递归做。右边那个式子其实是和原来的式子类似的。
不难发现
\[f(j)\sum_{i=q(j)}^nf(i) \]这玩意也是个关于 \(j\) 的多项式函数,然后大概是 \(2m+1\) 次的。
然后就,发现,我们可以递归下去搞了。然后,怎么算出来新的函数呢?还有查询函数的前缀和,我们可以用拉格朗日插值来解决,方法就是算出 \(2m+2\) 个处的点值,然后就可以查询任意位置了,对于前缀和来说,我们只要能搞出来 \(2m+2\) 个位置的点值就好了。
\[\sum_{i=q(j)}^n f(i)=sf(n)-sf(q(j)-1) \]#include <bits/stdc++.h>
using std::cin;
using std::cout;
const int MAXN = 1e5 + 10, M = 1e4 + 10, MOD = 1004535809;
long long N, A, B, C, fac[MAXN], ifac[MAXN], m, limit, G[MAXN], sG[MAXN], coef[MAXN], t[MAXN];
inline long long p(long long x) {
return (x + B) / C;
}
inline long long q(long long x) {
return (x * C - B) % MOD;
}
long long get(long long x) {
long long ret = 0;
for (int i = m; ~i; --i) {
ret = (ret * x + coef[i]) % MOD;
}
return ret;
}
inline long long Mod(long long x) {
if (x >= MOD) {
return x - MOD;
}
else if (x < 0) {
return x + MOD;
}
else {
return x;
}
}
long long Ksm(long long x, long long y) {
long long ret = 1;
for (; y; y >>= 1, x = x * x % MOD) {
if (y & 1) {
ret = ret * x % MOD;
}
}
return ret;
}
long long calc(long long x, long long *B, long long C) {
if (x <= C) {
return B[x];
}
static long long pre[MAXN], suf[MAXN];
pre[0] = 1;
suf[C + 1] = 1;
for (int i = 1; i <= C; ++i) {
pre[i] = pre[i - 1] * (x - i) % MOD;
}
for (int i = C; i; --i) {
suf[i] = suf[i + 1] * (x - i) % MOD;
}
long long ret = 0;
for (int i = 1; i <= C; ++i) {
long long t = B[i] * pre[i - 1] % MOD * suf[i + 1] % MOD * ifac[i - 1] % MOD * ifac[C - i] % MOD;
if ((C - i) & 1) {
ret = Mod(ret - t);
}
else {
ret = Mod(ret + t);
}
}
return ret;
}
long long solve(long long x) {
long long ret = 0;
if (x < M) {
for (int i = 1; i <= x; ++i) {
ret = (ret + t[p(i)] * calc(i, G, limit)) % MOD;
}
return ret;
}
ret = A * calc(x % MOD, sG, limit + 1) % MOD;
int t = limit + m + 1;
for (int i = 1; i <= t + 1; ++i) {
G[i] = get(i) * Mod(calc(x % MOD, sG, limit + 1) - calc(Mod(q(i) - 1), sG, limit + 1)) % MOD;
}
limit = t;
for (int i = 1; i <= limit + 1; ++i) {
sG[i] = Mod(sG[i - 1] + G[i]);
}
return Mod(ret + solve(p(x)));
}
int main() {
freopen("seq.in", "r", stdin);
freopen("seq.out", "w", stdout);
std::ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
fac[0] = 1;
for (int i = 1; i < MAXN; ++i) {
fac[i] = fac[i - 1] * i % MOD;
}
ifac[MAXN - 1] = Ksm(fac[MAXN - 1], MOD - 2);
for (int i = MAXN - 2; ~i; --i) {
ifac[i] = ifac[i + 1] * (i + 1) % MOD;
}
cin >> N >> A >> B >> C >> m;
for (int i = 0; i <= m; ++i) {
cin >> coef[i];
}
t[0] = A;
for (int i = 1; i < M; ++i) {
t[i] = (t[i - 1] + get(i) * t[p(i)]) % MOD;
}
limit = m + 1;
for (int i = 1; i <= limit + 1; ++i) {
G[i] = get(i);
sG[i] = Mod(sG[i - 1] + G[i]);
}
cout << Mod(A + solve(N)) << '\n';
return 0;
}
机动车限行
题意
给你一张 \(n\) 个点,\(m\) 条边的图,每个点的颜色可以是 \(1,2,3\),然后,从每种类型里选出来一些特殊点,满足这种类型的所有点,在其他颜色中有任意一个坏掉(即假设删去这个颜色的点的时候),可以都通过好的路径到至少一个特殊点。
题解
不妨设我们当前要求解对于 \(1\) 颜色的答案,那么,很明显地就是说,我们去掉 \(2\) 颜色的时候,求出来对于 \(1\) 颜色的连通块(个数为 \(t_1\),然后对于去掉 \(3\) 颜色的时候,求出来对于 \(1\) 颜色的连通块(个数为 \(t_2\)),最坏情况下答案是 \(t1+t2\),然后如果我们有一个 \(t1\) 里边的连通块和 \(t2\) 里边的连通块有交集,那么答案会减去一个。那么很明显就是个二分图匹配了,用左边的 \(t1\) 个连通块匹配右边的 \(t2\) 个连通块。
#include<bits/stdc++.h>
using std::queue;
using std::vector;
using std::cin;
using std::cout;
template<typename T>
inline T min(const T &x, const T &y) {
return x < y ? x : y;
}
const int MAXN = 2e5 + 10, INF = 0x3f3f3f3f;
int N, M, A[MAXN];
vector<int> G[MAXN];
bool vis[MAXN];
queue<int> Q;
int c1[MAXN], c2[MAXN], t1, t2;
void getc(int s, int ban, int *c, int &t) {
assert(Q.size() == 0);
Q.push(s);
vis[s] = 1;
c[s] = ++t;
while (!Q.empty()) {
int u = Q.front();
Q.pop();
for (auto &to: G[u]) {
if (vis[to] || A[to] == ban) {
continue;
}
vis[to] = 1;
c[to] = t;
Q.push(to);
}
}
}
int H[MAXN];
struct Edge {
int to, lac, flow, id;
void insert(int u, int v, int w, int pos) {
to = v;
lac = H[u];
flow = w;
id = pos;
H[u] = (*H)++;
}
} e[MAXN * 10];
auto add_edge = [] (int u, int v, int w, int pos) -> void {
e[*H].insert(u, v, w, pos);
e[*H].insert(v, u, 0, pos);
return;
};
int S, T;
void build(int now) {
t1 = t2 = 0;
for (int j = 1, tot = 0; j <= 3; ++j) {
if (j == now) {
continue;
}
memset(vis + 1, 0, N);
++tot;
for (int i = 1; i <= N; ++i) {
if (A[i] == now && !vis[i]) {
if (tot == 1) {
getc(i, j, c1, t1);
}
else {
getc(i, j, c2, t2);
}
}
}
}
S = t1 + t2 + 1, T = t1 + t2 + 2;
*H = 0;
memset(H + 1, -1, T * 4);
for (int i = 1; i <= N; ++i) {
if (A[i] == now) {
add_edge(c1[i], t1 + c2[i], 1, i);
}
}
for (int i = 1; i <= t1; ++i) {
add_edge(S, i, 1, 0);
}
for (int i = 1; i <= t2; ++i) {
add_edge(i + t1, T, 1, 0);
}
return;
}
int dep[MAXN];
int bfs() {
memset(dep + 1, -1, T * 4);
Q.push(S);
dep[S] = 0;
while (!Q.empty()) {
int nw = Q.front();
Q.pop();
for (int i = H[nw], to; ~i; i = e[i].lac) {
if (~dep[to = e[i].to] || !e[i].flow) {
continue;
}
dep[to] = dep[nw] + 1;
Q.push(to);
}
}
return ~dep[T];
}
int cur[MAXN];
int dfs(int u, int w) {
if (u == T) {
return w;
}
int sum = w;
for (int &i = cur[u]; ~i; i = e[i].lac) {
int to = e[i].to;
if (dep[to] != dep[u] + 1 || !e[i].flow) {
continue;
}
int ret = dfs(to, min(e[i].flow, w));
e[i].flow -= ret;
e[i ^ 1].flow += ret;
w -= ret;
if (!w) {
break;
}
}
return sum - w;
}
int dinic() {
int ANS = 0;
while (bfs()) {
for (int i = 1; i <= T; ++i) {
cur[i] = H[i];
}
memcpy(cur + 1, H + 1, 4 * T);
ANS += dfs(S, INF);
}
return ANS;
}
void solve(int now) {
build(now);
cout << t1 + t2 - dinic() << ' ';
for (int u = 1; u <= t1; ++u) {
for (int i = H[u], to; ~i; i = e[i].lac) {
to = e[i].to;
if (to != S && !e[i].flow) {
cout << e[i].id << ' ';
}
}
}
for (int i = H[S]; ~i; i = e[i].lac) {
if (e[i].flow) {
for (int j = H[e[i].to]; ~j; j = e[j].lac) {
if (e[j].to != S) {
cout << e[j].id << ' ';
break;
}
}
}
}
for (int i = H[T]; ~i; i = e[i].lac) {
if (!e[i].flow) {
for (int j = H[e[i].to]; ~j; j = e[j].lac) {
if (e[j].to != T) {
cout << e[j].id << ' ';
break;
}
}
}
}
cout << '\n';
}
int main() {
freopen("restriction.in", "r", stdin);
freopen("restriction.out", "w", stdout);
std::ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> N >> M;
for (int i = 1; i <= N; ++i) {
cin >> A[i];
}
for (int i = 1, x, y; i <= M; ++i) {
cin >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
for (int i = 1; i <= 3; ++i) {
solve(i);
}
return 0;
}
城市绿化
题意
给你一个 \(n\) 个顶点的树,然后你可以询问两点之间的距离,然后找出来这个树。
交互题目。
题解
大概写了几份程序。
就是,一个自然的想法就是可以先对于每个节点都询问出来和 \(1\) 节点的距离,然后就可以得到每个结点的深度,然后按照深度递增的做每个节点。
然后暴力的做法就是,直接对于每一层的点分别询问关于上一层的点的距离,然后这么做的复杂度是 \(O(N^2)\),这样可以得到 \(25\) 分。
一个60分的做法是,我们考虑对于每次的树,首先可以找出来他的一条长链,这会花费我们 \(O(SZ)\) 的操作次数,具体的操作就是,首先找到一个长链的最深点,然后一个点在长链上当且仅当他到两个端点距离是长链的长度。
然后我们就做完了。
由于长链剖分的性质,这样子做的复杂度为 \(O(N\sqrt N)\) 时间复杂度和操作数均是。
第三种做法是 \(LCT\),这是我没有想到的,这题目里运用的方法有点类似于 [WC2018 即时战略]一题,我们考虑其实就是这样的操作,动态地维护一个树,然后你每次可以在树上快速的得到某个节点到新加入的节点的路径上的下一个节点。
怎么快速得到?我们考虑对于一个节点 \(u\),他的所有相邻节点中到 \(v\) 距离最小的点就是下一个节点了。
然后我们在重链上二分,就是说,维护段重链的上端点和下端点即可。当不在这个重链的时候只要跳虚边就可以了。
还有酷炫的第四种做法,这种做法是吊打标算的,也就是说,这个题的 \(N\) 其实可以开到 \(3\times 10^6\)
我们考虑按照深度递增的想法做每个节点。
假设我们在做第 \(k\) 层的节点,然后之前是 \(k-1\) 层的,我们考虑把这两个一起做分治。
我们考虑,当前这个子树里,我们不妨算出来所有 \(k-1\) 层和 \(k\) 层节点对于第一个 \(k-1\) 层节点(记为 \(u\))的距离,然后可以得到 \(u\) 节点的直接儿子。
然后,我们就可以分子树算了,加入对于一些 \(k-1\) 层节点他们对于 \(u\) 节点距离一样,然后有某些 \(k\) 层节点到 \(u\) 节点的距离恰好是 \(k-1\) 层的答案加一,那么这些在一个子树里,然后递归做他们就可以了。
发现不会算复杂度。但是跑的好快。。。
#include "green.h"
#include <bits/stdc++.h>
using std::sort;
using std::vector;
const int MAXN = 1e5 + 10;
int dep[MAXN], dis[MAXN];
vector<int> v[MAXN];
void Sort(int x, vector<int>::iterator l, vector<int>::iterator r) {
for (auto i = l; i != r; ++i) {
dis[*i] = visit(*i, x);
}
sort(l, r, [&] (const int &x, const int &y) -> int {
return dis[x] < dis[y];
});
}
int *fa;
void work(int k, int l, int r, int L, int R) {
if (L == R) {
for (int i = l; i <= r; ++i) {
fa[v[k][i]] = v[k - 1][L];
}
return;
}
if (l == r) {
for (int i = L; i <= R; ++i) {
if (visit(v[k - 1][i], v[k][l]) == 1) {
fa[v[k][l]] = v[k - 1][i];
return;
}
}
std::cerr << "none";
}
Sort(v[k - 1][L], v[k - 1].begin() + L + 1, v[k - 1].begin() + R + 1);
Sort(v[k - 1][L], v[k].begin() + l, v[k].begin() + r + 1);
int l1 = l, L1 = L + 1;
for (; l1 <= r && dis[v[k][l1]] == 1; ++l1) {
fa[v[k][l1]] = v[k - 1][L];
}
while (l1 <= r) {
while (dis[v[k - 1][L1]] + 1 != dis[v[k][l1]]) {
++L1;
}
int i = l1, j = L1;
while (j < R && dis[v[k - 1][j + 1]] == dis[v[k - 1][L1]]) {
++j;
}
while (i < r && dis[v[k][i + 1]] == dis[v[k][l1]]) {
++i;
}
work(k, l1, i, L1, j);
l1 = i + 1;
L1 = j + 1;
}
}
void findtree(int n, int m, int *p) {
fa = p;
v[0].push_back(1);
for (int i = 2; i <= n; ++i) {
v[dep[i] = visit(i, 1)].push_back(i);
}
for (int k = 1; v[k].size() != 0; ++k) {
work(k, 0, v[k].size() - 1, 0, v[k - 1].size() - 1);
}
return;
}