[做题记录]ABC F/G/Ex 做题计划

\(\mathbf{Luogu \ Atcoder}\) 页面题目难度设置紫,从上向下排刷。

ABC133F

离线后树上差分。

设四元组 \(\langle u, c, v, w\rangle\) 表示从 \(u\) 点开始到根,颜色是 \(c\) 的权值全部替换成 \(v\),答案需要加 / 减。

\(f_u\) 表示从 \(u\) 到根颜色与 \(u\) 相同的点的个数,\(g_u\) 表示从 \(u\) 到根颜色与 \(u\) 相同的点的权值和,\(cf_u\) 表示从 \(u\) 到根颜色与 \(u\) 相同的最深的点,\(h_u\) 表示从 \(u\) 到根的路径权值和。dfs 求解即可。

struct Edge { int v, w, c; }; vector<Edge> E[N];
struct Query { int c, w, u, v, id; }q[N];
struct TQ { int c, v, w, id; }; vector<TQ> Q[N];
int n, m, col[N], val[N], f[N], g[N], h[N];
int dep[N], fa[N][21], bin[N], cf[N], ans[N];
void dfs(int u, int F) {
	fa[u][0] = F, dep[u] = dep[F] + 1;
	for (auto [v, w, c] : E[u]) if (v ^ F) col[v] = c, val[v] = w, dfs(v, u);
}
void dfs2(int u, int F) {
	cf[u] = bin[col[u]]; bin[col[u]] = u;
	for (auto [v, w, c] : E[u]) if (v ^ F) dfs2(v, u); bin[col[u]] = cf[u];
}
void dfs3(int u, int F) {
	f[u] = f[cf[u]] + 1; g[u] = g[cf[u]] + val[u]; h[u] = h[fa[u][0]] + val[u];
	for (auto [v, w, c] : E[u]) if (v ^ F) dfs3(v, u);
}
void dfs4(int u, int F) {
   bin[col[u]] = u;
	for (auto [c, v, w, t] : Q[u]) ans[t] += w * h[u];
	for (auto [c, v, w, t] : Q[u]) ans[t] -= w * g[bin[c]];
	for (auto [c, v, w, t] : Q[u]) ans[t] += w * v * f[bin[c]];
	for (auto [v, w, c] : E[u]) if (v ^ F) dfs4(v, u); bin[col[u]] = cf[u];
}
int lca(int u, int v) {
	if (dep[u] < dep[v]) swap(u, v);
	dep(i, 20, 0) if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
	dep(i, 20, 0) if (fa[u][i] ^ fa[v][i]) u = fa[u][i], v = fa[v][i];
	return u == v ? u : fa[u][0];
}
signed main() {
	scanf("%lld%lld", &n, &m); rep(i, 1, n - 1) {
		int u, v, w, c; scanf("%lld%lld%lld%lld", &u, &v, &c, &w);
		E[u].push_back({v, w, c}); E[v].push_back({u, w, c});
	} dfs(1, 0); dfs2(1, 0); dfs3(1, 0);
	rep(j, 1, 20) rep(i, 1, n) fa[i][j] = fa[fa[i][j - 1]][j - 1];
	rep(i, 1, m) scanf("%lld%lld%lld%lld", &q[i].c, &q[i].w, &q[i].u, &q[i].v), q[i].id = i;
	rep(i, 1, m) Q[q[i].u].push_back({q[i].c, q[i].w, 1, q[i].id});
	rep(i, 1, m) Q[q[i].v].push_back({q[i].c, q[i].w, 1, q[i].id});
	rep(i, 1, m) Q[lca(q[i].u, q[i].v)].push_back({q[i].c, q[i].w, -1, q[i].id});
	dfs4(1, 0); rep(i, 1, m) printf("%lld\n", ans[i]); return 0;
}

ABC136F

容斥 + 二维数点

问题转化为:对于所有点,包含该点的矩形数量之和。

