暑集作题摘要
作题摘要(唐石集锦)
网络流 7.15 ~ 7.16
1. Luogu P2472 蜥蜴
算是板子题吧(
思路:
首先这些石柱的踩踏限制可以看作是流量的上界,让求的最大逃离数量显然就是最大流了。
对于可达的点,都连一条边,边权为...不怼,直接相连不能表示真正石柱的踩踏限制!
那怎么办?
可以对于每个石柱都搞一个虚点,由这个石柱的实点连向虚点来描绘限制,然后虚点向可达点的实点连边权 inf 的边(其实只要大于限制大小就行了...)。
警示后人:
平面距离指欧式距离 90 -> 100
代码:
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid id << 1
#define rid id << 1 | 1
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int E = 50000 + 7, V = 1000 + 7, mod = 1e9 + 7, B = 300, K = 40, N = 30; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = 0x3f3f3f3f;
const ull base = 1331;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int cnt = 1, r, c, d, n, pos[N][N], tot, head[V], cur[V], dep[V];
struct Star
{
int flow, nxt, to;
}e[E << 1];
bool bfs(int s, int t)
{
memset(dep, 0, sizeof(dep));
cur[s] = head[s], dep[s] = 1;
queue <int> q;
q.push(s);
while (q.size())
{
int x = q.front();
q.pop();
for (int i = head[x]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == 0)
{
cur[to] = head[to], dep[to] = dep[x] + 1;
if (to == t) return true;
q.push(to);
}
}
}
return false;
}
int dfs(int s, int flow, int t)
{
if (s == t || flow <= 0) return flow;
int rest = flow;
for (int i = cur[s]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == dep[s] + 1)
{
int k = dfs(to, min(rest, e[i].flow), t);
if (k <= 0) dep[to] = 0;
rest -= k;
e[i].flow -= k;
e[i ^ 1].flow += k;
if (rest <= 0) break;
}
}
return flow - rest;
}
int Dinic(int s, int t)
{
int ans = 0;
while (bfs(s, t))
{
ans += dfs(s, inf, t);
}
return ans;
}
inline void add(int x, int y, int w)
{
e[++cnt] = {w, head[x], y};
head[x] = cnt;
e[++cnt] = {0, head[y], x};
head[y] = cnt;
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> r >> c >> d;
for (int i = 0; i <= r + 1; i++) for (int j = 0; j <= c + 1; j++) pos[i][j] = ++tot;
for (int i = 1; i <= r; i++)
{
for (int j = 1; j <= c; j++)
{
char ch; cin >> ch;
int x = ch - '0';
add(pos[i][j], pos[i][j] + tot, x);
for (int dx = i - d; dx <= i + d; dx++)
{
for (int dy = j - d; dy <= j + d; dy++)
{
if (dx == i && dy == j) continue; // ???
if (dx < 0 || dy < 0 || dx > r + 1 || dy > c + 1) continue;
int tmp = (dx - i) * (dx - i) + (dy - j) * (dy - j);
if (tmp > d * d) continue;
add(pos[i][j] + tot, pos[dx][dy], inf);
}
}
}
}
int s = tot + 1, t = tot + 2, qwq = 0;
for (int i = 0; i <= r + 1; i++) add(pos[i][0], t, inf), add(pos[i][c + 1], t, inf);
for (int i = 0; i <= c + 1; i++) add(pos[0][i], t, inf), add(pos[r + 1][i], t, inf);
for (int i = 1; i <= r; i++)
{
for (int j = 1; j <= c; j++)
{
char ch; cin >> ch;
if (ch == 'L') add(s, pos[i][j], 1), ++qwq;
}
}
cout << qwq - Dinic(s, t);
QED;
}
2. Luogu P3324 星际战争
也挺板子的qwq
思路:
直接去流时间好像不现实,所以一个思路就是直接二分然后最大流检验正确性。
但是小数二分容易被卡,所以一个经典(?)的技巧就是转为整数然后最后在转回小数输出。
每次向每个武器连 \(time \times B_i\) 的边权。
每个武器向每个敌人连 inf。
每个敌人向汇源连 \(A_i\)
最大流是 \(\sum_{i=1}^n{A_i}\) 时方案合法。
警示后人:
无。
代码:
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid id << 1
#define rid id << 1 | 1
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int E = 50000 + 7, V = 1000 + 7, mod = 1e9 + 7, B = 300, K = 40, N = 60; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = 0x3f3f3f3f3f3f3f3f, base_double = 10000;
const ull base = 10000;
const ld P = 0.5, eps = 1e-6, inf_double = 1e300;
int head[V], cur[V], dep[V], cnt = 1;
struct Star
{
int flow, nxt, to;
}e[E << 1];
bool bfs(int s, int t)
{
memset(dep, 0, sizeof(dep));
queue <int> q;
cur[s] = head[s], dep[s] = 1;
q.push(s);
while (q.size())
{
int x = q.front();
q.pop();
for (int i = head[x]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == 0)
{
cur[to] = head[to], dep[to] = dep[x] + 1;
if (to == t) return true;
q.push(to);
}
}
}
return false;
}
int dfs(int s, int flow, int t)
{
if (s == t || flow <= 0) return flow;
int rest = flow;
for (int i = head[s]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == dep[s] + 1)
{
int k = dfs(to, min(rest, e[i].flow), t);
if (k <= 0) dep[to] = 0;
rest -= k;
e[i].flow -= k;
e[i ^ 1].flow += k;
if (rest <= 0) break;
}
}
return flow - rest;
}
int Dinic(int s, int t)
{
int ans = 0;
while (bfs(s, t))
{
ans += dfs(s, inf, t);
}
return ans;
}
void add(int x, int y, int w)
{
e[++cnt] = {w, head[x], y};
head[x] = cnt;
e[++cnt] = {0, head[y], x};
head[y] = cnt;
}
int a[N], b[N], n, m, l_head[V];
bool vis[N][N];
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
int s = 0, t = n + m + 1;
for (int i = 1; i <= n; i++) cin >> a[i], a[i] = a[i] * base_double;
for (int i = 1; i <= m; i++) cin >> b[i];
for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++)
{
bool ch; cin >> ch;
if (ch) add(i + n, j, inf);
vis[i][j] = ch;
}
int l = 0, r = 1e5 * base_double, mid, ans = r, res = 0;
for (int i = 1; i <= n; i++) res += a[i];
while (l <= r)
{
mid = (l + r) >> 1;
memset(cur, 0, sizeof(cur));
memset(head, 0, sizeof(head));
for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++)
{
bool ch = vis[i][j];
if (ch) add(i + n, j, inf);
}
for (int i = 1; i <= n; i++) add(i, t, a[i]);
for (int i = 1; i <= m; i++) add(s, i + n, b[i] * mid);
int tmp = Dinic(s, t);
if (tmp >= res) ans = mid, r = mid - 1;
else l = mid + 1;
cnt = 1;
}
cout << fixed << setprecision(4) << (ld) ans / (1.0 * base_double);
QED;
}
3. Luogu P4311 士兵占领
思路:
最小有两种方向,第一是最小割,第二便是将最小转最大或二分检验跑最大流
这题并不是很适合使用最小割,也不适合使用二分。
注意一件事情,假设现在已经放满了,则放置最少等价于删除最多的士兵。
但删除最多还是不好做,又注意到,每次删除一个点会影响一行和一列,所以可以考虑转为类似二分图的东西(行之间不影响,列之间不影响,故为二分图)。
具体就是源点向行连该行最多删除数量,每有一个可以删除(可以放置)的点便由行向列连一条1的边表示同时删除,列向汇点连该列最多删除数量。
全集减去最大删除点数即为答案。
警示后人:
数组不要开小了:60 -> 100 + inf 小时
代码:
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid id << 1
#define rid id << 1 | 1
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int E = 500000 + 7, V = 10000 + 7, mod = 1e9 + 7, B = 300, K = 40, N = 120; // !!!!!!!!
// #define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = 0x3f3f3f3f, base_double = 10000;
const ull base = 10000;
const ld P = 0.5, eps = 1e-6, inf_double = 1e300;
int head[V], cur[V], dep[V], cnt = 1;
struct Star
{
int flow, nxt, to;
}e[E << 1];
bool bfs(int s, int t)
{
memset(dep, 0, sizeof(dep));
queue <int> q;
cur[s] = head[s], dep[s] = 1;
q.push(s);
while (q.size())
{
int x = q.front();
q.pop();
for (int i = head[x]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == 0)
{
cur[to] = head[to], dep[to] = dep[x] + 1;
if (to == t) return true;
q.push(to);
}
}
}
return false;
}
int dfs(int s, int flow, int t)
{
if (s == t || flow <= 0) return flow;
int rest = flow;
for (int i = head[s]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == dep[s] + 1)
{
int k = dfs(to, min(rest, e[i].flow), t);
if (k <= 0) dep[to] = 0;
rest -= k;
e[i].flow -= k;
e[i ^ 1].flow += k;
if (rest <= 0) break;
}
}
return flow - rest;
}
int Dinic(int s, int t)
{
int ans = 0;
while (bfs(s, t))
{
ans += dfs(s, inf, t);
}
return ans;
}
void add(int x, int y, int w)
{
e[++cnt] = {w, head[x], y};
head[x] = cnt;
e[++cnt] = {0, head[y], x};
head[y] = cnt;
}
int a[N], b[N], n, m, k, cnt_a[N], cnt_b[N];
bool vis[N][N];
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m >> k;
int s = n + m + 1, t = n + m + 2;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= m; i++) cin >> b[i];
for (int i = 1; i <= k; i++)
{
int x, y; cin >> x >> y;
vis[y][x] = 1;
cnt_a[y]++, cnt_b[x]++;
}
for (int i = 1; i <= n; i++) add(s, i, m - a[i] - cnt_a[i]);
for (int i = 1; i <= m; i++) add(i + n, t, n - b[i] - cnt_b[i]);
for (int i = 1; i <= n; i++) if (m - a[i] - cnt_a[i] < 0) {cout << "JIONG!"; return 0;}
for (int i = 1; i <= m; i++) if (n - b[i] - cnt_b[i] < 0) {cout << "JIONG!"; return 0;}
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (!vis[i][j]) add(i, n + j, 1);
cout << n * m - k - Dinic(s, t);
QED;
}
4. Luogu P3191 紧急疏散
思路:
由于每个单位时间内只能移动一格的限制让人很恼火,好像根本不可能用流来刻画。
其实可以枚举时间,然后建图,由低层时间节点连向高层时间节点(说的有点抽象,意会一下)。
具体一点就是设 \(u_{i j}\) 表示在时间 i 时的节点 j。
这样由每个时间的出口节点都可以向外界(汇源)连一条 1 的边代表可以向外走一个人。
每个空地都向下一个时间的相邻节点(包括本身)连 inf 边。
警示后人:
无。
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <iomanip>
#include <cassert>
#include <climits>
#include <queue>
using namespace std;
#define lid id << 1
#define rid id << 1 | 1
#define fi first
#define se second
#define emp emplace_back
#define pb push_back
#define mp make_pair
#define IL inline
#define reg register
const int N = 30, M = 0, V = 200000, E = 1000000, B = 0, mod = 0;
#define int long long
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair <int, int>;
using pid = pair <int, double>;
const int inf = INT_MAX, inf2 = LONG_LONG_MAX / 2;
const ld eps = 1e-8, inf_double = 1e300;
const ull base = 1331;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int head[V], cur[V], dep[V], cnt = 1;
struct Star
{
int flow, nxt, to;
}e[E << 1];
bool bfs(int s, int t)
{
memset(dep, 0, sizeof(dep));
queue <int> q;
cur[s] = head[s], dep[s] = 1;
q.push(s);
while (q.size())
{
int x = q.front();
q.pop();
for (int i = head[x]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == 0)
{
cur[to] = head[to], dep[to] = dep[x] + 1;
if (to == t) return true;
q.push(to);
}
}
}
return false;
}
int dfs(int s, int flow, int t)
{
if (s == t || flow <= 0) return flow;
int rest = flow;
for (int i = head[s]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == dep[s] + 1)
{
int k = dfs(to, min(rest, e[i].flow), t);
if (k <= 0) dep[to] = 0;
rest -= k;
e[i].flow -= k;
e[i ^ 1].flow += k;
if (rest <= 0) break;
}
}
return flow - rest;
}
int Dinic(int s, int t)
{
int ans = 0;
while (bfs(s, t))
{
ans += dfs(s, inf, t);
}
return ans;
}
void add(int x, int y, int w)
{
e[++cnt] = {w, head[x], y};
head[x] = cnt;
e[++cnt] = {0, head[y], x};
head[y] = cnt;
}
int a[N][N], s, t, n, m, sum, ans = 0;
int vis[N][N];
bool check(int floor)
{
int ba = floor * n * m, b = (floor - 1) * n * m;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
add(b + (i - 1) * m + j, ba + (i - 1) * m + j, inf);
if (vis[i][j] == 1) {add(ba + (i - 1) * m + j, t, 1); continue;}
if (vis[i][j] == 2) continue;
for (int k = 0; k < 4; k++)
{
int x = i + dx[k], y = j + dy[k];
if (x < 1 || x > n || y < 1 || y > m) continue;
if (vis[x][y] == 2) continue;
add(b + (i - 1) * m + j, ba + (x - 1) * m + y, inf);
}
}
}
ans += Dinic(s, t);
return ans == sum;
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
s = 0, t = n * n * m * m + 1;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++)
{
char op; cin >> op;
if (op == '.') sum++, add(s, (i - 1) * m + j, 1);
if (op == 'D') vis[i][j] = 1;
if (op == 'X') vis[i][j] = 2;
}
for (int i = 1; i <= n * m; i++)
{
if (check(i))
{
cout << i;
return 0;
}
}
cout << "impossible";
return 0;
}
5. Luogu P5038 奇怪的游戏
思路:
由于每次只会给网格里相邻节点同时加一,所以好像可是使用黑白染色构建二分图。
现在考虑怎么求最优方案,首先想到二分,可以二分网格最后变成的数。
这时就出现了问题:如果黑点和白点数量不相等这个数的合法性是没有单调性的!
分讨一下:
设 \(c_1, c_2, s_1, s_2, x\) 分别为黑点数量,白点数量,黑点的权值和,白点的权值和,最后变成的数。
则由黑点和白点的操作次数相同可得:
移项:
这也解释了为什么黑白数量相同时是有单调性的了。
所以对于黑白数量不相等的情况,求出 \(x\),然后检验合法性即可。
检验方法:由源点连向黑点操作次数,白点连向汇点操作次数,然后相邻点连 inf 即可(注意:\((s_1 != s_2) 且 (c_1 == c_2)\) 时无解)。
警示后人:
网络流的 inf 要开的足够大:40 -> 100 + inf 小时
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <iomanip>
#include <cassert>
#include <climits>
#include <queue>
using namespace std;
#define lid id << 1
#define rid id << 1 | 1
#define fi first
#define se second
#define emp emplace_back
#define pb push_back
#define mp make_pair
#define IL inline
#define reg register
const int N = 50, M = 0, V = 2000, E = 20000, B = 0, mod = 0;
#define int long long
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair <int, int>;
using pid = pair <int, double>;
const int inf = INT_MAX, inf2 = LONG_LONG_MAX / 2;
const ld eps = 1e-8, inf_double = 1e300;
const ull base = 1331;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int head[V], cur[V], dep[V], cnt = 1;
struct Star
{
int flow, nxt, to;
}e[E << 1];
bool bfs(int s, int t)
{
memset(dep, 0, sizeof(dep));
queue <int> q;
cur[s] = head[s], dep[s] = 1;
q.push(s);
while (q.size())
{
int x = q.front();
q.pop();
for (int i = head[x]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == 0)
{
cur[to] = head[to], dep[to] = dep[x] + 1;
if (to == t) return true;
q.push(to);
}
}
}
return false;
}
int dfs(int s, int flow, int t)
{
if (s == t || flow <= 0) return flow;
int rest = flow;
for (int i = head[s]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == dep[s] + 1)
{
int k = dfs(to, min(rest, e[i].flow), t);
if (k <= 0) dep[to] = 0;
rest -= k;
e[i].flow -= k;
e[i ^ 1].flow += k;
if (rest <= 0) break;
}
}
return flow - rest;
}
int Dinic(int s, int t)
{
int ans = 0;
while (bfs(s, t))
{
ans += dfs(s, inf2, t);
}
return ans;
}
void add(int x, int y, int w)
{
e[++cnt] = {w, head[x], y};
head[x] = cnt;
e[++cnt] = {0, head[y], x};
head[y] = cnt;
}
int a[N][N], s, t, n, m;
bool check(int x)
{
memset(head, 0, sizeof(head)), memset(cur, 0, sizeof(cur));
cnt = 1;
int maxsiz = 0, sum = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if ((i + j) & 1) add(s, (i - 1) * m + j, x - a[i][j]), sum += x - a[i][j];
else add((i - 1) * m + j, t, x - a[i][j]), maxsiz += x - a[i][j];
}
}
if (maxsiz != sum) return false;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (!((i + j) & 1)) continue;
for (int k = 0; k <= 3; k++)
{
int nx = i + dx[k], ny = j + dy[k];
if (nx < 1 || nx > n || ny < 1 || ny > m) continue;
add((i - 1) * m + j, (nx - 1) * m + ny, inf2);
}
}
}
return sum == Dinic(s, t);
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T; cin >> T;
while (T--)
{
cin >> n >> m;
s = 0, t = n * m + 1;
int l = 0, r = inf, mid = 0, ans = -1, L = 0, s1 = 0, s2 = 0, c1 = 0, c2 = 0;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cin >> a[i][j], L = max(a[i][j], L);
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++)
{
if ((i + j) & 1) s1 += a[i][j], c1++;
else s2 += a[i][j], c2++;
}
l = L;
while (l <= r)
{
mid = (l + r) >> 1;
if (check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
if ((n * m) & 1) // c1 != c2
{
// assert(T != 1);
int x = (s1 - s2) / (c1 - c2);
if (check(x)) ans = x;
else ans = -1;
if (x < L) ans = -1;
}
int res = 0;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) res += ans - a[i][j];
if (ans == -1) cout << ans << '\n';
else cout << res / 2 << '\n';
}
return 0;
}
6. Luogu P1646 happiness
思路:
首先对于每个同学都可以建两条边连向源点和汇点,分别代表不选文和不选理。
然后对于题目的依托权值,本质是一个东西,对于图上两个点,\(i\) 不选理或 \(j\) 不选理则必须割掉(增加代价)同时选理的happy值。
这个东西怎么做呢?
好像我偶然灵机一动的做法竟是真的。
对于每两个点,可以建一个虚点,由虚点连向汇点或源点happy值,然后这两个点连向这个虚点 inf 边来限制不能逃避失败的代价。
至于什么时候连汇点还是源点,请自行模拟。
警示后人:
这下不是唐错了。
如果你是喜欢把点拆成一个文一个理的小朋友的话,注意连同时选理的代价和同时选文的代价的顺序(是连在文科点还是理科点)。
为什么呢?
因为可以同时删除一个人的文科和理科情况,所以爆炸了!
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <iomanip>
#include <cassert>
#include <climits>
#include <queue>
using namespace std;
#define lid id << 1
#define rid id << 1 | 1
#define fi first
#define se second
#define emp emplace_back
#define pb push_back
#define mp make_pair
#define IL inline
#define reg register
const int N = 1200, M = 0, V = 61000, E = 2e6 + 100, B = 0, mod = 0;
#define int long long
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair <int, int>;
using pid = pair <int, double>;
const int inf = INT_MAX, inf2 = LONG_LONG_MAX / 2;
const ld eps = 1e-8, inf_double = 1e300;
const ull base = 1331;
int head[V], cur[V], dep[V], cnt = 1;
struct Star
{
int flow, nxt, to;
}e[E << 1];
bool bfs(int s, int t)
{
memset(dep, 0, sizeof(dep));
queue <int> q;
cur[s] = head[s], dep[s] = 1;
q.push(s);
while (q.size())
{
int x = q.front();
q.pop();
for (int i = head[x]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == 0)
{
cur[to] = head[to], dep[to] = dep[x] + 1;
if (to == t) return true;
q.push(to);
}
}
}
return false;
}
int dfs(int s, int flow, int t)
{
if (s == t || flow <= 0) return flow;
int rest = flow;
for (int i = head[s]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == dep[s] + 1)
{
int k = dfs(to, min(rest, e[i].flow), t);
if (k <= 0) dep[to] = 0;
rest -= k;
e[i].flow -= k;
e[i ^ 1].flow += k;
if (rest <= 0) break;
}
}
return flow - rest;
}
int Dinic(int s, int t)
{
int ans = 0;
while (bfs(s, t))
{
ans += dfs(s, inf, t);
}
return ans;
}
void add(int x, int y, int w)
{
e[++cnt] = {w, head[x], y};
head[x] = cnt;
e[++cnt] = {0, head[y], x};
head[y] = cnt;
}
bool check_sqrt(int x, int y)
{
int l = 1, r = x + y, ans = 0;
while (l <= r)
{
int mid = (l + r) >> 1;
if (x * x + y * y <= mid * mid) ans = mid, r = mid - 1;
else l = mid + 1;
}
return (ans * ans) == (x * x + y * y);
}
bool check(int x, int y)
{
return (__gcd(x, y) == 1) && check_sqrt(x, y);
}
int a[N][N], n, m, s, t = V - 1, b[N][N], tot;
inline int P(int i, int j)
{
return (i - 1) * m + j;
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int sum = 0; cin >> n >> m;
tot = 2 * n * m;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cin >> a[i][j], sum += a[i][j];
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cin >> b[i][j], sum += b[i][j];
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) add(s, P(i, j), a[i][j]), add(P(i, j) + n * m, t, b[i][j]), add(P(i, j), P(i, j) + n * m, inf);
for (int i = 1; i < n; i++) for (int j = 1; j <= m; j++)
{
int x; cin >> x; sum += x;
++tot;
add(s, tot, x);
add(tot, P(i, j), inf);
add(tot, P(i + 1, j), inf);
}
for (int i = 1; i < n; i++) for (int j = 1; j <= m; j++)
{
int x; cin >> x; sum += x;
++tot;
add(tot, t, x);
add(P(i, j) + n * m, tot, inf);
add(P(i + 1, j) + n * m, tot, inf);
}
for (int i = 1; i <= n; i++) for (int j = 1; j < m; j++)
{
int x; cin >> x; sum += x;
++tot;
add(s, tot, x);
add(tot, P(i, j), inf);
add(tot, P(i, j + 1), inf);
}
for (int i = 1; i <= n; i++) for (int j = 1; j < m; j++)
{
int x; cin >> x; sum += x;
++tot;
add(tot, t, x);
add(P(i, j) + n * m, tot, inf);
add(P(i, j + 1) + n * m, tot, inf);
}
cout << sum - Dinic(s, t);
return 0;
}
7. Luogu P1791 人员雇佣
思路:
比较神奇的题目,随手一写结果对了...。
首先可以只考虑两个人。
如果两个人都没来,则会损失 \(2 \times E_{ij}\)。
如果其中一个人没来,则会损失 \(3 \times E_{ij}\)
好像和上一题十分相似哎!
不过有一种更加简单的方法,对于每个一个选一个不选可以连一条 \(2 \times E_{ij}\) 的边,对于每个不选的点都花费 \(\sum_{j=1}^n{E_{ij}}\) 的边权。
浅浅手模一下发现是对的。
警示后人:
无。
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <iomanip>
#include <cassert>
#include <climits>
#include <queue>
using namespace std;
#define lid id << 1
#define rid id << 1 | 1
#define fi first
#define se second
#define emp emplace_back
#define pb push_back
#define mp make_pair
#define IL inline
#define reg register
const int N = 1200, M = 0, V = 2000, E = 2e6 + 100, B = 0, mod = 0;
#define int long long
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair <int, int>;
using pid = pair <int, double>;
const int inf = INT_MAX, inf2 = LONG_LONG_MAX / 2;
const ld eps = 1e-8, inf_double = 1e300;
const ull base = 1331;
int head[V], cur[V], dep[V], cnt = 1;
struct Star
{
int flow, nxt, to;
}e[E << 1];
bool bfs(int s, int t)
{
memset(dep, 0, sizeof(dep));
queue <int> q;
cur[s] = head[s], dep[s] = 1;
q.push(s);
while (q.size())
{
int x = q.front();
q.pop();
for (int i = head[x]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == 0)
{
cur[to] = head[to], dep[to] = dep[x] + 1;
if (to == t) return true;
q.push(to);
}
}
}
return false;
}
int dfs(int s, int flow, int t)
{
if (s == t || flow <= 0) return flow;
int rest = flow;
for (int i = head[s]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == dep[s] + 1)
{
int k = dfs(to, min(rest, e[i].flow), t);
if (k <= 0) dep[to] = 0;
rest -= k;
e[i].flow -= k;
e[i ^ 1].flow += k;
if (rest <= 0) break;
}
}
return flow - rest;
}
int Dinic(int s, int t)
{
int ans = 0;
while (bfs(s, t))
{
ans += dfs(s, inf, t);
}
return ans;
}
void add(int x, int y, int w)
{
e[++cnt] = {w, head[x], y};
head[x] = cnt;
e[++cnt] = {0, head[y], x};
head[y] = cnt;
}
bool check_sqrt(int x, int y)
{
int l = 1, r = x + y, ans = 0;
while (l <= r)
{
int mid = (l + r) >> 1;
if (x * x + y * y <= mid * mid) ans = mid, r = mid - 1;
else l = mid + 1;
}
return (ans * ans) == (x * x + y * y);
}
bool check(int x, int y)
{
return (__gcd(x, y) == 1) && check_sqrt(x, y);
}
int a[N], n, s, t;
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
int sum = 0;
t = n + 1;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++)
{
int S = 0;
for (int j = 1; j <= n; j++)
{
int x; cin >> x; S += x;
sum += x;
add(i, j, x << 1);
}
add(s, i, a[i]);
add(i, t, S);
}
cout << sum - Dinic(s, t);
return 0;
}
8. Luogu P3973 线性代数
思路:
乍一看,这是网络流吗???
但是,我们充分发扬人类智慧,\(A_i\) 为零代表了什么?
其实就是给贡献减去 \(\sum_{j=1}^n{B_{ij}+B_{ji}} - C_{i}\)
不过要注意 \(B_{ii}\) 只能减一次。
不过即使想出了做法,怎么写呢?
如果直接写,处理行和列,显然会有重复的贡献。
但有一种写法写起来非常简单,考虑将行和列分开处理。
先处理行,这些行之间显然互不影响,直接在源点到节点的路径上设边权即可。
再考虑处理列,其实本质就是,当一个选一个不选时会有一个贡献。
不过我向来不用正常解法,首先选(为 1)一个节点肯定会贡献 \(C_i\),所以先把它连上,而不选则不会有任何贡献。
当两个节点其中任意一个选时,会产生 \(B_{ij}\) 的贡献,直接使用 happiness 的做法即可。
警示后人:
不要把新增出来的点 \(u\) 写成 \(t\):99 -> 100 (太水了)
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <iomanip>
#include <cassert>
#include <climits>
#include <queue>
using namespace std;
#define lid id << 1
#define rid id << 1 | 1
#define fi first
#define se second
#define emp emplace_back
#define pb push_back
#define mp make_pair
#define IL inline
#define reg register
const int N = 600, M = 0, V = 5e5 + 10, E = 6e6 + 100, B = 0, mod = 0;
#define int long long
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair <int, int>;
using pid = pair <int, double>;
const int inf = INT_MAX, inf2 = LONG_LONG_MAX / 2;
const ld eps = 1e-8, inf_double = 1e300;
const ull base = 1331;
int head[V], cur[V], dep[V], cnt = 1;
struct Star
{
int flow, nxt, to, from;
}e[E << 1];
bool bfs(int s, int t)
{
memset(dep, 0, sizeof(dep));
queue <int> q;
cur[s] = head[s], dep[s] = 1;
q.push(s);
while (q.size())
{
int x = q.front();
q.pop();
for (int i = head[x]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == 0)
{
cur[to] = head[to], dep[to] = dep[x] + 1;
if (to == t) return true;
q.push(to);
}
}
}
return false;
}
int dfs(int s, int flow, int t)
{
if (s == t || flow <= 0) return flow;
int rest = flow;
for (int i = head[s]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == dep[s] + 1)
{
int k = dfs(to, min(rest, e[i].flow), t);
if (k <= 0) dep[to] = 0;
rest -= k;
e[i].flow -= k;
e[i ^ 1].flow += k;
if (rest <= 0) break;
}
}
return flow - rest;
}
int Dinic(int s, int t)
{
int ans = 0;
while (bfs(s, t))
{
ans += dfs(s, inf, t);
}
return ans;
}
void add(int x, int y, int w)
{
e[++cnt] = {w, head[x], y, x};
head[x] = cnt;
e[++cnt] = {0, head[y], x, y};
head[y] = cnt;
}
int b[N][N], c[N], n, s, t, sum, tot;
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
t = n + 1, tot = t + 1;
for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) cin >> b[i][j], sum += b[i][j];
for (int i = 1; i <= n; i++) cin >> c[i];
for (int i = 1; i <= n; i++) add(s, i, c[i]), add(i, t, 0);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
add(++tot, t, b[i][j]);
add(i, tot, inf);
add(j, tot, inf);
}
}
cout << max(0ll, sum - Dinic(s, t));
return 0;
}
9. Luogu P3227 切糕
思路:
很有趣的题,可以练习 inf 边的使用。
没图咋写?
不写了。
警示后人:
连边判断要分着,就是上界和下界要分开,因为一个极大值可能会因为下界问题继续连边:50 -> 100 + inf 小时。
代码:
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <bitset>
#include <set>
#include <map>
#include <iomanip>
#include <cassert>
#include <climits>
#include <queue>
using namespace std;
#define lid id << 1
#define rid id << 1 | 1
#define fi first
#define se second
#define emp emplace_back
#define pb push_back
#define mp make_pair
#define IL inline
#define reg register
const int N = 50, M = 0, V = 5e5 + 10, E = 6e6 + 100, B = 0, mod = 0;
#define int long long
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair <int, int>;
using pid = pair <int, double>;
const int inf = INT_MAX, inf2 = LONG_LONG_MAX / 2;
const ld eps = 1e-8, inf_double = 1e300;
const ull base = 1331;
int head[V], cur[V], dep[V], cnt = 1;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
struct Star
{
int flow, nxt, to;
}e[E << 1];
bool bfs(int s, int t)
{
memset(dep, 0, sizeof(dep));
queue <int> q;
cur[s] = head[s], dep[s] = 1;
q.push(s);
while (q.size())
{
int x = q.front();
q.pop();
for (int i = head[x]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == 0)
{
cur[to] = head[to], dep[to] = dep[x] + 1;
if (to == t) return true;
q.push(to);
}
}
}
return false;
}
int dfs(int s, int flow, int t)
{
if (s == t || flow <= 0) return flow;
int rest = flow;
for (int i = head[s]; i; i = e[i].nxt)
{
int to = e[i].to;
if (e[i].flow > 0 && dep[to] == dep[s] + 1)
{
int k = dfs(to, min(rest, e[i].flow), t);
if (k <= 0) dep[to] = 0;
rest -= k;
e[i].flow -= k;
e[i ^ 1].flow += k;
if (rest <= 0) break;
}
}
return flow - rest;
}
int Dinic(int s, int t)
{
int ans = 0;
while (bfs(s, t))
{
ans += dfs(s, inf, t);
}
return ans;
}
void add(int x, int y, int w)
{
e[++cnt] = {w, head[x], y};
head[x] = cnt;
e[++cnt] = {0, head[y], x};
head[y] = cnt;
}
int a[N][N][N], p, q, r, d, s, t, tot, bl[N][N][N];
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> p >> q >> r >> d;
for (int i = 1; i <= r; i++) for (int j = 1; j <= p; j++) for (int k = 1; k <= q; k++) cin >> a[j][k][i];
for (int i = 0; i <= r; i++) for (int j = 1; j <= p; j++) for (int k = 1; k <= q; k++) bl[j][k][i] = ++tot;
t = tot + 1;
for (int i = 1; i <= p; i++)
{
for (int j = 1; j <= q; j++)
{
add(s, bl[i][j][0], inf);
for (int k = 1; k <= r; k++)
{
add(bl[i][j][k - 1], bl[i][j][k], a[i][j][k]);
for (int l = 0; l < 4; l++)
{
int x = i + dx[l], y = j + dy[l], z1 = k - d - 1, z2 = k + d;
if (x < 1 || x > p || y < 1 || y > q) continue;
if (z1 >= 0) add(bl[i][j][k - 1], bl[x][y][z1], inf);
if (z2 <= r) add(bl[x][y][z2], bl[i][j][k], inf);
}
}
add(bl[i][j][r], t, inf);
}
}
cout << Dinic(s, t);
return 0;
}
10. Luogu P4126 最小割
思路:
不写了,自己找去,不是我自己想的。
详见:神!
警示后人:
tarjan别写假,边开大点:60 -> 100
代码:
点击查看代码
可持久化线段树
写的还是比较爽的(平均一道题调 inf 小时)
Luogu P2633 Count On Tree
思路
板子题,直接使用主席树进行前缀和,注意到使用子树进行前缀和就退化成线段树合并了,而且几乎不可能维护链上信息,那么要用魔法打败魔法,所以可以维护链上前缀和。
使用经典套路将查询的链转化为到根的差分 \(f_{ij} = s_i + s_j - s_{lca} - s_{f_{lca}}\)
主席树轻松维护。
警示后人
因为网络流写多导致只建单向边... 20 -> 100 + eps 小时
代码
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid ls[id]
#define rid rs[id]
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 1e5 + 10, E = 5000 + 7, V = 200 + 7, mod = 998244353, B = 0, K = 0; // !!!!!!!!
// #define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const int base = 3000;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int cnt[N], root[N], n, m;
class Sem_Tree
{
public :
int sum[N << 6], ls[N << 6], rs[N << 6], tot = 0;
void PushUp(int id) {sum[id] = sum[lid] + sum[rid];}
void Insert(int &id, int u, int l, int r, int pos)
{
id = ++tot; sum[id] = sum[u], ls[id] = ls[u], rs[id] = rs[u];
if (l == r) return sum[id]++, void();
int mid = (l + r) >> 1;
if (pos <= mid) Insert(lid, ls[u], l, mid, pos);
else Insert(rid, rs[u], mid + 1, r, pos);
PushUp(id);
}
int Query(int a, int b, int c, int d, int l, int r, int k)
{
if (l == r) return l;
int has = sum[ls[a]] + sum[ls[b]] - sum[ls[c]] - sum[ls[d]], mid = (l + r) >> 1;
if (k <= has) return Query(ls[a], ls[b], ls[c], ls[d], l, mid, k);
else return Query(rs[a], rs[b], rs[c], rs[d], mid + 1, r, k - has);
}
}T;
int dep[N], siz[N], top[N], f[N], son[N];
vector <int> G[N];
void dfs(int x, int fa)
{
siz[x] = 1, f[x] = fa, dep[x] = dep[fa] + 1;
T.Insert(root[x], root[fa], 1, n, cnt[x]);
for (auto &to : G[x])
{
if (to == fa) continue;
dfs(to, x);
siz[x] += siz[to];
if (siz[to] > siz[son[x]]) son[x] = to;
}
}
void dfs1(int x, int fa)
{
top[x] = fa;
if (son[x]) dfs1(son[x], fa);
for (auto &to : G[x])
{
if (to == f[x] || to == son[x]) continue;
dfs1(to, to);
}
}
int Lca(int x, int y)
{
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]]) swap(x, y);
x = f[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
return x;
}
void add(int x, int y) {G[x].emp(y);}
int a[N];
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i], cnt[i] = a[i];
sort(a + 1, a + 1 + n);
int len = unique(a + 1, a + 1 + n) - (a + 1);
for (int i = 1; i <= n; i++) cnt[i] = lower_bound(a + 1, a + 1 + len, cnt[i]) - a;
for (int i = 1; i < n; i++)
{
int x, y; cin >> x >> y; add(x, y), add(y, x);
}
dfs(1, 0), dfs1(1, 1);
int last = 0;
for (int i = 1; i <= m; i++)
{
int u, v, k; cin >> u >> v >> k;
u ^= last;
int lca = Lca(u, v);
cout << (last = a[T.Query(root[u], root[v], root[lca], root[f[lca]], 1, n, k)]) << '\n';
}
QED;
} // 😡
Luogu P4735 最大异或和
思路
可持久化01Trie板子
区间异或和套路使用前缀和进行求解,问题转化为求区间一个数与另一个数异或和最大,直接上可持久化01Trie轻松维护。
警示后人
一定要提前插入一个 0 元素,因为有一种合法情况是什么也不异或 81 -> 100 + inf 小时。
代码
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid ls[id]
#define rid rs[id]
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 3e5 + 10, E = 5000 + 7, V = 200 + 7, mod = 998244353, B = 0, K = 0, MAXK = 30; // !!!!!!!!
// #define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const int base = 3000;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int root[N << 2];
class Trie
{
public :
int cnt[N << 6], ch[N << 6][2], tot;
void Insert(int &id, int u, int rest, int val)
{
if (!id) id = ++tot; // !!!
if (rest == MAXK) cnt[id] = cnt[u] + 1;
if (rest < 0) return;
int i = (val >> rest) & 1, j = i ^ 1;
ch[id][i] = ++tot;
ch[id][j] = ch[u][j];
cnt[ch[id][i]] = cnt[ch[u][i]] + 1;
Insert(ch[id][i], ch[u][i], rest - 1, val);
}
int Query(int lt, int rt, int rest, int val)
{
if (rest < 0) return 0;
int i = (val >> rest) & 1, j = i ^ 1;
int has = cnt[ch[rt][j]] - cnt[ch[lt][j]];
if (has > 0) return Query(ch[lt][j], ch[rt][j], rest - 1, val) + (1 << rest);
else return Query(ch[lt][i], ch[rt][i], rest - 1, val);
}
}T;
int sum = 0, n, m;
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
++n;
T.Insert(root[1], 0, MAXK, 0);
for (int i = 2; i <= n; i++)
{
int x; cin >> x; sum ^= x;
// cerr << sum << ' ';
T.Insert(root[i], root[i - 1], MAXK, sum);
}
for (int i = 1; i <= m; i++)
{
char ch; cin >> ch;
if (ch == 'A')
{
int x; cin >> x;
sum ^= x, ++n;
T.Insert(root[n], root[n - 1], MAXK, sum);
}
else
{
int l, r, x; cin >> l >> r >> x;
cout << T.Query(root[max(0, l - 1)], root[r], MAXK, x ^ sum) << '\n';
}
}
QED;
} // 😡
Luogu P4098 [HEOI2013] ALO
思路
首先这个次大值让我们很难受,所以可以考虑枚举次大值,然后区间求异或最大值。
首先肯定要让一个更大值来庇护它,但是却不能出现第二个,因为王不见王
那就很明了了,只需要向左右的第二个比它大的数向右左第一个比它大的数区间查询。
关于寻找第二大数:
第一种做法是链表,我不会链表(
所以就想出了一种大份做法,ST + 二分。
具体就是先找出左边第一大设为 \(l_i\),然后二分判断 \([mid, l_i - 1]\) 的最大值是否比 \(a_i\) 大即可。
警示后人
一开始想假了,处理成左右第一个大数直接询问了... 60 -> 100
代码
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid ls[id]
#define rid rs[id]
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 3e5 + 10, E = 5000 + 7, V = 200 + 7, mod = 998244353, B = 0, K = 0, MAXK = 30; // !!!!!!!!
// #define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const int base = 3000;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int L[N], R[N], sta[N], top, a[N];
class Trie
{
public :
int cnt[N << 5], ch[N << 5][2], tot;
void Insert(int &id, int u, int rest, int val)
{
if (!id) id = ++tot;
if (rest == MAXK) cnt[id] = cnt[u] + 1;
if (rest < 0) return;
int i = (val >> rest) & 1, j = i ^ 1;
ch[id][j] = ch[u][j];
ch[id][i] = ++tot;
cnt[ch[id][i]] = cnt[ch[u][i]] + 1;
Insert(ch[id][i], ch[u][i], rest - 1, val);
}
int Query(int lt, int rt, int rest, int val)
{
if (rest < 0) return 0;
int i = (val >> rest) & 1, j = i ^ 1;
int has = cnt[ch[rt][j]] - cnt[ch[lt][j]];
if (has > 0) return Query(ch[lt][j], ch[rt][j], rest - 1, val) + (1 << rest);
else return Query(ch[lt][i], ch[rt][i], rest - 1, val);
}
}T;
int root[N], pre[N], nxt[N], L1[N], R1[N];
int st[30][N], logn[N];
int Query(int l, int r)
{
int s = logn[r - l + 1];
return max(st[s][l], st[s][r - (1 << s) + 1]);
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int ans = 0;
int n; cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], T.Insert(root[i], root[i - 1], MAXK, a[i]);
for (int i = 2; i <= n; i++) logn[i] = logn[i / 2] + 1;
for (int i = 1; i <= n; i++) st[0][i] = a[i];
for (int i = 1; i <= 20; i++)
{
for (int j = 1; j + (1 << (i - 1)) <= n; j++)
{
st[i][j] = max(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);
}
}
for (int i = 1; i <= n; i++)
{
if (a[i] == Query(1, n)) continue;
int l1 = 0, l2 = 0, r1 = n + 1, r2 = n + 1;
int l = 1, r = i - 1, mid;
while (l <= r)
{
mid = (l + r) >> 1;
if (Query(mid, i - 1) > a[i]) l1 = mid, l = mid + 1;
else r = mid - 1;
}
l = 1, r = l1 - 1;
while (l <= r)
{
mid = (l + r) >> 1;
if (Query(mid, l1 - 1) > a[i]) l2 = mid, l = mid + 1;
else r = mid - 1;
}
l = i + 1, r = n;
while (l <= r)
{
mid = (l + r) >> 1;
if (Query(i + 1, mid) > a[i]) r1 = mid, r = mid - 1;
else l = mid + 1;
}
l = r1 + 1, r = n;
while (l <= r)
{
mid = (l + r) >> 1;
if (Query(r1 + 1, mid) > a[i]) r2 = mid, r = mid - 1;
else l = mid + 1;
}
ans = max(ans, T.Query(root[l2], root[r1 - 1], MAXK, a[i]));
ans = max(ans, T.Query(root[l1], root[r2 - 1], MAXK, a[i]));
}
cout << ans;
QED;
} // 😡
Luogu P10690 Fotile 模拟赛 L
思路
我仔细一看,这题好像不是很好做,但是又仔细一想,这种不好做的问题都有一种神秘的做法,然后猛然一看 \(N = 12000, M = 6000\)。
教练,我会分块!
但是分块了之后怎么查询,不仅散块间会有贡献,整块间也有贡献,散块整块间也有贡献,不可差分,只能合并,之间贡献不可合并。
难道就要放弃了吗?
我会预处理!
可以预处理出来 \(sum_{i,j}\) 表示 从 \(i\) 块到 \(j\) 块的全部贡献。
这样查询时块间的贡献就不用考虑了,然后只需要考虑散块和整块和散块的贡献,注意到就是对于每个散块元素都暴力区间查询整个区间的答案。
\(sum_{i,j}\) 的转移和查询十分相似,只需要把最后一个块看做散块轻松转移即可。
复杂度 \(O(n \sqrt n \log n)\)
警示后人
不能不选另一个数。。。
代码
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid ls[id]
#define rid rs[id]
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 1e5 + 10, E = 5000 + 7, V = 200 + 7, mod = 998244353, B = 1200, K = 0, MAXK = 30; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const int base = 3000;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
class Trie
{
public :
int cnt[N << 5], ch[N << 5][2], tot;
void Insert(int &id, int u, int rest, int val)
{
if (!id) id = ++tot;
if (rest == MAXK) cnt[id] = cnt[u] + 1;
if (rest < 0) return;
int i = (val >> rest) & 1, j = i ^ 1;
ch[id][j] = ch[u][j];
ch[id][i] = ++tot;
cnt[ch[id][i]] = cnt[ch[u][i]] + 1;
Insert(ch[id][i], ch[u][i], rest - 1, val);
}
int Query(int lt, int rt, int rest, int val)
{
if (rest < 0) return 0;
int i = (val >> rest) & 1, j = i ^ 1;
int has = cnt[ch[rt][j]] - cnt[ch[lt][j]];
if (has > 0) return Query(ch[lt][j], ch[rt][j], rest - 1, val) + (1 << rest);
else return Query(ch[lt][i], ch[rt][i], rest - 1, val);
}
}T;
int sum[B][B], st[B], en[B], n, a[N], belong[N], root[N];
int Query(int l, int r)
{
int L = belong[l], R = belong[r], ans = 0;
if (R - L <= 1)
{
for (int i = l; i <= r; i++) ans = max(ans, T.Query(root[l], root[r], MAXK, a[i]));
// for (int i = l; i <= r; i++) ans = max(ans, a[i]);
return ans;
}
ans = sum[L + 1][R - 1];
for (int i = l; i <= en[L]; i++) ans = max(ans, T.Query(root[l], root[r], MAXK, a[i]));
// for (int i = l; i <= en[L]; i++) ans = max(ans, a[i]);
for (int i = st[R]; i <= r; i++) ans = max(ans, T.Query(root[l], root[r], MAXK, a[i]));
// for (int i = st[R]; i <= r; i++) ans = max(ans, a[i]);
return ans;
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int m; cin >> n >> m;
int len = sqrt(n) + 1;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
a[i] ^= a[i - 1];
T.Insert(root[i], root[i - 1], MAXK, a[i]);
belong[i] = (i - 1) / len + 1;
en[belong[i]] = i;
}
int cnt_k = belong[n], last = 0;
for (int i = 1; i <= cnt_k; i++) st[i] = en[i - 1] + 1, sum[i][i] = Query(st[i], en[i]);
for (int i = 1; i <= cnt_k; i++) // sqrt(n)
{
for (int j = i + 1; j <= cnt_k; j++) // sqrt(n)
{
sum[i][j] = sum[i][j - 1];
for (int k = st[j]; k <= en[j]; k++) // sqrt(n)
{
sum[i][j] = max(sum[i][j], T.Query(root[st[i] - 1], root[en[j]], MAXK, a[k]));
// sum[i][j] = max(sum[i][j], a[k]);
}
}
}
for (int i = 1; i <= m; i++)
{
int l, r; cin >> l >> r;
l = (l + last) % n + 1ll, r = (r + last) % n + 1ll;
if (l > r) swap(l, r);
cout << (last = Query(l - 1, r)) << '\n';
}
QED;
} // 😡
Luogu P5795 [THUSC 2015] 异或运算
思路
第一眼就发现 \(n,q\) 都很小,显然就是对这一维进行暴力,然后再上个可持久化01Trie来维护另一维。
那现在就变成了很多棵树,求这一堆树中的第 k 大值,可以直接二分答案然后求排名即可。
具体就是每次如果该位是 1 则加上所有 0 的数字。
对于第一维的限制本质就是,如果该位是 1 则将左右子树翻转即可。
难道就这么轻松就创过去了?
交一发 TLE?
开始卡常 TLE?
仔细一算,发现大常数 \(2e8\) 根本过不去。
那怎么办?
其实一个二分十分唐,根本就没有必要,可以考虑一下,如果该位填一,则会有多少贡献,如果贡献太多,就说明不能填 1,这样每一位下去,就可以求出答案,并吃掉一个老哥。
警示后人
\(O(n \log n V^2)\) 过不去。
代码
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid ls[id]
#define rid rs[id]
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 3e5 + 10, E = 5000 + 7, V = 200 + 7, mod = 998244353, B = 1200, K = 0, MAXK = 30; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const int base = 3000;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int n, m, a[N], b[N], u, d, l, r, root[N], rl[N], rr[N];
class Trie
{
public :
int cnt[N << 5], ch[N << 5][2], tot;
inline void Insert(int &id, int u, int rest, int val)
{
if (!id) id = ++tot;
if (rest < 0) return;
if (rest == MAXK) cnt[id] = cnt[u] + 1;
int i = (val >> rest) & 1, j = i ^ 1;
ch[id][j] = ch[u][j];
ch[id][i] = ++tot;
cnt[ch[id][i]] = cnt[ch[u][i]] + 1;
Insert(ch[id][i], ch[u][i], rest - 1, val);
}
inline int Rnk(int k, int lt, int rt) // !!!
{
ll ans = 0;
// for (int i = u; i <= d; i++) ans += T.Rnk(root[l - 1], root[r], MAXK, x, a[i]);
for (int i = u; i <= d; i++) rl[i] = lt, rr[i] = rt;
for (int rest = MAXK; rest >= 0; rest--)
{
int Cnt = 0;
for (int i = u; i <= d; i++)
{
int o = (a[i] >> rest) & 1;
int has = cnt[ch[rr[i]][o]] - cnt[ch[rl[i]][o]];
Cnt += has;
}
int to = 0;
// cerr << Cnt << ' ';
if (k > Cnt) k -= Cnt, to = 1, ans += (1 << rest);
else to = 0;
for (int i = u; i <= d; i++)
{
int o = (a[i] >> rest) & 1;
rr[i] = ch[rr[i]][o ^ to], rl[i] = ch[rl[i]][o ^ to];
}
}
return ans;
}
}T;
signed main()
{
// cerr << sizeof(T) / 1024 / 1024 << ' ';
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= m; i++) cin >> b[i], T.Insert(root[i], root[i - 1], MAXK, b[i]);
int q; cin >> q;
for (int i = 1; i <= q; i++)
{
cin >> u >> d >> l >> r;
ll k; cin >> k;
k = (r - l + 1) * (d - u + 1) - k + 1;
cout << T.Rnk(k, root[l - 1], root[r]) << '\n';
}
QED;
} // 😡
[Codechef FEB13] Obserbing the tree树上询问
思路
直接将问题转为两个链到根的区间加,一个加单增,一个加单减即可。
注意到求和公式中的首项和公差都有可加性,直接上一个可持久化线段树就行。
注意可持久化线段树只能做永久标记,因为下放的标记可能会更改其他版本树中信息。
警示后人
太多了,调了 1.2 小时。
代码
点击查看代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <bitset>
#include <vector>
#include <set>
#include <queue>
#include <cmath>
#include <map>
#include <cassert>
#include <bitset>
#include <cstdio>
#include <climits>
#include <iomanip>
using namespace std;
#define lid ls[id]
#define rid rs[id]
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int M = 20000 + 7, N = 300000 + 7, mod = 10007, B = 300; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
int root[N], rt;
inline int calc(int a, int d, int n)
{
return n * a + n * (n - 1) / 2 * d;
}
class Sem_Tree
{
public :
int tag1[N << 6], tag2[N << 6], sum[N << 6], ls[N << 6], rs[N << 6], tot, be[N << 6], now[N << 6];
void F(int id, int u)
{
tag1[id] = tag1[u], tag2[id] = tag2[u], sum[id] = sum[u], ls[id] = ls[u], rs[id] = rs[u], now[id] = now[u];
}
void PushUp(int id) {sum[id] = sum[lid] + sum[rid] + now[id];}
void Update(int &id, int u, int cl, int cr, int l, int r, int a, int d, int c)
{
if (!id || be[id] != c) id = ++tot, be[id] = c, F(id, u);
if (l <= cl && cr <= r)
{
tag1[id] += a, tag2[id] += d;
// cerr << a << '\n';
// if (cl == cr && cl == 2) cerr << a << ' ';
sum[id] += calc(a, d, cr - cl + 1); // must need ?
now[id] += calc(a, d, cr - cl + 1);
return;
}
int mid = (cl + cr) >> 1;
int mid_a = a + (cr - mid) * d;
// cerr << a << ' ' << cr << ' ';
if (l <= mid) Update(lid, ls[u], cl, mid, l, r, mid_a, d, c);
if (r > mid) Update(rid, rs[u], mid + 1, cr, l, r, a, d, c);
PushUp(id);
}
int Query(int id, int cl, int cr, int l, int r, int Tag1, int Tag2)
{
if (l <= cl && cr <= r)
{
int s = calc(Tag1, Tag2, cr - cl + 1);
return s + sum[id];
}
Tag1 += tag1[id], Tag2 += tag2[id];
int ans = 0, mid = (cl + cr) >> 1, mid_a = Tag1 + (cr - (mid + 1) + 1) * Tag2;
if (l <= mid) ans = Query(lid, cl, mid, l, r, mid_a, Tag2);
if (r > mid) ans += Query(rid, mid + 1, cr, l, r, Tag1, Tag2);
return ans;
}
}T;
int dep[N], siz[N], son[N], f[N], top[N], dfn[N], n;
void Update(int x, int y, int a, int d, int c, int rt)
{
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]]) swap(x, y);
T.Update(root[c], rt, 1, n, dfn[top[x]], dfn[x], a - (n - dfn[x]) * d, d, c);
a += (dfn[x] - dfn[top[x]] + 1) * d;
x = f[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
// cerr << a << ' ' << x << ' ' << y << '\n';
// cout << a - (n - dfn[x]) * d << ' ';
T.Update(root[c], rt, 1, n, dfn[x], dfn[y], a - (n - dfn[y]) * d, d, c);
}
int Lca(int x, int y)
{
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]]) swap(x, y);
x = f[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
return x;
}
int Query(int x, int y, int c)
{
int ans = 0;
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]]) swap(x, y);
ans += T.Query(c, 1, n, dfn[top[x]], dfn[x], 0, 0);
x = f[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
ans += T.Query(c, 1, n, dfn[x], dfn[y], 0, 0);
return ans;
}
vector <int> G[N];
void dfs(int x, int fa)
{
dep[x] = dep[fa] + 1, siz[x] = 1, f[x] = fa;
for (auto &to : G[x])
{
if (to == fa) continue;
dfs(to, x);
siz[x] += siz[to];
if (siz[to] > siz[son[x]]) son[x] = to;
}
}
int timer = 0;
void dfs1(int x, int fa)
{
top[x] = fa, dfn[x] = ++timer;
if (son[x]) dfs1(son[x], fa);
for (auto &to : G[x])
{
if (to == f[x] || to == son[x]) continue;
dfs1(to, to);
}
}
void add(int x, int y) {G[x].emp(y);}
signed main()
{
// cerr << sizeof(T) / 1024 / 1024 << ' ';
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int m; cin >> n >> m;
// T.Update(root[0], 0, 1, n, 1, 4, 1, 1, 1);
// T.Update(root[0], 0, 1, n, 2, 4, 1 - (2 - 1) * 1, 1, 1);
// cerr << T.Query(root[0], 1, n, 1, 3, 0, 0) << '\n';
for (int i = 1; i < n; i++)
{
int x, y; cin >> x >> y;
add(x, y), add(y, x);
}
dfs(1, 0), dfs1(1, 0);
int last = 0, tot = 0;
for (int i = 1; i <= m; i++)
{
char op; cin >> op;
if (op == 'l')
{
int x; cin >> x; x ^= last;
rt = root[x];
}
if (op == 'c')
{
++tot;
int x, y, a, d; cin >> x >> y >> a >> d;
x ^= last, y ^= last;
int lca = Lca(x, y);
Update(x, lca, a, d, tot, rt);
a += (dep[x] - dep[lca]) * d;
Update(lca, lca, -a, 0, tot, rt);
a += (dep[y] - dep[lca]) * d;
Update(y, lca, a, -d, tot, rt);
rt = root[tot];
// cerr << T.Query(rt, 1, n, 2, 2, 0, 0) << '\n';
}
if (op == 'q')
{
int x, y; cin >> x >> y;
x ^= last, y ^= last;
cout << (last = Query(x, y, rt)) << '\n';
}
}
return 0;
}
容斥原理及二项式反演
本质就是套
即钦定和恰好。
Luogu P6478 [NOI Online #2 提高组] 游戏
思路
一定要看懂题面,说的是两个人每次选一个点,如果是对方点的子节点就输,然后不能选重复的。
可以枚举是那些点对参加输赢,这样其他不管就是钦定了。
然后跑一个树上背包即可。
设 \(dp_{i,j}\) 表示 \(i\) 子树内选了 \(j\) 对节点的方案数,最后乘上其他节点的总方案即可。
警示后人
char 不要直接运算...
代码
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid id << 1
#define rid id << 1 | 1
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 5000 + 10, E = 5000 + 7, V = 200 + 7, mod = 998244353, B = 0, K = 0; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const int base = 3000;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int dp[N][N], pre[N][N], siz[N], sz[N], n;
char A[N];
vector <int> G[N];
void dfs(int x, int fa)
{
sz[x] = A[x] - '0', siz[x] = 1;
dp[x][0] = 1;
for (auto &to : G[x])
{
if (to == fa) continue;
dfs(to, x);
for (int i = 0; i <= siz[x]; i++) pre[x][i] = dp[x][i], dp[x][i] = 0;
for (int i = 0; i <= siz[x]; i++)
{
for (int j = 0; j <= siz[to]; j++)
{
dp[x][i + j] = (dp[x][i + j] + pre[x][i] * dp[to][j]) % mod;
}
}
sz[x] += sz[to], siz[x] += siz[to];
}
for (int i = min(sz[x], siz[x] - sz[x]); i >= 1; i--) (dp[x][i] += dp[x][i - 1] * (((A[x] - '0') ? (siz[x] - sz[x]) : sz[x]) - (i - 1))) %= mod;
}
void add(int x, int y) {G[x].emp(y);}
int fac[N], inv[N];
int C(int n, int m) {return fac[n] * inv[m] % mod * inv[n - m] % mod;}
int qpow(int x, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = res * x % mod;
x = x * x % mod;
b >>= 1;
}
return res;
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
fac[0] = inv[0] = 1;
for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % mod, inv[i] = qpow(fac[i], mod - 2);
for (int i = 1; i <= n; i++) cin >> A[i];
for (int i = 1; i < n; i++)
{
int x, y; cin >> x >> y;
add(x, y), add(y, x);
}
dfs(1, 0);
// for (int i = 0; i <= n / 2; i++) cerr << dp[1][i] * fac[n / 2 - i] % mod << ' ';
for (int i = 0; i <= n / 2; i++)
{
int res = 0;
int flag = 1;
for (int j = i; j <= n / 2; j++)
{
res = (res + flag * C(j, i) * dp[1][j] % mod * fac[n / 2 - j] % mod) % mod;
flag = -flag;
}
cout << (res + mod) % mod << '\n';
}
QED;
} // 😡
Luogu P5505 [JSOI2011] 分特产
思路
算是板子题吧。
第一眼以为是斯特林数,不过有点问题,就是其不全是本质不同点。
先考虑钦定有 \(i\) 个人没有拿到东西。
然后......好像不能直接算。
我会 dp!
设 \(dp_{i,j}\) 表示钦定 \(n - i\) 个人不选,考虑到第 \(j\) 道美食的方案数。
转移考虑插板将 \(a_j - 1\) 个板子插入 \(i\) 个人中,可空,共有 \(C_{i+a_j-1}^{a_j-1}\) 种方案,乘起来就好。
警示后人
别开小了。
代码
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid id << 1
#define rid id << 1 | 1
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 3000, E = 5000 + 7, V = 200 + 7, mod = 1e9 + 7, B = 0, K = 0; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const ull base = 1331;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int qpow(int x, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = res * x % mod;
x = x * x % mod;
b >>= 1;
}
return res;
}
int fac[N], inv[N], f[N][N], cnt[N];
int C(int n, int m) {return fac[n] * inv[m] % mod * inv[n - m] % mod;}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, m; cin >> n >> m;
fac[0] = inv[0] = 1;
for (int i = 1; i < N; i++) fac[i] = fac[i - 1] * i % mod, inv[i] = qpow(fac[i], mod - 2);
for (int i = 1; i <= m; i++) cin >> cnt[i];
for (int i = 1; i <= n; i++)
{
f[n - i][0] = 1;
for (int j = 1; j <= m; j++)
{
f[n - i][j] = f[n - i][j - 1] * C(cnt[j] + i - 1, i - 1) % mod;
}
}
f[n][m] = 0;
int flag = 1, ans = 0;
for (int i = 0; i <= n; i++)
{
ans = (ans + C(n, i) * f[i][m] * flag % mod + mod) % mod;
flag = -flag;
}
cout << ans % mod;
QED;
}
Luogu P4921 [MtOI2018] 情侣?给我烧了!
思路
考虑钦定 \(i\) 对情侣和睦,则方案数为:
排列数指的是将 \(i\) 对情侣选出,组合数指的是选择 \(i\) 个座位来坐,剩下的阶乘是剩下的人的所有情况,\(2^i\) 指的是情侣间的方案。
然后答案就是
这是暴力式子,根本过不去。
考虑怎么优化,先把式子全部拆开。
\( \begin{aligned} g(n) &=\sum_{i=k}^{n}{(-1)^{i-k}\binom{i}{k}f(i)} \\ &=\sum_{i=k}^{n}{(-1)^{i-k}C_{i}^{k}A_{n}^{i} C_{n}^{i} (2n-2i)!2^i} \\ &=\sum_{i=k}^{n}{(-1)^{i-k}\frac{i!n!n!}{k!(i-k)!(n-i)!i!(n-i)!}(2n-2i)!2^i} \\ &=k!(n!)^2\sum_{i=k}^{n}{(-1)^{i-k}\frac{(2n-2i)!2^i}{(i-k)!((n-i)!)^2}} \\ &=\frac{2^k!(n!)^2}{i!}\sum_{i=0}^{n-k}{(-1)^{i}\frac{(2n-2k-2i)!2^i}{i!((n-k-i)!)^2}} \\ \end{aligned} \)
到这里如果看不出来可以把上面的 \(n-k\) 换元,发现可以获得一个只与 \(n-k\) 有关的式子,直接预处理就好。
警示后人
\(O(Tn^2)\) 可过。
代码
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid id << 1
#define rid id << 1 | 1
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 3000, E = 5000 + 7, V = 200 + 7, mod = 998244353, B = 0, K = 0; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const ull base = 1331;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int qpow(int x, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = res * x % mod;
x = x * x % mod;
b >>= 1;
}
return res;
}
int fac[N], inv[N], f[N], n;
inline int C(int n, int m) {return fac[n] * inv[m] % mod * inv[n - m] % mod;}
inline int A(int n, int m) {return fac[n] * inv[n - m] % mod;}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
fac[0] = inv[0] = 1;
for (int i = 1; i < N; i++) fac[i] = fac[i - 1] * i % mod, inv[i] = qpow(fac[i], mod - 2);
int T; cin >> T;
while (T--)
{
cin >> n;
for (int i = 0; i <= n; i++) f[i] = A(n, i) * C(n, i) % mod * fac[2 * (n - i)] % mod * qpow(2, i) % mod;
for (int i = 0; i <= n; i++)
{
int now = 0;
bool flag = 1;
for (int j = i; j <= n; j++)
{
if (flag) now += C(j, i) * f[j] % mod;
else now -= C(j, i) * f[j] % mod;
flag = !flag;
}
cout << (now % mod + mod) % mod << '\n';
}
}
QED;
}
Luogu P3270 [JLOI2016] 成绩比较
思路
自己想出来的,感觉挺好玩的。
首先先考虑钦定被碾压的人,因为被碾压其实并不好刻画恰好的情况。
设 \(f(i)\) 为钦定 \(i\) 人被碾压方案数。
考虑枚举B神每一科的分数。
看着有点恐怖,但是现在可以考虑一下怎么把这一坨式子合并一下,注意到一件事情,就是每一个 \(i,j,k\) 都会遍历到并且进行乘法,立马就想到了,多个求和式再相乘会有同样的效果,所以就很好化简了。
然后......然后?
不会了。
吗?
发现这个式子只有一个突破口了。
我会二项式展开!
\( \begin{aligned} f(K)&=C_{n-1}^{K}\prod_{k=1}^{m}{C_{n-K-1}^{R_k-1}\sum_{i=1}^{U_k}{i^{n-R_k}\sum_{t=0}^{R_k-1}{C_{R_k-1}^{t}{U_k^t(-i)^{R_k-1-t}}}}} \\ &=C_{n-1}^{K}\prod_{k=1}^{m}{C_{n-K-1}^{R_k-1}\sum_{i=1}^{U_k}{i^{n-t-1}\sum_{t=0}^{R_k-1}{C_{R_k-1}^{t}{U_k^t(-1)^{R_k-1-t}}}}} \\ &=C_{n-1}^{K}\prod_{k=1}^{m}{C_{n-K-1}^{R_k-1}\sum_{t=0}^{R_k-1}\sum_{i=1}^{U_k}{i^{n-t-1}{C_{R_k-1}^{t}{U_k^t(-1)^{R_k-1-t}}}}} \\ &=C_{n-1}^{K}\prod_{k=1}^{m}{C_{n-K-1}^{R_k-1}\sum_{t=0}^{R_k-1}{C_{R_k-1}^{t}{U_k^t(-1)^{R_k-1-t}}}\sum_{i=1}^{U_k}{i^{n-t-1}}} \\ \end{aligned} \)
这时有趣的事就发生了,最后的式子竟然是一个 \(\sum{i^k}\)。
根据在很早之前,wang54321 说这是一个多项式,所以我话不多说就上了个拉插(好像用什么伯努利数、扰动法都行)。
于是设 \(s_{k,t}=\sum_{i=1}^{U_k}{i^t}\)
所以最终式子就是
使用连续值域拉插可以做到 \(O(n^3)\)(\(n,m\) 同阶)
不过直接写 \(O(n^4)\) 也能过。
警示后人
\(k\) 次多项式需要 \(k+1\) 个数差出来。
代码
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid id << 1
#define rid id << 1 | 1
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 3100, E = 5000 + 7, V = 200 + 7, mod = 1e9 + 7, B = 0, K = 0; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const int base = 3000;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int qpow(int x, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = res * x % mod;
x = x * x % mod;
b >>= 1;
}
return res;
}
int f[N], fac[N], inv[N], g[N], U[N], R[N], x[N], y[N], h[N][N], Inv[N << 1];
int C(int n, int m)
{
if (m > n) return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int Lage(int h, int d)
{
for (int i = 1; i <= d + 2; i++) x[i] = i, y[i] = (y[i - 1] + qpow(i, d)) % mod;
int ans = 0;
for (int i = 1; i <= d + 2; i++)
{
int res = y[i];
for (int j = 1; j <= d + 2; j++)
{
if (i == j) continue;
res = res * (h - x[j]) % mod * Inv[x[i] - x[j] + base] % mod;
}
ans = (ans + res) % mod;
}
return ans;
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
for (int i = -3000; i <= 3000; i++) Inv[i + base] = qpow(i, mod - 2);
int n, m, K; cin >> n >> m >> K;
for (int i = 1; i <= m; i++) cin >> U[i];
for (int i = 1; i <= m; i++) cin >> R[i];
for (int i = 1; i <= m; i++) for (int j = 0; j <= n; j++) h[i][j] = Lage(U[i], j);
fac[0] = inv[0] = 1;
for (int i = 1; i < N; i++) fac[i] = fac[i - 1] * i % mod, inv[i] = qpow(fac[i], mod - 2);
for (int i = 0; i < n; i++)
{
f[i] = C(n - 1, i);
for (int j = 1; j <= m; j++)
{
g[j] = 0;
for (int t = 0; t < R[j]; t++)
{
int tmp = h[j][n - t - 1];
g[j] = (g[j] + C(n - i - 1, R[j] - 1) * qpow(U[j], t) % mod * qpow(-1, R[j] - 1 - t) % mod * C(R[j] - 1, t) % mod * tmp % mod + mod) % mod;
}
f[i] = f[i] * g[j] % mod;
}
}
int ans = 0, flag = 1;
for (int i = K; i <= n; i++)
{
ans = (ans + flag * C(i, K) * f[i] % mod + mod) % mod;
// cerr << f[i] << ' ';
flag = -flag;
}
cout << ans;
QED;
}
CF997C Sky Full of Stars
思路
二维二反板子,只不过最后要推一下式子。
设 \(f_{i,j}\) 表示钦定 \(i\) 行 \(j\) 列颜色相同的方案数。
则答案 \(g_{i,j}=\sum_{k=i}^{n}{(-1)^{k-i}\binom{k}{i}}\sum_{l=j}^{n}{(-1)^{l-j}\binom{l}{j}f_{k,l}}\)
设 \(s=n^2-ni-nj+ij\)
因为列和行间有交叉,所以只能赋值成一种颜色。
则 \(f_{i,j}=3^{n^2-s+1}\binom{n}{i}\binom{n}{j}\)
写完以后,样例都过不去,为啥?
因为当 \(i=0\) 或 \(j=0\) 时,每行/每列都可以单独染色,计算公式会有小小的变动。
不过这时复杂度是 \(O(n^2)\) 的,根本跑不过去。
考虑怎么优化,还是把式子列出来。
由于要求的答案为 \(g_{0,0}\) 所以可以先代入进去。
\( \begin{aligned} ans&=\sum_{k=0}^{n}\sum_{l=0}^{n}{(-1)^{k+l}3^{n^2-s+1}\binom{n}{k}\binom{n}{l}} \\ &=\sum_{i=0}^{n}{(-1)^i}\sum_{j=0}^{n}{(-1)^j 3^{(n-i)(n-j)+1}\binom{n}{i}\binom{n}{j}} \\ &=3\sum_{i=0}^{n}{(-1)^i \binom{n}{i}}\sum_{j=0}^{n}{(-1)^j (3^{n-i})^{n-j}\binom{n}{j}} \\ &=3\sum_{i=0}^{n}{(-1)^i \binom{n}{i}}(3^{n-i}-1)^n \\ \end{aligned} \)
然后特殊处理 \(i=0\) 或 \(j=0\) 的情况即可。
复杂度 \(O(n \log n)\)
警示后人
OJ 叫 CF 不要开 O2。
代码
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid id << 1
#define rid id << 1 | 1
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 1e6 + 10, E = 5000 + 7, V = 200 + 7, mod = 998244353, B = 0, K = 0; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const int base = 3000;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
inline int qpow(int x, int b)
{
if (x == -1) return (b & 1) ? -1 : 1;
int res = 1;
while (b)
{
if (b & 1) res = res * x % mod;
x = x * x % mod;
b >>= 1;
}
return res;
}
int fac[N], inv[N];
inline int C(int n, int m)
{
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n; cin >> n;
fac[0] = inv[0] = 1;
for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % mod, inv[i] = qpow(fac[i], mod - 2);
int ans = 0;
for (int i = 0; i <= n; i++)
{
ans += qpow(-1, i) * C(n, i) % mod * qpow(qpow(3, n - i) - 1, n) % mod;
}
ans = ans * 3 % mod;
ans = (ans + mod - qpow(3, n * n + 1)) % mod;
ans = (ans + qpow(3, n * n)) % mod;
for (int i = 1; i <= n; i++) ans -= 2 * qpow(-1, i) * C(n, i) % mod * qpow(3, n * n - n * i + 1) % mod;
for (int i = 1; i <= n; i++) ans += 2 * qpow(-1, i) * C(n, i) % mod * qpow(3, n * n - n * i) % mod * qpow(3, i) % mod;
// cerr << ans << ' ';
cout << (qpow(3, n * n) - ans % mod + mod) % mod << endl;
QED;
}
Luogu P5643 [PKUWC2018] 随机游走
如果不是闲得慌学习优化的话,好像就算是切的第一个黑了。
谁知道高消写出来还真能过...
首先 \(min-max\) 是比较显然的。
\(E(min(S))\) 表示 \(S\) 中最早出现的点的期望时间。
所以就正常的设状态 \(f_{i,state}\) 表示 \(i\) 到 \(state\) 集合中一点的期望时间。
转移考虑枚举连向的点。
当 \(i \in S\) 时
否则
就是一个典型的图上随机游走,直接高消即可。
然后使用FMT等高维前缀和技巧优化一下做到 \(O(1)\) 查询即可。
然后最后上一步容斥 \(E(max(S))=\sum_{T \subseteq S}{(-1)^{|T|+1}E(min(T))}\)
复杂度 \(O(2^nn^3)\)
其实就能过了,到这其实好像只有紫的难度吧。
但是考虑怎么优化,因为树的性质我们还没用呢,后效性只会由父亲出现。
所以用一个十分奇怪的方法把父亲的后效性干掉设 \(f_i=A_i+B_if_{fa}\)(省略 \(state\) )。
那么把转移方程更改一下就是
化简一下设 \(suma_i=\sum_{j \in son_i}{A_j},sumb_i=\sum_{j \in son_i}{B_j}\)
则有
并得到
两次dfs处理即可。
复杂度 \(O(n2^n)\)
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid id << 1
#define rid id << 1 | 1
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 20, M = 4e5 + 10, V = 200 + 7, mod = 998244353, MAXK = 0; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const ull base = 1331;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int A[N], B[N], f[N], p[M], deg[N], n, q, x, p1[M];
vector <int> G[N];
int qpow(int x, int b)
{
int res = 1;
while (b)
{
if (b & 1) res = res * x % mod;
x = x * x % mod;
b >>= 1;
}
return res;
}
bool Check(int x, int S)
{
return S & (1 << (x - 1));
}
void dfs_AB(int x, int fa, int S)
{
int suma = 0, sumb = 0;
for (auto &to : G[x])
{
if (to == fa) continue;
dfs_AB(to, x, S);
(suma += A[to]) %= mod, (sumb += B[to]) %= mod;
}
if (Check(x, S)) A[x] = B[x] = 0;
else
{
A[x] = (deg[x] + suma) * qpow(deg[x] - sumb, mod - 2) % mod;
B[x] = qpow(deg[x] - sumb, mod - 2) % mod;
}
}
void dfs_f(int x, int fa, int S)
{
if (Check(x, S)) f[x] = 0;
else f[x] = (A[x] + B[x] * f[fa]) % mod;
for (auto &to : G[x])
{
if (to == fa) continue;
dfs_f(to, x, S);
}
}
void add(int x, int y) {G[x].emp(y);}
void FMT()
{
for (int j = 0; j < n; j++)
{
for (int i = 0; i < (1 << n); i++)
{
if (i & (1 << j))
{
(p[i] += p[i ^ (1 << j)]) %= mod;
}
}
}
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> q >> x;
for (int i = 1; i < n; i++)
{
int x, y; cin >> x >> y;
add(x, y), add(y, x);
deg[x]++, deg[y]++;
}
for (int i = 0; i < (1 << n); i++)
{
memset(A, 0, sizeof(A)), memset(B, 0, sizeof(B)), memset(f, 0, sizeof(f));
dfs_AB(x, 0, i), dfs_f(x, 0, i);
p[i] = f[x];
// cerr << f[x] << '\n';
int k = __builtin_popcount(i);
if (k & 1) p[i] = p[i];
else p[i] = -p[i];
// p1[i] = p[i];
}
FMT();
for (int i = 1; i <= q; i++)
{
int k, S = 0; cin >> k;
for (int j = 1; j <= k; j++)
{
int x; cin >> x;
S |= (1 << (x - 1));
}
cout << (p[S] + mod) % mod << '\n';
}
QED;
}
圆方树
感觉跟缩点差不多,感觉就是因为缩点双发现割点缩不进去然后就发现了这个树形结构。
Luogu [APIO2018] 铁人两项
其实不太是板子,感觉感性理解立大功。
考虑一下两个点之间的 \(c\) 可以怎么选,其实就是圆方树上两点间的权值和。
浅证一下:
由于方点只能连向圆点,而这些圆点都是割点,所以如果不在树上的路径上则一定无法不经过它到达目标。
而方点内的点都可能做到不经过它到达目标,所以整个经过的方点内的点都可以是 \(c\)(感性理解一下)。
因为如果无法到达,就说明一定在到达这个点的路上出现了割点,而进入方点后就没有割点了。
而树上任意两点间权值和可以转为每个点求经过次数,直接树形dp即可。
tips:圆点的全职可以设为-1,这样就可以把许多不合法和重复情况斥掉了。
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid id << 1
#define rid id << 1 | 1
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 2e5 + 10, V = 200 + 7, mod = 1e9 + 7, MAXK = 0; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const ull base = 1331;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int low[N], dfn[N], cnt, timer, sta[N], top, siz_t;
vector <int> G[N], E[N << 1];
int dp[N << 1], val[N << 1], siz[N << 1], n;
void add(int x, int y) {G[x].emp(y);}
void add_e(int x, int y) {E[x].emp(y);}
void Tarjan(int x)
{
sta[++top] = x;
low[x] = dfn[x] = ++timer;
++siz_t;
for (auto &to : G[x])
{
if (!dfn[to])
{
Tarjan(to);
low[x] = min(low[x], low[to]);
if (low[to] == dfn[x])
{
++cnt;
for (int now = 0; now != to; top--)
{
now = sta[top];
add_e(cnt, now), add_e(now, cnt);
++val[cnt];
}
add_e(cnt, x), add_e(x, cnt);
++val[cnt];
}
}
else low[x] = min(low[x], dfn[to]);
}
}
void dfs(int x, int fa)
{
siz[x] = (x <= n);
for (auto &to : E[x])
{
if (to == fa) continue;
dfs(to, x);
dp[x] += 2ll * siz[x] * siz[to] * val[x];
siz[x] += siz[to];
}
dp[x] += 2ll * siz[x] * (siz_t - siz[x]) * val[x];
}
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int m; cin >> n >> m;
cnt = n;
for (int i = 1; i <= n; i++) val[i] = -1;
for (int i = 1; i <= m; i++)
{
int x, y; cin >> x >> y;
add(x, y); add(y, x);
}
for (int i = 1; i <= n; i++)
{
if (!dfn[i])
{
siz_t = 0;
Tarjan(i);
top--;
cerr << cnt << ' ';
dfs(i, 0);
}
}
int ans = 0;
for (int i = 1; i <= cnt; i++) ans += dp[i];
cout << ans;
QED;
}
Luogu P4606 [SDOI2018] 战略游戏
感觉挺水的,只是有点难写。
首先暴力是显然的,直接暴力判联通性。
然后考虑怎么优化,根据子任务2可以发现一些事情,就是两个点间的合法点如何判断。
如果一个割点删除之后两个图中都存在要询问的点,则这个点就是要求的点。
所以现在就开心的还是获得了 30 分。
考虑怎么优化,发现如果去枚举割点肯定无法优化,所肯定是考虑怎么用 \(S\) 求答案。
用惊人的注意力一观察,发现要求的其实就是 \(S\) 中任意两点的路径割点数量(取并集)。
然后就可以 \(O(TS^2\log^2n)\) 做了。
注意一下:\(lca\) + 多个点?
我会虚树!
不过其实不用写出虚树,只是一个思想罢了。
考虑这些任意路径取并其实等价于顺序枚举前缀的 \(lca\) 到这个点的路径并。
证明也十分简单。
由于跳 \(fa\) 是本质固定的,所以无论顺序如何,最后该点跳父亲是固定知道的。
如果一个点本应被访问实际没有被访问,那么一定会在这一次或以后的某一次被访问。
严格证明就不写了,感兴趣的可以来找我(其实画个图可能更好理解吧)。
这样子每次把 \(S\) 中的点斥出去后用线段树维护查询的点,然后随便维护即可。
复杂度 \(O(TS\log^2 n)\)
点击查看代码
#include <iostream>
#include <vector>
#include <climits>
#include <iomanip>
#include <cmath>
#include <algorithm>
#include <cassert>
#include <queue>
#include <cstring>
using namespace std;
#define QED return 0
#define lid id << 1
#define rid id << 1 | 1
#define emp emplace_back
#define pb push_back
#define fi first
#define se second
#define IL inline
#define reg register
#define endl '\n'
#define IL inline
#define LF 1
#define RF 0
const int N = 4e5 + 10, M = 4e6 + 10, V = 200 + 7, mod = 998244353, MAXK = 0; // !!!!!!!!
#define int long long
using pii = pair <int, int>;
using ll = long long;
using ld = long double;
using ull = unsigned long long;
using I = __int128;
using uIt = __uint128_t;
const int inf = INT_MAX;
const ull base = 1331;
const ld P = 0.5, eps = 1e-8, inf_double = 1e300;
int n, m, q, val[N];
class Sem_Tree // maybe ArrayTree is better
{
public :
int sum[N << 2], tag[N << 2], q[N << 2];
void Build(int id, int l, int r)
{
tag[id] = -1, q[id] = 0;
if (l == r) return sum[id] = val[l], void();
int mid = (l + r) >> 1;
Build(lid, l, mid); Build(rid, mid + 1, r);
sum[id] = sum[lid] + sum[rid];
}
void PushUp(int id)
{
sum[id] = sum[lid] + sum[rid];
q[id] = q[lid] + q[rid];
}
void PushDown(int id)
{
if (tag[id] != -1)
{
tag[lid] = tag[id], tag[rid] = tag[id];
q[lid] = sum[lid] * tag[id], q[rid] = sum[rid] * tag[id];
tag[id] = -1;
}
}
void Update_Q(int id, int cl, int cr, int l, int r, int k)
{
if (l <= cl && cr <= r) return q[id] = sum[id] * k, tag[id] = k, void();
int mid = (cl + cr) >> 1;
PushDown(id);
if (l <= mid) Update_Q(lid, cl, mid, l, r, k);
if (r > mid) Update_Q(rid, mid + 1, cr, l, r, k);
PushUp(id);
}
void Update(int id, int cl, int cr, int pos, int k)
{
if (cl == cr) return sum[id] = k, void();
int mid = (cl + cr) >> 1;
PushDown(id);
if (pos <= mid) Update(lid, cl, mid, pos, k);
else Update(rid, mid + 1, cr, pos, k);
PushUp(id);
}
}T;
namespace Graph
{
int low[N], dfn[N], cnt, timer, sta[N], tp;
int top[N], f[N], son[N], siz[N], dep[N];
bool cut[N];
vector <int> G[N], E[N];
void clear()
{
for (int i = 1; i <= n; i++) G[i].clear();
for (int i = 1; i <= cnt; i++) E[i].clear();
memset(low, 0, sizeof(low));
memset(dfn, 0, sizeof(dfn));
memset(top, 0, sizeof(top));
memset(cut, 0, sizeof(cut));
memset(f, 0, sizeof(f));
memset(siz, 0, sizeof(siz));
memset(sta, 0, sizeof(sta));
memset(dep, 0, sizeof(dep));
memset(son, 0, sizeof(son));
memset(val, 0, sizeof(val));
cnt = timer = tp = 0;
}
void add(int x, int y) {G[x].emp(y);}
void add_e(int x, int y) {E[x].emp(y);}
void Tarjan(int x)
{
sta[++tp] = x;
low[x] = dfn[x] = ++timer;
for (auto &to : G[x])
{
if (!dfn[to])
{
Tarjan(to);
low[x] = min(low[x], low[to]);
if (low[to] >= dfn[x])
{
++cnt;
for (int y = 0; y != to; --tp)
{
y = sta[tp];
add_e(y, cnt), add_e(cnt, y); // can be not use
}
add_e(x, cnt), add_e(cnt, x);
cut[x] = 1;
}
}
else low[x] = min(low[x], dfn[to]);
}
}
void dfs(int x, int fa)
{
dep[x] = dep[fa] + 1, siz[x] = 1, f[x] = fa;
for (auto &to : E[x])
{
if (to == fa) continue;
dfs(to, x);
siz[x] += siz[to];
if (siz[to] > siz[son[x]]) son[x] = to;
}
}
void dfs1(int x, int fa)
{
top[x] = fa, dfn[x] = ++timer;
val[timer] = cut[x];
if (son[x]) dfs1(son[x], fa);
for (auto &to : E[x])
{
if (to == f[x] || to == son[x]) continue;
dfs1(to, to);
}
}
void Update_Q(int x, int y)
{
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]]) swap(x, y);
T.Update_Q(1, 1, cnt, dfn[top[x]], dfn[x], 1);
x = f[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
T.Update_Q(1, 1, cnt, dfn[x], dfn[y], 1);
}
int Lca(int x, int y)
{
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]]) swap(x, y);
x = f[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
return x;
}
};using namespace Graph;
int Q[N];
signed main()
{
// freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
int S; cin >> S;
while (S--)
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int x, y; cin >> x >> y;
add(x, y), add(y, x);
}
cnt = n; // !!! must
Tarjan(1);
memset(dfn, 0, sizeof(dfn));
timer = 0;
dfs(1, 0), dfs1(1, 0);
T.Build(1, 1, cnt);
cin >> q;
for (int i = 1; i <= q; i++)
{
int s, lca = -1; cin >> s;
for (int j = 1; j <= s; j++) cin >> Q[j];
for (int j = 1; j <= s; j++)
{
if (cut[Q[j]]) T.Update(1, 1, cnt, dfn[Q[j]], 0);
}
lca = Q[1];
for (int j = 1; j <= s; j++)
{
Update_Q(Q[j], lca);
lca = Lca(Q[j], lca);
}
cout << T.q[1] << '\n';
T.Update_Q(1, 1, cnt, 1, cnt, 0);
for (int j = 1; j <= s; j++)
{
if (cut[Q[j]]) T.Update(1, 1, cnt, dfn[Q[j]], 1);
}
}
clear();
}
QED;
}

浙公网安备 33010602011771号