还没写完 | 2023.6.25做题记录
还没写完 | 2023.6.25做题记录
P3232 [HNOI2013]游走
是不是什么神秘主元法,维护系数
考虑 \(\operatorname{EX}(u)\) 表示点 \(u\) 出发到 \(n\) 的期望步数,\(\operatorname{EX}(n)=0\)
如果假设 \(u\) 的出度为 \(d\)
然后我们发现我们不知道 \((u,v)\) 是啥!但是我们可以考虑维护所有 \((u,v)\) 的系数,我们希望知道每条 \((u,v)\) 对答案的贡献是多少
变一下形
那么我们可以对每条边开一个未知数,然后消元的时候整个未知数的向量一起消就行了,时间复杂度 \(\mathcal O(n^3m)\),能过第一个点
考虑把边的次数转到点的次数上来,现在假设 \(\operatorname{EX}(u)\) 表示 \(u\) 要经过的期望次数
所以 \((u,v)\) 的期望经过次数即为 \(\frac{\operatorname{EX}(u)}{d_u}+\frac{\operatorname{EX}(v)}{d_v}\)
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
const int N = 510;
double mat[N][N];
double f[N];
int d[N];
std::vector<int> G[N];
struct edge {
int u, v;
edge(){}
edge(int _u, int _v) {u = _u, v = _v;}
}E[250010];
struct redge {
int u, v;
double k;
bool operator < (const redge& b) {
return k > b.k;
}
}E2[250010];
int idx;
int n, m;
inline void add(int u, int v) {
G[u].push_back(idx), E[idx++] = edge(u, v);
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
d[u]++, d[v]++;
E2[i].u = u, E2[i].v = v;
}
for(int i : G[1]) {
int v = E[i].v;
if(v != n)
mat[1][v] += -(double)1 / (double)d[v];
}
mat[1][1] = 1, mat[1][n+1] = 1;
for(int u = 2; u < n; u++) {
for(int i : G[u]) {
int v = E[i].v;
if(v != n)
mat[u][v] += -(double)1 / (double)d[v];
}
mat[u][u] = 1;
}
mat[n][n] = 1;
for(int i = 1; i <= n; i++) {
double maxl = -1;
int row = i;
for(int j = i; j <= n; j++)
if(fabs(mat[j][i]) > maxl)
row = j, maxl = fabs(mat[i][j]);
for(int j = 1; j <= n + 1; j++)
std::swap(mat[i][j], mat[row][j]);
for(int j = 1; j <= n; j++) {
if(j != i) {
double x = mat[j][i] / mat[i][i];
for(int k = i; k <= n + 1; k++) {
mat[j][k] -= mat[i][k] * x;
}
}
}
}
for(int i = 1; i <= n; i++)
f[i] = mat[i][n+1] / mat[i][i];
for(int i = 1; i <= m; i++) {
if(E2[i].u != n)
E2[i].k += f[E2[i].u] / (double)d[E2[i].u];
if(E2[i].v != n)
E2[i].k += f[E2[i].v] / (double)d[E2[i].v];
}
std::sort(E2 + 1, E2 + 1 + m);
double ans = 0;
for(int i = 1; i <= m; i++) {
ans += E2[i].k * i;
}
printf("%.3f\n", ans);
return 0;
}
P5643 [PKUWC2018]随机游走
这位更是方程都不会列
点集中的所有点都经过一次的期望步数\(\Longleftrightarrow\)经过点集中最后一个点的期望步数
考虑 \(\min-\max\) 容斥,
变成经过点集中第一个点的期望步数
不妨假设 \(f_{u,S}\) 表示从 \(u\) 开始,经过 \(S\) 中至少一个点的期望步数,记 \(p\) 为 \(u\) 的父亲
如果 \(u\in S\),则为 \(0\),否则为
由于是树上的方程,所以可以直接递推,考虑推一下式子,对于这类问题可以拆贡献,把父亲和儿子的贡献分开,即 \(f_{u,S}=A_uf_{p,S}+B_u\)
那么
变换成一开始的形式
故
可以知道
而
显然,\(f_{root,S}\) 即 \(B_{root}\)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
typedef long long ll;
const int N = 20;
const ll mod = 998244353;
int h[N], e[N << 1], ne[N << 1], idx;
ll dp[1 << N];
ll A[N], B[N], deg[N];
int n, q, x;
inline void add(int u, int v) {
e[idx] = v, ne[idx] = h[u], h[u] = idx++;
}
ll f_pow(ll a, ll k = mod - 2) {
ll base = 1;
for(; k; k >>= 1, a = a * a % mod)
if(k & 1)
base = base * a % mod;
return base;
}
inline ll Plus(ll a, ll b) {
a += b;
return a >= mod ? a -= mod : a;
}
inline ll Minu(ll a, ll b) {
a -= b;
return a < 0 ? a + mod : a;
}
void dfs(int u, int fa, int s) {
if(s & (1 << u)) {
A[u] = B[u] = 0;
return ;
}
ll suma = 0, sumb = 0;
for(int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if(v == fa)
continue;
dfs(v, u, s);
suma = Plus(suma, A[v]);
sumb = Plus(sumb, B[v]);
}
ll t = Minu(deg[u], suma);;
ll invt = f_pow(t);
A[u] = invt;
B[u] = Plus(sumb, deg[u]) * invt % mod;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
memset(h, -1, sizeof(h));
std::cin >> n >> q >> x;
x--;
for(int i = 1; i < n; i++) {
int u, v;
std::cin >> u >> v;
u--, v--;
add(u, v), add(v, u);
deg[u]++, deg[v]++;
}
for(int st = 0; st < 1 << n; st++) {
dfs(x, -1, st);
dp[st] = B[x];
}
for(int i = 1; i <= q; i++) {
int st = 0;
int sz;
std::cin >> sz;
for(int i = 1; i <= sz; i++) {
int t;
std::cin >> t;
st |= 1 << (t-1);
}
ll ans = 0;
for(int s = st; s; s = (s-1) & st) {
int t = __builtin_popcount(s);
if(t & 1) {
ans = Plus(ans, dp[s]);
} else {
ans = Minu(ans, dp[s]);
}
}
std::cout << ans << '\n';
}
return 0;
}
P4117 [Ynoi2018] 五彩斑斓的世界
终于做掉了
先分块!考虑对块内怎么处理
这个 \(>x\) 的数减去 \(x\) 的修改性质不太好,考虑一些暴力。
我们注意到对于每一块,其最大值一定是不升的,
如果有 \(\sqrt n\) 块,那么每块的最大值都是 \(\mathcal O(n)\) 级别的,所有块的最值之和就是 \(\mathcal O(n\sqrt n)\),我们考虑把复杂度摊到这个东西上面
设定一个阈值 \(p\),考虑如果 \(x<p\) 时,我们把操作变为把 \(\le x\) 的数加 \(x\),再给整个区间减去 \(x\),就能花 \(\mathcal O(x)\cdot \mathcal O(DS)\) 的代价令最大值减去 \(\mathcal O(x)\)
如果 \(x\ge p\),对 \(x+1\sim v\) 暴力即可,花 \(\mathcal O(v-x)\cdot \mathcal O(DS)\) 的代价令最大值减小到 \(\mathcal O(x)\),即减小了 \(\mathcal O(v-x)\)
所以那么令 \(p=\frac{1}{2}v\),其中 \(v\) 是最大值,单块的总代价就是
用一个并查集实际上就够了。所以单块就是 \(\mathcal O(n)\)
均摊下来就是 \(\mathcal O((n+m)\sqrt n)\),空间复杂度 \(\mathcal O(n\sqrt n)\),把询问离线下来对每个块分别处理可以空间复杂度降到 \(\mathcal O(n)\)
猜我写了多久。五个钟。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <vector>
#include <cmath>
#include <queue>
const int N = 1e6 + 50;
int a[N];
//{{{ FAST IO
inline int read() {
int s = 0;
char c = getchar();
while (!isdigit(c))
c = getchar();
while (isdigit(c))
s = (s << 3) + (s << 1) + (c ^ 48), c = getchar();
return s;
}
inline void write(int x) {
static char stk[22];
static int tt = 0;
stk[tt++] = x % 10 + '0';
x /= 10;
while (x) {
stk[tt++] = x % 10 + '0';
x /= 10;
}
while (tt) {
putchar(stk[--tt]);
}
putchar('\n');
}
//}}}
struct query {
int tp, l, r, x;
}Q[N >> 1];
std::vector<int> ans;
int n, m;
int L[1010], R[1010];
int block, bnum;
void init() {
block = sqrt(n);
bnum = (n-1) / block + 1;
for (int i = 1; i <= bnum; i++) {
L[i] = (i-1) * block + 1, R[i] = std::min(i * block, n);
}
}
int fa[N / 10 + 10], sz[N / 10 + 10];
inline int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
inline void merge(int u, int v) {
int fu = find(u), fv = find(v);
if(fu == fv)
return ;
else
fa[fu] = fv, sz[fv] += sz[fu];
sz[fu] = 0;
}
void rebuild(int l, int r, int tag, int& v) {
for (int i = l; i <= r; i++) {
sz[find(a[i])] = 0;
if(a[i] != 0)
a[i] = find(a[i]) - tag;
}
v -= tag;
for (int i = 1; i <= tag; i++)
fa[i] = i;
for (int i = l; i <= r; i++)
sz[a[i]]++;
}
void solve(int b) {
int tag = 0, v = 0;
int l, r;
for (int i = L[b]; i <= R[b]; i++)
v = std::max(v, a[i]);
for (int i = 1; i <= v; i++)
sz[i] = 0, fa[i] = i;
for (int i = L[b]; i <= R[b]; i++)
sz[a[i]]++;
int qnum = 0;
for (int i = 1; i <= m; i++) {
if(Q[i].tp == 2)
qnum++;
l = std::max(Q[i].l, L[b]);
r = std::min(Q[i].r, R[b]);
if (l > r)
continue;
int x = Q[i].x;
if(Q[i].tp == 1) {
if(x == 0)
continue;
if (l == L[b] && r == R[b]) {
if (2 * x < v - tag) {
for (int j = 1 + tag; j <= x + tag; j++)
merge(j, j + x);
tag += x;
} else {
for (int j = x + 1 + tag; j <= v; j++)
merge(j, j - x);
while(!sz[v]) v--;
}
} else {
rebuild(L[b], R[b], tag, v);
tag = 0;
for (int j = l; j <= r; j++)
if (a[j] > x) {
sz[a[j]]--;
a[j] -= x;
sz[a[j]]++;
}
}
} else {
if(x + tag > v)
continue;
if (l == L[b] && r == R[b]) {
if(x != 0)
ans[qnum-1] += sz[find(x + tag)];
else
ans[qnum-1] += sz[0];
} else {
int cnt = 0;
rebuild(L[b], R[b], tag, v);
tag = 0;
for (int j = l; j <= r; j++)
if(a[j] == x)
cnt++;
ans[qnum-1] += cnt;
}
}
}
}
int main() {
n = read(), m = read();
for (int i = 1; i <= n; i++)
a[i] = read();
int cnt = 0;
for (int i = 1; i <= m; i++) {
Q[i].tp = read(), Q[i].l = read(), Q[i].r = read(), Q[i].x = read();
if(Q[i].tp == 2)
cnt++;
}
ans.resize(cnt);
init();
for (int k = 1; k <= bnum; k++) {
solve(k);
}
for (int i : ans)
write(i);
return 0;
}