以该点为原点作出笛卡尔坐标系,四个象限内的点数分别为 \(\mathrm{I, II, III, IV}\)。对于选择该点,方案数为 \(2 ^ {n - 1}\)。若选择了对角的两个象限,方案数为 \(\mathrm{(2 ^ I - 1)(2 ^ {III} - 1)2 ^ {II}2 ^ {IV} + (2 ^ {II} - 1)(2 ^ {IV} - 1)2 ^ {I}2 ^ {III}}\);若四个象限都选择,则有 \(\mathrm{(2 ^ I - 1)(2 ^ {II} - 1)(2 ^ {III} - 1)(2 ^ {IV} - 1)}\),这部分需要容斥掉。总方案数就是

\[2 ^ {n - 1} - \mathrm{(2 ^ I - 1)(2 ^ {II} - 1)(2 ^ {III} - 1)(2 ^ {IV} - 1) + (2 ^ I - 1)(2 ^ {III} - 1)2 ^ {II}2 ^ {IV} + (2 ^ {II} - 1)(2 ^ {IV} - 1)2 ^ {I}2 ^ {III}} \]

二维数点即可。复杂度线性对数。

一不小心写丑了。挤在屏上一坨。。。

scanf("%lld", &n);
rep(i, 1, n) scanf("%lld%lld", &p[i].x, &p[i].y), p[i].id = i; 
rep(i, 1, n) px.push_back(p[i].x), py.push_back(p[i].y);
sort(px.begin(), px.end()); sort(py.begin(), py.end());
auto find = [&](const vector<int> &p, int x) -> int {
	return lower_bound(p.begin(), p.end(), x) - p.begin() + 1;
}; rep(i, 1, n) p[i].x = find(px, p[i].x), p[i].y = find(py, p[i].y);
sort(p + 1, p + n + 1, [&](node a, node b) { return a.x < b.x; });
auto add = [&](int x) { for (int i = x; i <= n; i += (i & -i)) s[i] ++ ; };
auto Ask = [&](int x) { int ans = 0; for (int i = x; i; i -= (i & -i)) ans += s[i]; return ans; };
auto ask = [&](int l, int r) { return Ask(r) - Ask(l - 1); };
auto clear = [&]() { memset(s, 0, sizeof s); return; };
rep(i, 1, n) III[p[i].id] = ask(1, p[i].y), II[p[i].id] = ask(p[i].y, n), add(p[i].y); clear();
dep(i, n, 1) IV[p[i].id] = ask(1, p[i].y), I[p[i].id] = ask(p[i].y, n), add(p[i].y); clear();
pw[0] = 1; rep(i, 1, n) pw[i] = (pw[i - 1] * 2ll) % mod;
rep(i, 1, n) I[i] = pw[I[i]], II[i] = pw[II[i]], III[i] = pw[III[i]], IV[i] = pw[IV[i]];
rep(i, 1, n) ans = (ans - (I[i] - 1) * (II[i] - 1) % mod * (III[i] - 1) % mod * (IV[i] - 1) % mod) % mod,
			 ans = (ans + (I[i] - 1) * (III[i] - 1) % mod * II[i] % mod * IV[i] % mod) % mod,
			 ans = (ans + (II[i] - 1) * (IV[i] - 1) % mod * I[i] % mod * III[i] % mod) % mod,
			 ans = (ans + pw[n - 1] + mod) % mod;
printf("%lld\n", ans); return 0;

ABC137F

费马小定理 + 构造

考虑费马小定理:\(n ^ {p - 1} \equiv 1(\bmod\ p)\)

猜想该多项式是通过若干个 \((x - i) ^ {p - 1}\) 相加得到的。

参考拉格朗日插值的构造方式,不难得出这样的构造:

\[f(x) = \sum (a_i - a_i(x - i) ^ {p - 1}) \]

对于 \(x = i\)\(f(x)\) 中有贡献的项只有 \(a_i - a_i (i - i) ^ {p - 1} = a_i\) 这一项。剩下的项都是 \(0\)

二项式定理展开即可。复杂度 \(O(n ^ 2)\)

