我们 TM 怎么又要上文化课。。我 哔哔哔哔哔哔
「SDOI2017」数字表格
题意
有 \(T\) 组数据,求
\]
$ 1 \leq n, m \leq 10 ^ 6, 1 \leq T \leq 1000 $
题解
又是一道莫比乌斯反演套路题。。
&\prod_{i = 1}^{n} \prod_{j = 1}^{m} fib[\gcd(i, j)]\\
&=\prod_{d = 1}^{n} fib[d]^{\sum_{i = 1}^n\sum_{j = 1}^n [(i, j) = d]}
\end{aligned}
\]
写在幂次那里不好看,我们单独提出来。。。
& \sum_{i = 1}^n\sum_{j = 1}^n [(i, j) = d]\\
&= \sum_{d | x} \mu(\frac x d) \lfloor \frac{n}{x} \rfloor \lfloor \frac{m}{x} \rfloor\\
&= \sum_{i = 1} ^ {\lfloor \frac{n}{d} \rfloor} \mu(i) \lfloor \frac{n}{id} \rfloor \lfloor \frac{m}{id} \rfloor
\end{aligned}
\]
我们令 \(T = id\) 那么原式就变成
&\prod_{d = 1}^{n} fib[d]^{\sum_{i = 1} ^ {\lfloor \frac{n}{d} \rfloor} \mu(i) \lfloor \frac{n}{id} \rfloor \lfloor \frac{m}{id} \rfloor}\\
&= \prod_{T = 1}^n (\prod_{d | T} fib[d]^{\mu(\frac{T}{d} )})^{\lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor}
\end{aligned}
\]
预处理中间那个括号的内容,然后外面套整除分块即可。
其实不预处理,套两层整除分块 \(\mathcal LOJ\) 的机子可以卡过 QAQ
复杂度是 \(\mathcal O(n \log n + T \sqrt n \log n)\) 的。
代码
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), _end_ = (int)(r); i <= _end_; ++i)
#define Fordown(i, r, l) for(register int i = (r), _end_ = (int)(l); i >= _end_; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;
bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar() ) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar() ) x = (x<<1) + (x<<3) + (ch ^ '0');
return x * fh;
}
typedef long long ll;
int n, m;
const int N = 1e6 + 1e3;
ll mu[N], prime[N / 4], fib[N], ifib[N], cnt, g[N], ig[N];
bitset<N> is_prime;
const int Mod = 1e9 + 7, Pmod = Mod - 1;
ll fpm(ll x, ll power) {
ll res = 1;
for (; power; power >>= 1, (x *= x) %= Mod)
if (power & 1) (res *= x) %= Mod;
return res;
}
void Init(int maxn) {
is_prime.set();
is_prime[0] = is_prime[1] = false;
int res; g[0] = ig[0] = g[1] = ifib[1] = mu[1] = fib[1] = 1;
For (i, 2, maxn) {
if (is_prime[i]) {
prime[++cnt] = i;
mu[i] = -1;
}
For (j, 1, cnt) {
res = prime[j] * i;
if (res > maxn) break ;
is_prime[res] = false;
if (i % prime[j]) mu[res] = -mu[i];
else {mu[res] = 0; break ;}
}
g[i] = 1;
fib[i] = fib[i - 1] + fib[i - 2];
if (fib[i] >= Mod) fib[i] -= Mod;
ifib[i] = fpm(fib[i], Mod - 2);
}
For (i, 1, maxn) {
for (register int j = i, t = 1; j <= maxn; j += i, ++ t)
if (mu[t] == 1) g[j] *= fib[i], g[j] = g[j] >= Mod ? g[j] % Mod : g[j];
else if (mu[t] == -1) g[j] *= ifib[i], g[j] = g[j] >= Mod ? g[j] % Mod : g[j];
(g[i] *= g[i - 1]) %= Mod;
ig[i] = fpm(g[i], Mod - 2);
}
}
ll Calc() {
cerr << "Calc: " << endl;
ll res = 1; ll Nextd, n_, m_;
if (n > m) swap(n, m);
For(d, 1, n) {
n_ = n / d; m_ = m / d;
Nextd = min(n / n_, m / m_);
(res *= fpm(g[Nextd] * ig[d - 1] % Mod, n_ * m_ % Pmod) ) %= Mod;
d = Nextd;
}
return res;
}
void File() {
#ifdef zjp_shadow
freopen ("2000.in", "r", stdin);
freopen ("2000.out", "w", stdout);
#endif
}
int main() {
File();
Init((int)1e6);
int cases = read();
while (cases --) {
n = read(); m = read();
printf ("%lld\n", Calc() );
}
return 0;
}
「SDOI2017」树点涂色
看我之前的 题解 就好啦。
似乎说是一个套路题?染上没有的颜色就是 access
的过程,然后套上线段树维护就好啦。
「SDOI2017」序列计数
题意
求有多少个长度为 $ n $ 的序列,至少有一个数是质数,序列中的数都是不超过 $ m $ 的正整数,而且这 $ n $ 个数的和是 $ p $ 的倍数。答案对于 \(20170408\) 取模。
$ 1 \leq n \leq 10 ^ 9, 1 \leq m \leq 2 \times 10 ^ 7, 1 \leq p \leq 100 $。
题解
看到数据就知道是思博矩幂了,可以做到 \(\mathcal O(m + p^3 \log n)\) 。
其实这是个循环矩阵,无限自乘还是循环的。我们只需要维护其中一行就知道其它所有行了,那每次拿个向量搞搞得出一行就行啦。
这样可以做到 \(\mathcal O(m + p^2 \log n)\) 。
其实那个过程本质上是循环卷积,但是和 \(FFT\) 关于 \(2^k\) 循环似乎不太契合。
那么这个多项式快速幂每次只能做到 \(\mathcal O(m + p \log p \log n)\) 。
其实可以利用 ln
变成加法,然后 exp
回去即可,做到 \(\mathcal O(m + p \log p)\) 。
但由于模数的鬼畜,不能用 NTT
利用 MTT
实现就好啦。。。
代码
显然我的做法是最暴力的矩幂啦 qwq
#include <bits/stdc++.h>
#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
using namespace std;
template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
void File() {
#ifdef zjp_shadow
freopen ("2002.in", "r", stdin);
freopen ("2002.out", "w", stdout);
#endif
}
const int N = 2e7 + 1e3, M = 110, Mod = 20170408;
int n, m, p;
bitset<N> is_prime; int prime[N], plen;
void Linear_Sieve(int maxn) {
is_prime.set();
is_prime[0] = is_prime[1] = false;
For (i, 2, maxn) {
if (is_prime[i])
prime[++ plen] = i;
For (j, 1, plen) {
int res = prime[j] * i;
if (res > maxn) break;
is_prime[res] = false;
if (!(i % prime[j])) break;
}
}
}
int ptot[M], tot[M];
struct Mat {
int a[M][M];
Mat() { Set(a, 0); }
inline void Unit() {
Rep (i, p) a[i][i] = 1;
}
inline Mat friend operator * (const Mat &lhs, const Mat &rhs) {
Mat res;
Rep (i, p) Rep (k, p) Rep (j, p)
res.a[i][j] = (res.a[i][j] + 1ll * lhs.a[i][k] * rhs.a[k][j]) % Mod;
return res;
}
};
inline Mat fpm(Mat x, int power) {
Mat res; res.Unit();
for (; power; power >>= 1, x = x * x)
if (power & 1) res = res * x;
return res;
}
Mat f, trans;
int main () {
File();
n = read(); m = read(); p = read();
Linear_Sieve(m);
For (i, 1, m) {
++ tot[i % p];
if (is_prime[i]) ++ ptot[i % p];
}
Rep (i, p) Rep (j, p)
trans.a[(i + j) % p][i] += tot[j];
f.a[0][0] = 1; f = fpm(trans, n) * f;
int ans = f.a[0][0];
Rep (i, p) Rep (j, p)
trans.a[(i + j) % p][i] -= ptot[j];
Set(f.a, 0); f.a[0][0] = 1; f = fpm(trans, n) * f;
ans = (ans + Mod - f.a[0][0]) % Mod;
printf ("%d\n", ans);
return 0;
}
「SDOI2017」新生舞会
题意
左右各有 \(n\) 个点的二分图, \(a_{i, j}, b_{i, j}\) 为 \(i, j\) 匹配能得到的两个权值。
一个方案中有 $ n $ 对匹配,假设每组匹配的权值 \(a_{i, j}\) 分别是 $ a'_1, a'_2, \ldots, a'_n $, \(b_{i, j}\) 分别是 $ b'_1, b'_2, \ldots, b'_n $。令
求一个完美匹配,使得
\]
尽量大。
题解
这 round1 模板题是真多。。。
直接分数规划,二分 \(C\) ,边权就变成 \(a_{i, j} - C \times b_{i, j}\) 。
那么就是找一个完美匹配使得匹配的边权和不小于 \(0\) 。
然后用 KM
或者费用流跑最大权匹配就行了。
代码
#include <bits/stdc++.h>
#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
using namespace std;
template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
void File() {
#ifdef zjp_shadow
freopen ("2003.in", "r", stdin);
freopen ("2003.out", "w", stdout);
#endif
}
const int inf = 0x3f3f3f3f;
template<int Maxn, int Maxm>
struct MCMF {
int Head[Maxn], Next[Maxm], to[Maxm], cap[Maxm], e; double val[Maxm];
void Init() {
e = 1; Set(Head, 0);
}
inline void add_edge(int u, int v, double cost, int flow) {
to[++ e] = v; Next[e] = Head[u]; Head[u] = e;
val[e] = cost; cap[e] = flow;
}
inline void Add(int u, int v, double cost, int flow) {
add_edge(u, v, cost, flow); add_edge(v, u, -cost, 0);
}
int S, T;
int pre[Maxn]; double dis[Maxn]; bitset<Maxn> inq;
bool Spfa() {
queue<int> Q;
For (i, 1, T) dis[i] = - 1e8;
Set(pre, 0); inq.reset();
Q.push(S); dis[S] = 1;
while (!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]]) {
if (cap[i] && chkmax(dis[v], dis[u] + val[i])) {
pre[v] = i; if (!inq[v]) inq[v] = true, Q.push(v);
}
}
}
return pre[T];
}
double Run() {
int sum_flow = 0; double sum_cost = 0;
while (Spfa()) {
int flow = inf;
for (int u = T; u != S; u = to[pre[u] ^ 1]) chkmin(flow, cap[pre[u]]);
for (int u = T; u != S; u = to[pre[u] ^ 1]) {
cap[pre[u]] -= flow;
cap[pre[u] ^ 1] += flow;
sum_cost += val[pre[u]] * flow;
}
sum_flow += flow;
}
return sum_cost;
}
};
const int N = 110;
const double eps = 1e-8;
MCMF<N * 2, N * N * 2> T;
int n; double A[N][N], B[N][N];
#define In(x) (x)
#define Out(x) ((x) + n)
inline bool check(double k) {
T.Init();
T.T = (T.S = Out(n) + 1) + 1;
For (i, 1, n) {
T.Add(T.S, In(i), 0, 1);
T.Add(Out(i), T.T, 0, 1);
}
For (i, 1, n) For (j, 1, n)
T.Add(In(i), Out(j), A[i][j] - k * B[i][j], 1);
return T.Run() > 0;
}
int main () {
File();
n = read();
For (i, 1, n) For (j, 1, n) A[i][j] = read();
For (i, 1, n) For (j, 1, n) B[i][j] = read();
double l = 0.0, r = 1e8;
while (r - l > eps) {
double mid = (l + r) / 2.0;
if (check(mid)) l = mid; else r = mid;
}
printf ("%.6lf\n", l);
return 0;
}
「SDOI2017」硬币游戏
题意
选出 $ n $ 个同学, 每个同学猜一个长度为 $ m $ 的序列,当某一个同学猜的序列在硬币序列中出现时,就不再扔硬币了,并且这个同学胜利。为了保证只有一个同学胜利,同学们猜的 $ n $ 个序列两两不同。
最后求出每个同学胜利的概率。
$ 1 \leq n, m \leq 300 $
题解
六道题里唯一没有那么思博的题。。
暴力 ac自动机 + 高斯消元 是 \(\mathcal O((nm)^3)\) 的,只有 \(40\) 分。
考虑优化,显然我们最后要求的只有 \(n\) 个变量的值,其他变量都是毫无意义的。
那么我们只需要求出这些变量之间的相关方程即可。
我们多设一个 \(f_s\) 为不匹配到其中任意一个同学的概率,那么我们在当前串后面添加一个同学 \(i\) 的拥有的字符串 \(S_i\) 就能对应到它的概率。
这个似乎利用了一个结论,如果不匹配到 \(n\) 个结束节点,停到任意一个点的概率似乎是相同的。
但是这样会把 \(S_i\) 有个前缀和 \(S_j\) 一个后缀是一样的概率多算上去,我们减掉就行了,这个出现的概率是 \(2^{-l}\) ,\(l\) 为这个前缀的长度。
利用 KMP
实现就可以做到 \(\mathcal O(n^2(n + m))\) 啦。
代码
我懒,用的暴力匹配。。也过啦。。
#include <bits/stdc++.h>
#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
using namespace std;
template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
void File() {
#ifndef ONLINE_JUDGE
freopen ("2004.in", "r", stdin);
freopen ("2004.out", "w", stdout);
#endif
}
const int N = 310;
double G[N][N];
void Gauss(int n) {
For (i, 1, n) {
For (j, i + 1, n) if (fabs(G[j][i]) > fabs(G[i][i])) swap(G[i], G[j]);
if (!G[i][i]) continue;
Fordown (j, n + 1, i) G[i][j] /= G[i][i];
For (j, i + 1, n) Fordown (k, n + 1, i) G[j][k] -= G[j][i] * G[i][k];
}
Fordown (i, n, 1) {
For (j, 1, i - 1)
G[j][n + 1] -= G[j][i] * G[i][n + 1], G[j][i] = 0;
}
}
int n, m; double inv[N];
char str[N][N];
int main() {
File();
n = read(); m = read();
For (i, 1, n) scanf ("%s", str[i] + 1);
inv[0] = 1.0;
For (i, 1, m) inv[i] = inv[i - 1] / 2.0;
For (i, 1, n) {
G[i][i] = 1.0; G[i][n + 1] = - 1.0;
For (j, 1, n) {
For (p, 2, m) {
int len = 1;
while (p + len - 1 <= m && str[i][len] == str[j][p + len - 1]) ++ len;
if (p + len == m + 2) G[i][j] += inv[p - 1];
}
}
}
For (i, 1, n) G[n + 1][i] = 1.0; G[n + 1][n + 2] = 1.0;
Gauss(n + 1);
For (i, 1, n)
printf ("%.10lf\n", G[i][n + 2]);
return 0;
}
「SDOI2017」相关分析
题意
一个序列,每个元素是一个点 \((x_i, y_i)\) 。
有 \(m\) 个操作,共有三种:
\(\texttt{1 L R}:\) 求 \([L, R]\) 的回归直线的斜率。
\(\texttt{2 L R S T}:\) 把 \(i \in [L, R]\) \(x_i\) 加上 \(S\) ,\(y_i\) 加上 \(T\) 。
\(\texttt{2 L R S T}:\) 把 \(i \in [L, R]\) \(x_i\) 变成 \(S + i\) ,\(y_i\) 变成 \(T + i\) 。
$ 1 \leq n, m \leq 10 ^ 5 $
题解
昨天才讲的回归直线。。今天就用上啦QAQ
\]
这个数学书上常常用另外一种化简的方式。。
令 \(n = R - L + 1\) 即区间长度。
\]
然后维护区间 \(x, y, xy, x^2\) 的和就行了。
代码
#include <bits/stdc++.h>
#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
using namespace std;
template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
void File() {
#ifdef zjp_shadow
freopen ("2005.in", "r", stdin);
freopen ("2005.out", "w", stdout);
#endif
}
const int N = 1e5 + 1e3;
using ll = double;
ll pre1(ll x) {
return x * (x + 1) / 2;
}
ll pre2(ll x) {
return x * (x + 1) * (2 * x + 1) / 6;
}
#define ls o << 1
#define rs o << 1 | 1
#define mid ((l + r) >> 1)
#define lson ls, l, mid
#define rson rs, mid + 1, r
template<int Maxn>
struct Segment_Tree {
ll sumxy[Maxn], sumx[Maxn], sumy[Maxn], sumxx[Maxn];
int tag[Maxn], S[Maxn], T[Maxn];
inline void modify(int o, int l, int r, int opt, int sv, int tv) {
int len = r - l + 1;
chkmax(tag[o], opt);
if (opt == 2) {
sumxy[o] += sv * sumy[o] + tv * sumx[o] + 1ll * len * sv * tv;
sumxx[o] += 2 * sv * sumx[o] + 1ll * len * sv * sv;
sumx[o] += 1ll * sv * len;
sumy[o] += 1ll * tv * len;
S[o] += sv; T[o] += tv;
} else {
sv += l; tv += l;
sumxy[o] = 1ll * len * sv * tv + pre1(len - 1) * (sv + tv) + pre2(len - 1);
sumxx[o] = 1ll * len * sv * sv + 2 * pre1(len - 1) * sv + pre2(len - 1);
sumx[o] = 1ll * len * sv + pre1(len - 1);
sumy[o] = 1ll * len * tv + pre1(len - 1);
S[o] = sv - l; T[o] = tv - l;
}
}
inline void push_down(int o, int l, int r) {
if (tag[o]) {
modify(lson, tag[o], S[o], T[o]);
modify(rson, tag[o], S[o], T[o]);
tag[o] = S[o] = T[o] = 0;
}
}
inline void push_up(int o) {
sumxy[o] = sumxy[ls] + sumxy[rs];
sumxx[o] = sumxx[ls] + sumxx[rs];
sumx[o] = sumx[ls] + sumx[rs];
sumy[o] = sumy[ls] + sumy[rs];
}
void Update(int o, int l, int r, int opt, int ul, int ur, int sv, int tv) {
if (ul <= l && r <= ur) {
modify(o, l, r, opt, sv, tv); return;
}
push_down(o, l, r);
if (ul <= mid) Update(lson, opt, ul, ur, sv, tv);
if (ur > mid) Update(rson, opt, ul, ur, sv, tv);
push_up(o);
}
ll Query(int o, int l, int r, int ql, int qr, ll *sum) {
if (ql <= l && r <= qr) return sum[o];
push_down(o, l, r); ll res = 0;
if (ql <= mid) res += Query(lson, ql, qr, sum);
if (qr > mid) res += Query(rson, ql, qr, sum);
push_up(o); return res;
}
void Build(int o, int l, int r, int *x, int *y) {
if (l == r) {
sumxy[o] = 1ll * x[l] * y[r];
sumxx[o] = 1ll * x[l] * x[r];
sumx[o] = x[l]; sumy[o] = y[r];
return;
}
Build(lson, x, y); Build(rson, x, y); push_up(o);
}
};
Segment_Tree<N << 2> T;
int n, m, x[N], y[N];
#define root 1, 1, n
int main () {
File();
n = read(); m = read();
For (i, 1, n) x[i] = read();
For (i, 1, n) y[i] = read();
T.Build(root, x, y);
while (m --) {
int opt = read(), l = read(), r = read();
if (opt == 1) {
ll sumxy = T.Query(root, l, r, T.sumxy),
sumxx = T.Query(root, l, r, T.sumxx),
sumx = T.Query(root, l, r, T.sumx),
sumy = T.Query(root, l, r, T.sumy), len = r - l + 1;
double averx = 1.0 * sumx / len, avery = 1.0 * sumy / len,
k = 1.0 * (sumxy - len * averx * avery) / (sumxx - len * averx * averx);
printf ("%.6lf\n", k);
} else {
int sv = read(), tv = read();
T.Update(root, opt, l, r, sv, tv);
}
}
return 0;
}