日常拖更x
不会概率DP死翘翘系列,容斥套容斥飞天系列,队友洗衣服不会倒着思考系列x
A
定位一下每个位置的数是多少,然后大力树状数组扫描线就好了
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <vector>
using namespace std;
using ll = long long;
int const N = 1000005;
struct que_node {
int l, r, f, id;
};
struct add_node {
int pos;
int val;
};
vector<que_node> que[N];
vector<add_node> add_bag[N];
int n, m, Q;
struct ques {
int x1, y1, x2, y2;
ll ans;
} q[N];
ll sum[N];
void motify(int pos, ll v) {
for (int i = pos + 1; i <= n + 1; i += i & -i)
sum[i] += v;
}
ll query_(int p) {
ll ret = 0;
if (p > n + 1)
p = n + 1;
for (int i = p; i; i -= i & -i)
ret += sum[i];
return ret;
}
ll query(int l, int r) {
return query_(r + 1) - query_(l);
}
int digsum(ll x) {
int ret = 0;
for (; x != 0; x /= 10)
ret += x % 10;
return ret;
}
int calc(int x, int y) {
int l = std::min(
std::min(x, y),
std::min(n - x + 1, n - y + 1)
);
ll beg = 1 + (ll)4 * (l - 1) * (n + 1 - l);
ll side = n - 2 * l + 1;
ll dt;
if (x == n - l + 1)
dt = n - l + 1 - y;
else if (y == l)
dt = side + n - l + 1 - x;
else if (x == l)
dt = 2 * side + y - l;
else
dt = 3 * side + x - l;
return digsum(beg + dt);
}
int main() {
ios::sync_with_stdio(0);
int T;
cin >> T;
while (T--) {
cin >> n >> m >> Q;
for (int i = 0; i <= n; ++i) {
add_bag[i].clear();
que[i].clear();
}
for (int i = 1; i <= m; ++i) {
int x, y;
cin >> x >> y;
add_bag[x].push_back({ y, calc(x, y) });
}
for (int i = 1; i <= Q; ++i) {
cin >> q[i].x1 >> q[i].y1 >> q[i].x2 >> q[i].y2;
q[i].ans = 0;
que[q[i].x1 - 1].push_back({ q[i].y1, q[i].y2, -1, i });
que[q[i].x2].push_back({ q[i].y1, q[i].y2, 1, i });
}
fill(sum, sum + n + 2, 0);
for (int i = 0; i <= n; ++i) {
for (auto const& p : add_bag[i])
motify(p.pos, p.val);
for (auto const& p : que[i])
q[p.id].ans += query(p.l, p.r) * p.f;
}
for (int i = 1; i <= Q; ++i)
cout << q[i].ans << '\n';
}
}
B
来自小洛洛的欧拉降幂,但是因为有可能 a a a和 m m m不互质,所以每次取模的时候预留一倍的模数出来免得爆炸。
#include <iostream>
#include <cstdint>
#include <algorithm>
#include <cstdint>
#define int int64_t
const int N = 1000005;
int phi[N];
void eul () {
static int pri[N] = {};
static bool vis[N] = {};
int *ppri = pri;
phi[1] = 1;
for (int i = 2; i < N; ++i) {
if (!vis[i]) {
*ppri++ = i;
phi[i] = i - 1;
}
for (int *p = pri; p != ppri && (int64_t)i * *p < N; ++p) {
vis[i * *p] = true;
if (i % *p == 0) {
phi[i * *p] = phi[i] * *p;
break;
} else
phi[i * *p] = phi[i] * (*p - 1);
}
}
}
int wtf_mod(int a, int b, int m) {
int64_t x = (int64_t)a * b;
if (x < 2 * m)
return x;
return m + (x - m) % m;
}
int pow_mod (int p, int q, int m) {
int ret = 1 % m;
for (; q; q >>= 1, p = wtf_mod(p, p, m))
if (q & 1)
ret = wtf_mod(ret, p, m);
return ret;
}
int tower (int a, int b, int m) {
// std::cerr << "> " << a << " " << b << " " << m << "\n";
if (b == 0)
return 0;
if (m == 1)
return 1; // a != 0
return pow_mod(a, tower(a, b - 1, phi[m]), m);
}
signed main () {
std::ios::sync_with_stdio(false);
eul();
int t;
std::cin >> t;
while (t--) {
int a, b, m;
std::cin >> a >> b >> m;
int ans;
if (a == 1)
ans = 1 % m;
else
ans = tower(a, b + 1, m) % m;
std::cout << ans << '\n';
}
return 0;
}
C
这个拆法是真的牛逼。(学到了学到了,估计下次还是做不出来。
设
f
i
=
∑
k
=
1
n
[
φ
[
i
]
=
i
]
f_i = \sum_{k = 1}^n [\varphi[i] = i]
fi=∑k=1n[φ[i]=i]
于是原来的式子就会变成
=
∑
i
=
1
n
∑
j
=
1
n
f
i
f
j
i
j
2
i
j
=\sum_{i = 1}^n \sum_{j = 1}^n f_i f_j i j 2^{i j}
=i=1∑nj=1∑nfifjij2ij
=
2
∑
i
=
1
n
∑
j
=
1
i
f
i
f
j
i
j
2
i
j
−
∑
i
=
1
n
i
2
f
i
2
2
i
2
= 2\sum_{i = 1}^n \sum_{j = 1}^i f_i f_j i j 2^{i j} - \sum_{i = 1}^n i^2f_i^22^{i^2}
=2i=1∑nj=1∑ififjij2ij−i=1∑ni2fi22i2
=
2
∑
i
=
1
n
∑
j
=
1
i
f
i
f
j
i
j
2
i
2
+
j
2
−
(
i
−
j
)
2
−
∑
i
=
1
n
i
2
f
i
2
2
i
2
= 2\sum_{i = 1}^n \sum_{j = 1}^i f_i f_j i j 2^{i ^ 2 + j ^ 2 -(i - j)^2} - \sum_{i = 1}^n i^2f_i^22^{i^2}
=2i=1∑nj=1∑ififjij2i2+j2−(i−j)2−i=1∑ni2fi22i2
=
2
∑
i
=
1
n
i
f
i
2
i
2
∑
j
=
1
i
f
j
j
2
j
2
2
−
(
i
−
j
)
2
−
∑
i
=
1
n
i
2
f
i
2
2
i
2
=2\sum_{i = 1}^n i f_i \sqrt 2 ^ {i ^ 2} \sum_{j = 1}^i f_j j \sqrt 2^{j ^ 2} \sqrt 2^{-(i - j)^2} - \sum_{i = 1}^n i^2f_i^22^{i^2}
=2i=1∑nifi2i2j=1∑ifjj2j22−(i−j)2−i=1∑ni2fi22i2
里面那个求和号大力NTT就完事了,根号2的部分二次剩余,其实这题还可以分段打表。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
using namespace std;
using ll = long long;
int const TT = 998244353, G = 3, yg2 = 116195171;
int const mod = TT;
inline int KSM (int a, ll k) {
int ret = 1 % TT;
for (; k; k >>= 1, a = 1ll * a * a % TT)
if (k & 1)
ret = 1ll * ret * a % TT;
return ret;
}
inline int add (int x, int y) {
if (x + y >= TT)
return x + y - TT;
else
return x + y;
}
inline int sub (int x, int y) {
if (x - y >= 0)
return x - y;
else
return x - y + TT;
}
namespace Polynom
{
vector<int> rev, rt, e = {1}, one = {1};
vector<int> operator + (vector<int> f, vector<int> g) {
int n = (int)max(f.size(), g.size());
f.resize(n);
g.resize(n);
for (int i = 0; i < n; ++i)
f[i] = add(f[i], g[i]);
return f;
}
vector<int> operator - (vector<int> f, vector<int> g) {
int n = (int)max(f.size(), g.size());
f.resize(n);
g.resize(n);
for (int i = 0; i < n; ++i)
f[i] = sub(f[i], g[i]);
return f;
}
void getRevRoot (int n) {
int m = log(n) / log(2) + 1e-7;
rev.resize(n);
rt.resize(n);
for (int i = 1; i < n; ++i)
rev[i] = rev[i >> 1] >> 1 | (i & 1) << (m - 1);
for (int len = 1, uni; len < n; len <<= 1) {
uni = KSM(G, (TT ^ 1) / (len << 1));
rt[len] = 1;
for (int i = 1; i < len; ++i)
rt[i + len] = 1ll * rt[i + len - 1] * uni % TT;
}
}
void NTT (vector<int> &f, int n) {
f.resize(n);
for (int i = 0; i < n; ++i)
if (i < rev[i])
swap(f[i], f[rev[i]]);
for (int len = 1; len < n; len <<= 1)
for (int i = 0; i < n; i += len << 1)
for (int j = 0, x, y; j < len; j++) {
x = f[i + j];
y = 1ll * f[i + j + len] * rt[j + len] % TT;
f[i + j] = add(x, y);
f[i + j + len] = sub(x, y);
}
}
vector<int> operator * (vector<int> f, vector<int> g) {
int n = 1, m = (int)f.size() + (int)g.size() - 1, ivn;
while (n < m)
n <<= 1;
ivn = KSM(n, TT - 2);
getRevRoot(n);
NTT(f, n);
NTT(g, n);
for (int i = 0; i < n; ++i)
f[i] = 1ll * f[i] * g[i] % TT;
reverse(f.begin() + 1, f.end());
NTT(f, n);
f.resize(m);
for (int i = 0; i < m; ++i)
f[i] = 1ll * f[i] * ivn % TT;
return f;
}
} // namespace Polynom
using namespace Polynom;
int const N = 100005;
int phi[N], pri[N / 3], tot;
bool isp[N];
void SAI() {
isp[1] = 1;
phi[1] = 1;
for (int i = 2; i < N; ++i) {
if (isp[i] == 0) {
pri[++tot] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= tot && i * pri[j] < N; ++j) {
isp[i * pri[j]] = 1;
if (i % pri[j])
phi[i * pri[j]] = phi[i] * phi[pri[j]];
else {
phi[i * pri[j]] = phi[i] * pri[j];
break;
}
}
}
}
vector<int> g1, g2;
int f[N];
int main() {
SAI();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
fill(f, f + n + 1, 0);
for (int i = 1; i <= n; ++i)
f[phi[i]]++;
g1.resize(n + 1);
g2.resize(n + 1);
for (int i = 0; i <= n; ++i)
g1[i] = 1ll * i * f[i] % mod * KSM(yg2, 1ll * i * i) % mod;
for (int i = 0; i <= n; ++i)
g2[i] = KSM(yg2, mod - 1 - 1ll * i * i % (mod - 1)) % mod;
auto g = g1 * g2;
int ans = 0;
for (int i = 1; i <= n; ++i)
ans = (ans + 1ll * i * f[i] % mod * KSM(yg2, 1ll * i * i) % mod * g[i]) % mod;
ans = 2ll * ans % mod;
for (int i = 1; i <= n; ++i)
ans = (ans - 1ll * i * i % mod * f[i] % mod * f[i] % mod * KSM(2, 1ll * i * i) % mod + mod) % mod;
cout << ans << '\n';
}
}
D
这个期望DP是我菜了。
主要是没有想到第二次DP的权是期望时间。。。
不过好像也很有道理。
接下来我会大力学概率DP和容斥的。
假设
d
p
1
dp_1
dp1表示到的时候的期望时间,
d
p
1
dp_1
dp1表示到的时候的期望代价。
那么就会有
d
p
1
[
i
]
=
∑
n
x
t
d
p
1
[
n
x
t
]
d
u
[
i
]
+
1
+
d
p
1
[
i
]
d
u
[
i
]
+
1
+
1
dp_1[i] = \sum_{nxt} \frac{dp_1[nxt]}{du[i] + 1} + \frac{dp_1[i]}{du[i] +1} + 1
dp1[i]=nxt∑du[i]+1dp1[nxt]+du[i]+1dp1[i]+1
d
p
2
[
i
]
=
∑
n
x
t
d
p
2
[
n
x
t
]
d
u
[
i
]
+
1
+
d
p
2
[
i
]
d
u
[
i
]
+
1
+
d
p
1
[
i
]
dp_2[i] = \sum_{nxt} \frac{dp_2[nxt]}{du[i] + 1} + \frac{dp_2[i]}{du[i] +1} + dp_1[i]
dp2[i]=nxt∑du[i]+1dp2[nxt]+du[i]+1dp2[i]+dp1[i]
移项就完事了。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <iomanip>
using namespace std;
int const N = 100005;
double f1[N], f2[N], g[N];
vector<int> G[N];
int du[N];
int in[N];
int n, m;
vector<int> topoSort(int S) {
auto q = queue<int>();
q.push(S);
auto ret = vector<int>();
while (q.empty() == 0) {
int now = q.front();
q.pop();
ret.push_back(now);
for (auto const& y : G[now]) {
in[y]--;
if (in[y] == 0)
q.push(y);
}
}
return ret;
}
void DP(double* f, vector<int> const& ver, double* v) {
for (auto const& x : ver) {
double sum = 0;
for (auto const& y : G[x])
sum += f[y];
f[x] = (sum + (du[x] + 1) * v[x]) / du[x];
}
}
int main() {
ios::sync_with_stdio(0);
int T;
cin >> T;
while (T--) {
cin >> n >> m;
fill(du, du + n + 1, 0);
fill(in, in + n + 1, 0);
for (int i = 1; i <= n; ++i)
G[i].clear();
for (int i = 0; i < m; ++i) {
int x, y;
cin >> x >> y;
G[x].push_back(y);
du[x]++;
in[y]++;
}
auto ver = topoSort(1);
reverse(ver.begin(), ver.end());
ver.erase(ver.begin());
fill(g, g + n + 1, 1);
fill(f1, f1 + n + 1, 0);
fill(f2, f2 + n + 1, 0);
DP(f1, ver, g);
DP(f2, ver, f1);
cout << fixed << setprecision(2) << f2[1] << '\n';
}
}
E
还好留了个简单数论题,不然就要4题gg了(虽然5题也还是gg了),主要是C没有意识到可以分段打表x
简单化简一下,就是
f
n
(
k
)
=
∑
p
=
1
n
[
n
p
]
k
∑
d
∣
p
d
2
μ
(
p
d
)
f_n(k) = \sum_{p = 1}^n [\frac{n}{p}]^k \sum_{d | p} d^2 \mu(\frac{p}{d})
fn(k)=p=1∑n[pn]kd∣p∑d2μ(dp)
答案就是
∑
p
=
1
n
∑
d
∣
p
d
2
μ
(
p
d
)
∑
i
=
2
k
[
n
p
]
i
\sum_{p = 1}^n \sum_{d | p} d^2 \mu(\frac{p}{d}) \sum_{i = 2}^k [\frac{n}{p}]^i
p=1∑nd∣p∑d2μ(dp)i=2∑k[pn]i
看
i
i
i求和的那部分,就是一个简单的等比数列求和而已,注意特判公比为1的时候
外面那个显然就是
i
d
2
∗
μ
id^2 * \mu
id2∗μ,直接配一个
I
I
I上去,就发现变成了
i
d
2
id^2
id2了,大力杜教筛就完事了。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <unordered_map>
using namespace std;
using ll = long long;
int const mod = 1e9 + 7;
int const M = 1e6;
int const N = M + 5;
ll mi, n, inv6;
string s;
ll ans_1;
int f[N];
int pri[N], tot, miu[N];
bool isp[N];
void SAI() {
isp[1] = 1;
miu[1] = 1;
for (int i = 2; i <= M; ++i) {
if (isp[i] == 0) {
pri[++tot] = i;
miu[i] = -1;
}
for (int j = 1; j <= tot && i * pri[j] <= M; ++j) {
isp[i * pri[j]] = 1;
if (i % pri[j])
miu[i * pri[j]] = -miu[i];
else {
miu[i * pri[j]] = 0;
break;
}
}
}
for (int i = 1; i <= M; ++i)
for (int p = i; p <= M; p = p + i)
if (miu[p / i] != 0)
f[p] = (f[p] + 1ll * i * i * miu[p / i]) % mod;
for (int i = 1; i <= M; ++i)
f[i] = (f[i] + f[i - 1]) % mod;
}
ll KSM(ll a, ll k) {
ll ret = 1;
for (; k; k >>= 1, a = a * a % mod)
if (k & 1)
ret = ret * a % mod;
return ret;
}
ll calc_f(int a) {
if (a == 1)
return ans_1;
ll fz = KSM(a, mi) - 1ll * a * a % mod;
fz = (fz % mod + mod) % mod;
ll fm = KSM(a - 1, mod - 2);
return fz * fm % mod;
}
ll calc_pf(ll x) {
return x * (x + 1) % mod * (2 * x + 1) % mod * inv6 % mod;
}
unordered_map<int, int> mp;
int calc_(int x) {
if (x <= M)
return f[x];
if (mp.count(x))
return mp[x];
ll ret = calc_pf(x);
for (int l = 2, r; l <= x; l = r + 1) {
r = x / (x / l);
ret = (ret - 1ll * calc_(x / l) * (r - l + 1)) % mod;
}
ret = (ret + mod) % mod;
mp[x] = ret;
return ret;
}
int main() {
ios::sync_with_stdio(0);
SAI();
int T;
cin >> T;
inv6 = KSM(6, mod - 2);
while (T--) {
cin >> n >> s;
mi = 0;
ans_1 = 0;
for (int i = 0; i < (int)s.size(); ++i) {
mi = (mi * 10 + s[i] - '0') % (mod - 1);
ans_1 = (ans_1 * 10 + s[i] - '0') % mod;
}
ans_1 = (ans_1 - 1 + mod) % mod;
mi = (mi + 1) % mod;
ll ans = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
ans += calc_f(n / l) * (calc_(r) - calc_(l - 1) + mod) % mod;
ans %= mod;
}
cout << ans << '\n';
}
}
F
随便用个数据结构大力贪心就完事了
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <vector>
using namespace std;
using pii = pair<int, int>;
int const N = 100005;
int f[N];
pii mx[N << 2];
int a[N], n, m;
inline void Union(pii& x, pii y) {
if (y > x)
x = y;
}
void pushup(int k) {
mx[k] = mx[k << 1];
Union(mx[k], mx[k << 1 | 1]);
}
void motifiy(int k, int l, int r, pii const& p) {
if (l == r) {
mx[k] = p;
return;
}
int mid = (l + r) >> 1;
if (p.second <= mid)
motifiy(k << 1, l, mid, p);
else
motifiy(k << 1 | 1, mid + 1, r, p);
pushup(k);
}
pii query(int k, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr)
return mx[k];
int mid = (l + r) >> 1;
pii ret = { 0, 0 };
if (ql <= mid)
Union(ret, query(k << 1, l, mid, ql, qr));
if (qr > mid)
Union(ret, query(k << 1 | 1, mid + 1, r, ql, qr));
return ret;
}
int main() {
ios::sync_with_stdio(0);
int T;
cin >> T;
while (T--) {
cin >> n >> m;
auto det = vector<pii>(n);
f[0] = 0;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
det[i - 1] = { a[i], i };
f[i] = 0;
}
for (int i = 0; i <= n * 4; ++i)
mx[i] = { 0, 0 };
sort(det.begin(), det.end());
for (int i = 0; i < (int)det.size(); ++i) {
auto p = det[i];
int posl = max(p.second - m, 1);
int posr = min(p.second + m, n);
auto tp = query(1, 1, n, posl, posr);
f[p.second] = f[tp.second] + 1;
cout << f[p.second];
if (i + 1 != (int)det.size())
cout << ' ';
motifiy(1, 1, n, p);
}
cout << '\n';
}
}
G
H
每次反向连最短路的逆就好了。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
using ll = long long;
int const N = 305;
ll const INF = N * 1e10;
ll f[N][N];
int n, m;
int main() {
ios::sync_with_stdio(0);
int T;
cin >> T;
while (T--) {
cin >> n >> m;
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
f[i][j] = (i == j) ? 0 : INF;
for (int i = 1; i <= m; ++i) {
int x, y, w;
cin >> x >> y >> w;
f[x][y] = w;
}
for (int k = 0; k < n; ++k)
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
for (int o = 0; o < 6; ++o) {
int x, y;
cin >> x >> y;
cout << -f[y][x] << '\n';
f[x][y] = -f[y][x];
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
f[i][j] = min(f[i][j], f[i][y] + f[y][j]);
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
f[i][j] = min(f[i][j], f[i][x] + f[x][j]);
}
}
}
I
昨天就补了,但是最开始的想法没有想到怎么做。
这里提一个问题,假设还是手洗可以同时任意多个,机洗一次一个,对于每个人洗衣服,这两种方式的时间都相同,同样给出来洗衣服,假设按照时间递增顺序给出,多组询问,每次给出
t
t
t和
x
x
x,问假设让所有来的时间
≤
t
\leq t
≤t的人全部手洗,剩下的全部机洗,每次机洗时间为
x
x
x,那么最终洗完衣服的时间是啥。
因为上面那个问题我只会
n
m
n \sqrt m
nm,所以我直接套决策单调性的话只会
n
l
o
g
n
n
nlogn\sqrt n
nlognn
这里的话我们从最后一个人往前考虑,显然存在一个位置,这个位置前面所有的人全部手洗,后面的全部机洗,这样会构成一个答案,而且决策点具有决策单调性。
这里有个小trick,因为在
m
/
x
m / x
m/x这个位置,如果决策点还在这个前面,那么会贡献
m
+
x
m+x
m+x的时间,所以决策点一定在
[
m
/
x
,
m
]
[m / x, m]
[m/x,m]之间。又因为
1
+
1
2
+
1
3
+
.
.
.
+
1
n
1 + \frac{1}{2} + \frac{1}{3} + ... + \frac{1}{n}
1+21+31+...+n1约为
l
n
n
+
C
lnn + C
lnn+C,那么我们直接爆枚所有的可能决策点就行。
#include <iostream>
#include <algorithm>
using namespace std;
int const N = 1000005;
int a[N];
int n, m;
int main() {
ios::sync_with_stdio(0);
while (cin >> n >> m) {
for (int i = 1; i <= n; ++i)
cin >> a[i];
sort(a + 1, a + n + 1);
for (int i = 1; i <= m; ++i) {
int p = min(n, m / i);
long long ans = 0;
if (p < n)
ans = a[n - p] + m;
for (int j = n; j > n - p; --j)
ans = max(ans, a[j] + (n - j + 1ll) * i);
cout << ans;
if (i == m)
cout << '\n';
else
cout << ' ';
}
}
}