scanf("%lld", &p); inv[0] = fac[0] = 1;
rep(i, 0, p - 1) scanf("%lld", &a[i]);
rep(i, 1, p - 1) fac[i] = fac[i - 1] * i % p;
auto qpow = [&](int a, int b = p - 2, int s = 1) {
	for (; b; b >>= 1, a = a * a % p) if (b & 1) s = s * a % p; return s;
}; inv[p - 1] = qpow(fac[p - 1]);
dep(i, p - 2, 1) inv[i] = inv[i + 1] * (i + 1) % p;
auto C = [&](int n, int m) { return fac[n] * inv[m] * inv[n - m] % p; };
rep(i, 0, p - 1) (ans[0] += a[i]) %= p;
rep(i, 0, p - 1) rep(j, 0, p - 1) 
	ans[p - j - 1] -= a[i] * ((j & 1) ? -1 : 1) * C(p - 1, j) * qpow(i, j) % p,
	ans[p - j - 1] %= p, ans[p - j - 1] += p, ans[p - j - 1] %= p;
rep(i, 0, p - 1) printf("%lld ", ans[i]); return 0;

ABC138F

考虑异或和取模的性质:

  • \(x \le y < 2x\),则 \(y \bmod x = y - x\)\(y \oplus x \ge y - x\)

  • \(y > 2x\),则 \(y \bmod x = y - x\)\(y \oplus x > y - x\)

由此可见,如果让 \(y \bmod x = y \oplus x\),需要满足 \(x \le y < 2x\),也就是 \(x\)\(y\) 在二进制表示下位数相同。

考虑数位 dp。不妨设 \(f_{i, j, k}\) 表示当前填完了 \(i\) 位(从高往低),是否顶上界,是否顶下界。我们考虑当前这一位的情况:

  • \(y\)\(1\)\(x\)\(0\),则 \((y - x)_i = 1, (y \oplus x)_i = 1\),合法。

  • \(y\)\(1\)\(x\)\(1\),则 \((y - x)_i = 0, (y \oplus x)_i = 0\),合法。

  • \(y\)\(0\)\(x\)\(0\),则 \((y - x)_i = 0, (y \oplus x)_i = 0\),合法。

  • \(y\)\(0\)\(x\)\(1\),则 \((y - x)_i = 1, (y \oplus x)_i = 1\)。但是这种情况要讨论一下。我们再相减的时候从前面借了一位。找到这一位往前第一个 \(1\) 的位置,也就是借位的位置。我们发现,无论 \(x\) 这一位填的是 \(0\) 还是 \(1\) 都是不合法的。因此不可能出现借位的情况。

因此确定,\(x\) 该位所填的一定 \(\le\) \(y\) 该位所填的。dp 转移即可。

数位 dp 的复杂度真没啥要分析的,\(n = 20\) 狗来写都不会 T 吧。

auto Lg = [&](int x) { dep(i, 63, 0) if ((x >> i) & 1ll) return i; };
scanf("%lld%lld", &l, &r); int len1 = Lg(l) + 1, len2 = Lg(r) + 1;
auto solve = [](int l, int r, int n) -> int {
	rep(i, 0, n - 1) R[i] = (r >> i) & 1ll, L[i] = (l >> i) & 1ll;
	memset(f, 0, sizeof f); f[n - 1][1][1] = 1ll;
	dep(i, n - 1, 1) rep(j, 0, 1) rep(k, 0, j) rep(a, 0, 1) rep(b, 0, 1) {
		if ((a and j > R[i - 1]) or (b and k < L[i - 1])) continue;
		(f[i - 1][a & (j == R[i - 1])][b & (k == L[i - 1])] += f[i][a][b]) %= mod;
	} int s = 0; rep(i, 0, 1) rep(j, 0, 1) (s += f[0][i][j]) %= mod; return s;
}; rep(i, len1, len2) ans += solve(max(1ll << i - 1, l), min((1ll << i) - 1, r), i);
printf("%lld\n", ans % mod); return 0;

题解清一色记搜。记搜是坏文明,递推是好文明。

ABC157F

随机化。

好久没写随机手又痒了。拿来练练手。直接套上遗传板子。

发现参数非常难调。于是换不同参数多跑几遍就行了。

struct node { double x, y, c; }p[N];
double random(double x) { return (double)rand() / RAND_MAX * x; }
struct Individual {
    double x, y, fitness;
    Individual(double x, double y);
    Individual mate();
    double calc_fitness();
    bool operator < (const Individual& tmp)const {
        return fitness < tmp.fitness;
    }
};
Individual::Individual(double x, double y) {
    this -> x = x, this -> y = y;
    this -> fitness = calc_fitness();
}
Individual Individual::mate() {
	double X = x + ((rand() & 1) ? -1 : 1) * random(lim);
	double Y = y + ((rand() & 1) ? -1 : 1) * random(lim);
	return Individual(X, Y);
}
double dist(double x1, double y1, double x2, double y2, double c) {
	auto sq = [&](double x) { return x * x; };
	return sqrt(sq(x1 - x2) + sq(y1 - y2)) * c;
}
double Individual::calc_fitness() {
    vector<double> vec; rep(i, 1, n)
		vec.push_back(dist(x, y, p[i].x, p[i].y, p[i].c));
	sort(vec.begin(), vec.end()); return vec[k - 1];
}
double solve(int POPULATION, int TIMES, double T) {
	vector<Individual> population; lim = 1000.00;
    for (int i = 0; i < POPULATION; i ++ ) {
        double x = random(100000.00) * (rand() & 1 ? -1 : 1);
        double y = random(100000.00) * (rand() & 1 ? -1 : 1);
        population.push_back(Individual(x, y));
    }
    for (int i = TIMES; i >= 1; i -- ) {
        sort(population.begin(), population.end());
        vector<Individual> new_population;
        int s = (10 * POPULATION) / 100;
        for (int i = 0; i < s; i ++ )
            new_population.push_back(population[i]);
        s = POPULATION - s;
        while (new_population.size() < POPULATION) {
            int len = population.size();
            Individual p = population[rand() % 50];
            Individual q = p.mate();
            new_population.emplace_back(q);
        }  population = new_population; lim = lim * T;
    } return population[0].fitness;
}
int main() {
	srand(998244353);
    scanf("%d%d", &n, &k);
    rep(i, 1, n) scanf("%lf%lf%lf", &p[i].x, &p[i].y, &p[i].c);
    double ans = solve(300, 800, 0.985);
	ans = min(ans, solve(800, 800, 0.97));
    ans = min(ans, solve(500, 1000, 0.98));
    ans = min(ans, solve(1000, 500, 0.97));
    ans = min(ans, solve(800, 800, 0.975));
    printf("%.7lf\n", ans);
}

ABC237G

线段树简单题

好像和某道叫排序的题差不多。。。考虑将 \(< x\) 的记为 \(0\)\(> x\) 的记为 \(2\)\(= x\) 的记为 \(1\)

对一个区间的升序 / 降序排序,就是将这一段赋成 00...00122...22 / 22...22100...00 的过程。这一部分可以直接上一棵线段树维护区间赋值。

由于时间给到了八秒,使用 bit 压位实现也未尝不可。

void up(int u) { rep(i, 0, 2) s[u][i] = s[ls][i] + s[rs][i]; }
void push(int u, int l, int r, int v) { 
	tag[u][(v + 1) % 3] = tag[u][(v + 2) % 3] = 0, tag[u][v] = 1;
	s[u][(v + 1) % 3] = s[u][(v + 2) % 3] = 0; s[u][v] = r - l + 1;
}
void down(int u, int l, int r) {
	if (tag[u][0]) push(lb, 0), push(rb, 0), tag[u][0] = 0;
	if (tag[u][1]) push(lb, 1), push(rb, 1), tag[u][1] = 0;
	if (tag[u][2]) push(lb, 2), push(rb, 2), tag[u][2] = 0;
}
void build(int u, int l, int r) {
	if (l == r) return void(s[u][p[r]] ++ );
	build(lb), build(rb); up(u); return;
}
void chg(int u, int l, int r, int L, int R, int v) {
	if (l > R or L > r) return; if (l >= L and r <= R) return push(u, l, r, v);
	down(u, l, r); chg(lb, L, R, v), chg(rb, L, R, v); up(u);
}
int ask(int u, int l, int r, int L, int R, int v) {
	if (l > R or L > r) return 0; if (l >= L and r <= R) return s[u][v];
	down(u, l, r); return ask(lb, L, R, v) + ask(rb, L, R, v);
}
signed main() {
	scanf("%d%d%d", &n, &q, &x); rep(i, 1, n) scanf("%d", &p[i]);
	rep(i, 1, n) p[i] = (p[i] == x ? 1 : (p[i] > x ? 2 : 0)); build(T);
	for (int i = 1, op, l, r; i <= q; i ++ ) {
		scanf("%d%d%d", &op, &l, &r);
		int t0 = ask(1, 1, n, l, r, 0), t1 = ask(1, 1, n, l, r, 1), t2 = ask(1, 1, n, l, r, 2);
		if (op & 1) chg(T, l, l + t0 - 1, 0), chg(T, l + t0, l + t0 + t1 - 1, 1), chg(T, l + t0 + t1, r, 2);
		else chg(T, l, l + t2 - 1, 2), chg(T, l + t2, l + t2 + t1 - 1, 1), chg(T, l + t2 + t1, r, 0);
	} rep(i, 1, n) if (ask(1, 1, n, i, i, 1) == 1) return printf("%d\n", i), 0;
}

ABC155F

差分思维题

还有十分钟睡觉了,先记一下思路。首先将异或操作转化为 \(+1 \bmod 2\) 操作,这样可以和前缀和 / 差分挂上钩。

考虑原序列的差分数组 \(d\),对于 \(l \sim r\) 的操作,相当于是将 \(d_l + 1, d_{r + 1} + 1\)。很简单也很套路。

对于每个按钮,从 \(l\)\(r + 1\) 连边,形成一张图。需要找到一张子图,使得 \(d_i = 1\) 的点度为奇数,\(d_i = 0\) 的点度为偶数。

考虑到如果图中出现环,将所有边都选上一次必然不是最佳选择,因为全部选上相当于不选。

因此保留原图的一棵生成森林,dfs 匹配即可。匹配方法是从叶子忘根匹配。如果当前边为 \(x \rightarrow y\)\(y\)\(1\),而且在子树里匹配了偶数次,则 \((x, y)\) 必选,否则必不选。复杂度显然线性。

代码略微压行。

void dfs(int u) {
    vis[u] = 1; for (auto [v, id] : E[u]) if (!vis[v])
		add2(u, v, id), fa[v] = u, dfs(v);
}
void dfs2(int u, int Id) {
	vis[u] = 1; for (auto [v, id] : E2[u]) dfs2(v, id);
	if (s[u]) s[fa[u]] ^= 1, s[u] ^= 1, ans.push_back(Id);
}
bool check() { rep(i, 1, n + 1) if (s[a[i]]) return 0; return 1; }
bool solve(int w) {
    ans.clear(); memset(s, 0, sizeof s); memset(st, 0, sizeof st); b[n + 1] = w;
    rep(i, 1, n + 1) st[a[i]] = b[i]; rep(i, 1, n) if (!vis[i]) dfs(i);
	rep(i, 1, n + 1) s[i] = st[i] ^ st[i - 1]; memset(vis, 0, sizeof vis);
	rep(i, 1, n) { if (!vis[i]) dfs2(i, 0); if (s[0]) return 0; }
	if (!check()) return 0; sort(ans.begin(), ans.end()); return 1;
}
int main() {
	scanf("%d%d", &n, &m);
	rep(i, 1, n) scanf("%d%d", &a[i], &b[i]); a[n + 1] = V;
	rep(i, 1, n + 1) p.push_back(a[i]);
	rep(i, 1, m) scanf("%d%d", &l[i], &r[i]);
	sort(p.begin(), p.end()); p.resize(unique(p.begin(), p.end()) - p.begin());
	auto find = [&](int x) { return lower_bound(p.begin(), p.end(), x) - p.begin() + 1; };
	rep(i, 1, m) add(find(l[i]), find(r[i] + 1), i); rep(i, 1, n + 1) a[i] = find(a[i]);
	if (solve(0)) { printf("%d\n", (int)ans.size()); for (int i : ans) printf("%d ", i); return 0; }
	if (solve(0)) { printf("%d\n", (int)ans.size()); for (int i : ans) printf("%d ", i); return 0; }
	return puts("-1"), 0;
}

ABC163F

奇妙数据结构 + 容斥。

抽出颜色为 \(c\) 的虚树。对于一个点的父亲来说,如果是其 lca,且颜色不是 \(c\),则对于改点到其父亲之间,向上继续转移的边都可以和任意子树匹配(若在该点子树内已经匹配到 \(c\)),否则都必须向上转移。

因此设 \(f_u\) 表示 \(u\) 子树内已经匹配完的方案数,\(g_{u, k}\) 表示还在向上匹配,子树内是否已经匹配上 \(c\) 的方案数,时间复杂度 \(O(\sum S(c)) = O(n)\)

但是非常复杂。正难则反,换个方向考虑。考虑对于每个颜色 \(c\),求出不包含该颜色的路径条数。

将颜色为 \(c\) 的全部扣掉,剩下若干个连通块,连通块内部可以随意匹配,连通块之间不能匹配,方案数就是 \(\sum \dbinom{sz}{2}\)。复杂度 \(O(n ^ 2)\)

发现复杂度的瓶颈在于维护对于每个颜色连通块的大小。假设对于上一个颜色的已经维护出了连通块的结构,维护下一个颜色的连通块时,只需要连起来一些连通块,然后再断掉一个端点颜色是当前颜色的边即可。发现每条边最多会被删一次加一次,使用 LCT 维护即可。复杂度 \(O(n \log n)\)。由于 LCT 太难写,这道题就不写了。

可能还有更简单的方法我太菜了想不到吧。。。

upd:可能可以线段树分治。对于颜色线段树分治,使用可撤销并查集维护每个连通块的大小。复杂度 \(O(n \log ^ 2 n)\)

int find(int x) { return fa[x] == x ? x : find(fa[x]); }
bool ins(int u, int v) {
	if (find(u) == find(v)) return 0;
	if (sz[u = find(u)] > sz[v = find(v)]) swap(u, v);
	stk[ ++ top] = u; fa[u] = v;
	S -= sz[v] * (sz[v] - 1); S -= sz[u] * (sz[u] - 1);
	sz[v] += sz[u]; S += sz[v] * (sz[v] - 1); return 1;
}
void del() {
	int u = stk[top -- ]; S -= sz[fa[u]] * (sz[fa[u]] - 1);
	sz[fa[u]] -= sz[u]; S += sz[u] * (sz[u] - 1); S += sz[fa[u]] * (sz[fa[u]] - 1);
	fa[u] = u;
}
void ins(int u, int l, int r, int L, int R, int a, int b) {
	if (l > R or L > r) return; 
	if (l >= L and r <= R) return vec[u].push_back({a, b});
	ins(ls, l, mid, L, R, a, b); ins(rs, mid + 1, r, L, R, a, b);
}
void dfs(int u, int l, int r) {
	int cnt = 0;
	for (auto [x, y] : vec[u]) if (ins(x, y)) cnt ++ ;
	if (l == r) { ans[r] = (S >> 1); }
	else dfs(ls, l, mid), dfs(rs, mid + 1, r);
	rep(i, 1, cnt) del();
}
signed main() {
	scanf("%lld", &n); rep(i, 1, n) scanf("%lld", &c[i]);
	rep(i, 1, n - 1) { scanf("%lld%lld", &a[i], &b[i]); }
	rep(i, 1, n) fa[i] = i, sz[i] = 1, bin[c[i]] ++ ;
	rep(i, 1, n - 1) {
		int c1 = c[a[i]], c2 = c[b[i]];
		if (c1 > c2) swap(c1, c2);
		if (c1 >= 2) ins(1, 1, n, 1, c1 - 1, a[i], b[i]); 
		if (c1 + 1 <= c2 - 1) ins(1, 1, n, c1 + 1, c2 - 1, a[i], b[i]);
		if (c2 + 1 <= n) ins(1, 1, n, c2 + 1, n, a[i], b[i]);
	} dfs(1, 1, n); rep(i, 1, n) printf("%lld\n", (n * (n - 1) / 2) - ans[i] + bin[i]);
}

ABC239G

简单网络牛蹄。

拆点,拆出来两个点分别为出点和入点。从出点到入点连接容量为 \(c_i\) 的边。以 \(1\) 号点的出点为源点,\(n\) 号点的入点为汇点做最小割即可。

对于方案的输出:对于最终选上的点,在最后一次 bfs 建分层图后,一定有入点可以被遍历到,出点不可以被遍历到。输出即可。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <vector>
#define int long long
#define rep(i, a, b) for (int i = (a); i <= (b); i ++ )

using namespace std;

const int INF = 2e9;
const int N = 200010;
int h[N], e[N], ne[N], f[N], idx = 1;
int n, m, S, T, cur[N], d[N], a[N], b[N], c[N];
vector<int> ans;
void add(int a, int b, int c) {
    e[ ++ idx] = b, ne[idx] = h[a], h[a] = idx, f[idx] = c;
    e[ ++ idx] = a, ne[idx] = h[b], h[b] = idx, f[idx] = 0;
}
bool bfs() {
    queue<int> q; memcpy(cur, h, sizeof cur); 
    memset(d, -1, sizeof d); q.push(S); d[S] = 0;
    while (q.size()) {
        auto u = q.front(); q.pop(); if (u == T) return 1;
        for (int i = h[u]; i; i = ne[i]) {
            int v = e[i]; if ((~d[v]) || (!f[i])) continue;
            d[v] = d[u] + 1; cur[v] = h[v], q.push(v);
        }
    } return 0;
}
int dfs(int u, int F) {
    if (u == T) return F; int now = 0;
    for (int &i = cur[u]; i; i = ne[i]) {
        int v = e[i]; if (d[v] == d[u] + 1 and f[i]) {
            if (now = dfs(v, min(F, f[i]))) {
                f[i] -= now, f[i ^ 1] += now; return now;
            }
        }
    } return 0;
}
int dinic(int s = 0, int nw = 0) {
    while (bfs()) while (nw = dfs(S, INF)) s += nw; return s;
}
signed main() {
    scanf("%lld%lld", &n, &m);
    rep(i, 1, m) scanf("%lld%lld", &a[i], &b[i]);
    rep(i, 1, n) scanf("%lld", &c[i]);
    rep(i, 1, n) add(i, i + n, c[i]);
    rep(i, 1, m) add(a[i] + n, b[i], INF), add(b[i] + n, a[i], INF);
    S = 1 + n, T = n; printf("%lld\n", dinic());
    rep(i, 2, n - 1) if ((~d[i]) and (d[i + n] == -1))
    	ans.push_back(i);
    sort(ans.begin(), ans.end()); printf("%lld\n", (int)ans.size());
    for (auto i : ans) printf("%lld ", i); return 0;
}

ABC236Ex

\(f_s\) 表示当前已经构造完了的位置状态为 \(s\) 的方案数。考虑加入一个数 \(d_i\),方案数首先要乘上 \(\left \lfloor \dfrac{m}{d_i} \right \rfloor\),变成 \(\left \lfloor \dfrac{m}{d_i} \right \rfloor f_s\)

然鹅发现有一些 \(a_i = a_j\) 的被重复计算了。考虑容斥。

\[f_{s \bigcup i} = \left \lfloor \dfrac{m}{d_i} \right \rfloor f_s - \sum_{j \in s} \left \lfloor \dfrac{m}{[d_i, d_j]} \right \rfloor f_{s \setminus \{i, j\}} \]

要做枚举子集的操作,故复杂度 \(O(3 ^ n \log V)\),在预处理 \(\operatorname{lcm}\) 的前提下可以做到 \(O(3 ^ n + 2 ^ n \log n)\)

似乎很对。但是发现容斥系数实际上是错误的。但是我也不会了。

\[f_{s \bigcup i} = \left \lfloor \dfrac{m}{d_i} \right \rfloor f_s - g(s) \sum_{j \in s} \left \lfloor \dfrac{m}{[d_i, d_j]} \right \rfloor f_{s \setminus j} \]

ABC218H

DP 凸优化。

考虑暴力 dp,设 \(f_{i, j, k}\) 表示当前选到了第 \(i\) 个 lamp,已经选择了 \(j\) 个红色的,最后一个选的是 \(k\)。复杂度显然 \(O(nk)\)

因此考虑优化。考虑该 dp 是否具有凸性。设 \(f(x)\) 表示选择了 \(x\) 个红 lamp,所取得的最大收益。可以发现,\(f(x) = f(n - x)\)

然后发现,对于 \(x \le \dfrac{n}{2}\)\(f(x) - f(x - 1) \ge f(x + 1) - f(x)\),换句话说就是该函数上凸。

二分斜率切凸包即可。典型的 wqs 二分,复杂度 \(O(n \log V)\)

然鹅第 \(18\) 个点打死都过不去。。。自闭了。

ABC220G

小模拟。

考虑到形成等腰梯形的充要条件:两底边中垂线重合且两底边不共线。

枚举每条线段求出其中垂线方程,排个序判断一下就 OK 了。复杂度显然 \(O(n ^ 2)\),如果用 map 就多个老哥。

然鹅写挂了,不管了。

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <map>
#define k first
#define int long long
#define rep(i, a, b) for (int i = (a); i <= (b); i ++ )

using namespace std;

const int N = 1010;
const int P = 1e9 + 7;
int n, x[N], y[N], w[N], ans = -1;
map<pair<int, int>, int> bin;
map<int, int> mx1, mx2; 
pair<int, int> hs(int a, int b, int c, int x, int y) {
    if (a == 0 and b < 0) b *= -1, c *= -1;
    if (b == 0 and a < 0) a *= -1, c *= -1;
	if (a < 0) a *= -1, b *= -1, c *= -1; int d = 0;
	if (a) d = __gcd(abs(a), d);
	if (b) d = __gcd(abs(b), d);
	if (c) d = __gcd(abs(c), d);
	a /= d, b /= d, c /= d;
	int h1 = ((a * 13331ll % P * 13331ll % P + b * 13331ll % P + c) % P + P) % P;
	int h2 = ((x * 13331ll % P + y) % P + P) % P; return make_pair(h1, h2);
}
signed main() {
	scanf("%lld", &n);
	rep(i, 1, n) scanf("%lld%lld%lld", &x[i], &y[i], &w[i]);
	rep(i, 1, n) rep(j, i + 1, n) {
		int x1 = x[i], x2 = x[j], y1 = y[i], y2 = y[j];
		int a = y1 + y2, b = x1 + x2, c = x1 - x2, d = y1 - y2;
		auto h = hs(2 * d, 2 * c, b * c + a * d, x1 + x2, y1 + y2);
		bin[h] = max(bin[h], w[i] + w[j]);
	}
	for (auto [a, b] : bin) {
		if (b >= mx1[a.k]) mx2[a.k] = mx1[a.k], mx1[a.k] = b;
		else if (b > mx2[a.k]) mx2[a.k] = b;
	}
	for (auto [a, b] : mx2) if (b)
	    ans = max(ans, mx1[a] + b); 
	cout << ans << endl; return 0;
}

留几道题睡觉想。

ABC193F

算了还没关电脑就差不多了。

看到边两边颜色不同直接想到二分图匹配。

考虑网络流模型,后面不想写了,睡觉。

ABC163F

posted @ 2024-11-28 21:15  Link-Cut-Y  阅读(2)  评论(0编辑  收藏  举报