近获

\(\text{2-SAT}\) 问题

大致是从题目中得到一些限制,形如:
\(x\) 为真/假或 \(y\) 为真/假

问题是给 \(x\) 赋值满足上述条件,判定有无解,有则输出一种方案。

我们从条件中推出一些必然关系
如:若 \(x\)\(a\) ,则 \(y\) 必为 \(b\)
这是具有传递性的
这样我们只要判断是否存在矛盾,即:若 \(x\) 为真,推出 \(y\) 为······又推出······得到 \(x\) 为假······又得到 \(x\) 为真
也就是说 \(x\) 为真时推出 \(x\) 为假又推出 \(x\)
这样就不存在给 \(x\) 赋值的方法,于是问题无解

怎么做呢?
\(x\) 拆成 \(x_0,x_1\) 两点表示 \(x\) 为真/假,
那么就可以按上述严格关系建边
\(Tarjan\) 解决缩点后判断 \(x_0,x_1\) 是否存在同一连通分量即可

给出方案的话,注意到可能存在 \(x_0\) 推出 \(x_1\) 的情况,即我们要让 \(x\) 取真
观察到边的方向,不难想到令 \(x\) 取缩点后拓扑序最大的值即可

\(P4782\) 【模板】\(\text{2-SAT}\) 问题

\(\text{Code}\)

$\text{Code}$
#include <cstdio>
#include <iostream>
#define IN inline
using namespace std;

const int N = 2e6 + 5;
int n, m, dfn[N], low[N], stk[N], col[N], top, h[N], tot, dfc, color, vis[N];
struct edge{int to, nxt;}e[N];
IN void add(int x, int y) {e[++tot] = edge{y, h[x]}, h[x] = tot;}

IN void read(int &x) {
	x = 0; char ch = getchar(); int f = 1;
	for(; !isdigit(ch); f = (ch == '-' ? -1 : f), ch = getchar());
	for(; isdigit(ch); x = (x<<3)+(x<<1)+(ch^48), ch = getchar());
	x *= f;
}

void Tarjan(int x) {
	dfn[x] = low[x] = ++dfc, stk[++top] = x, vis[x] = 1;
	for(int i = h[x]; i; i = e[i].nxt) {
		int v = e[i].to;
		if (!dfn[v]) Tarjan(v), low[x] = min(low[x], low[v]);
		else if (vis[v]) low[x] = min(low[x], dfn[v]);
	}
	if (dfn[x] == low[x]) {
		++color;
		for(int u = 0; u != x; --top) col[u = stk[top]] = color, vis[u] = 0;
	}
}

int main() {
	read(n), read(m);
	for(int u, x, v, y; m; --m) {
		read(u), read(x), read(v), read(y);
		if (x) {if (y) add(u, v + n), add(v, u + n); else add(u, v), add(v + n, u + n);}
		else {if (y) add(u + n, v + n), add(v, u); else add(u + n, v), add(v + n, u);}
	}
	for(int i = 1; i <= n + n; i++) if (!dfn[i]) top = 0, Tarjan(i);
	for(int i = 1; i <= n + n; i++)
		if (col[i] == col[i + n]) {printf("IMPOSSIBLE\n"); return 0;}
	printf("POSSIBLE\n");
	for(int i = 1; i <= n; i++)
		if (col[i] < col[i + n]) printf("0 "); else printf("1 ");
}

\(P3209\text{[HNOI2010]}\) 平面图判定

单是平面图判定很难,但是题中明确给出了一条哈密顿回路,那么只要让剩下的边不相交
于是两条交叉边只能让其一条在里面一条在外面,这样就转化成了 \(\text{2-sat}\) 问题

\(P6378 \text{[PA2010] Riddle}\)

题中说到 “且每条边至少有一个端点是关键点”
于是考虑边端点选不选

有 “使得每个部分恰有一个关键点”
于是考虑同一部分只能选 \(1\)

如此建图即可
但发现边数过多后者
做点简单的观察发现可以新建点前后缀优化
于是构造一下

\(\text{WQS}\) 二分

\(P4983\) 忘情

简单 \(dp\)
发现必需分成 \(m\) 段复杂度很高
没有这个限制就很好做
于是把分成 \(i\) 段看成点 \((i,f(i))\)
发现是凸的,于是可以 \(\text{WQS}\) 二分
具体的就是二分直线斜率,让这条直线切凸包
发现如果让直线分别经过每个点,那么斜率最大/最小的就是切点,有 \(f(i)-ki=b\),如果令原问题多选一段就多 \(-k\),由于只关心 \(b\) 的最值,那么就相当于没有一定分成 \(m\) 段的限制了

但毕竟需要分成 \(m\) 段,所以看看切到的点的横坐标足不足 \(m\),再进行二分,因为凸包斜率具有单调性所以可行

二分范围均取整数的话,可能会使直线同时切到多个截距最值点,其中可能包含 \(m\),所以答案是 \(mid+km\)
且具体题目要考虑斜率变化到底可能会增加分段还是减少分段以确定二分的写法

\(\text{Code}\)

$\text{Code}$
#include <cstdio>
#include <cstring>
#include <iostream>
#define IN inline
using namespace std;
typedef long long ll;

const int N = 1e5 + 5;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m, g[N], Q[N];
ll f[N], s[N];

IN ll sqr(ll x) {return x * x;}
IN double slope(int j, int k) {
	return 1.0 * ((f[j] + sqr(s[j]) - s[j] * 2) - (f[k] + sqr(s[k]) - s[k] * 2)) / (s[j] - s[k]);
}

IN int check(ll mid) {
	int l = 0, r = 1; Q[++l] = 0;
	for(int i = 1; i <= n; i++) {
		while (l < r && slope(Q[l], Q[l + 1]) < s[i] * 2.0) ++l;
		f[i] = f[Q[l]] + sqr(s[i] - s[Q[l]] + 1) - mid, g[i] = g[Q[l]] + 1;
		while (l < r && slope(Q[r], i) < slope(Q[r], Q[r - 1])) --r;
		Q[++r] = i;
	}
	return g[n] <= m;
}

int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) scanf("%lld", &s[i]), s[i] += s[i - 1];
	ll l = -INF, r = 0, mid = l + r >> 1, ans;
	for(; l <= r; mid = l + r >> 1)
		if (check(mid)) ans = f[n] + mid * m, l = mid + 1;
		else r = mid - 1;
	printf("%lld\n", ans);
}

拉格朗日插值优化 \(dp\)

\(P4463\) [集训队互测 \(2012\)] \(calc\)

还是明显的 \(dp\)
但发现值域这一维怎么都降不下来

于是假设 \(f(n,i)\) 是关于 \(i\)\(g(n)\) 次多项式,找到 \(g(n)\) 等于多少,那么只要取 \(g(n)+1\) 个值就可以拉格朗日插值直接计算 \(f(n,k)\)

本题观察转移方程得 \(f(n,i)-f(n,i-1)=f(n-1,i-1)\times i\)
发现左侧次数是 \(g(n)-1\),右侧次数是 \(g(n-1)+1\)
那么两多项式次数相等,有 \(g(n)-g(n-1)=2\)
\(g\) 为等差数列
\(f(0,i)=1\),即 \(g(0)=0\)
所以 \(g(n)=2n\)

于是只要得到 \(f(n,1)\)\(f(n,2n+1)\) 的值即可计算 \(f(n,k)\)

\(\text{Code}\)

$\text{Code}$
#include <cstdio>
#define IN inline
typedef long long ll;
using namespace std;

const int N = 1005;
int K, n, P, f[N][N], x[N], y[N];

IN int fpow(int x, int y) {
	int s = 1;
	for(; y; y >>= 1, x = (ll)x * x % P) if (y & 1) s = (ll)s * x % P;
	return s;
}
IN int Lagrange(int k) {
	int ans = 0;
	for(int i = 1; i <= n * 2 + 1; i++) {
		int s1 = y[i], s2 = 1;
		for(int j = 1; j <= n * 2 + 1; j++)
			if (i ^ j) s1 = (ll)s1 * (k - x[j]) % P, s2 = (ll)s2 * (x[i] - x[j]) % P;
		ans = (ans + (ll)s1 * fpow(s2, P - 2)) % P;
	}
	return ans;
}

int main() {
	scanf("%d%d%d", &K, &n, &P);
	for(int i = 0; i <= n * 2 + 1; i++) f[0][i] = 1;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n * 2 + 1; j++)
			f[i][j] = ((ll)f[i - 1][j - 1] * j % P + f[i][j - 1]) % P;
	for(int i = 1; i <= n * 2 + 1; i++) x[i] = i, y[i] = f[n][i];
	int fac = 1;
	for(int i = 2; i <= n; i++) fac = (ll)fac * i % P;
	printf("%d\n", (ll)Lagrange(K) * fac % P);
}

\(\text{CF995F Cowmpany Cowmpensation}\)

这不过是搬到了树上,一样分析即可

\(\text{Code}\)

$\text{Code}$
#include <cstdio>
#define IN inline
typedef long long ll;
using namespace std;

const int N = 3005, P = 1e9 + 7;
int n, m, d, h[N], fa[N], f[N][N], X[N], Y[N], tot;
struct edge{int to, nxt;}e[N];
IN void add(int x, int y) {e[++tot] = edge{y, h[x]}, h[x] = tot;}

void dfs(int u) {
	for(int i = h[u]; i; i = e[i].nxt) dfs(e[i].to);
	if (!h[u]) for(int i = 1; i <= m; i++) f[u][i] = i;
	else {
		for(int i = 1; i <= m; i++) {
			f[u][i] = f[u][i - 1];
			int s = 1;
			for(int j = h[u]; j; j = e[j].nxt) s = (ll)s * f[e[j].to][i] % P;
			(f[u][i] += s) %= P;
		}
	}
}

IN int fpow(int x, int y) {
	int s = 1;
	for(; y; y >>= 1, x = (ll)x * x % P) if (y & 1) s = (ll)s * x % P;
	return s;
}
IN int Lagrange(int k) {
	int res = 0;
	for(int i = 1; i <= m; i++) {
		int s1 = Y[i], s2 = 1;
		for(int j = 1; j <= m; j++)
			if (i ^ j) s1 = (ll)s1 * (k - X[j] + P) % P, s2 = (ll)s2 * (X[i] - X[j] + P) % P;
		res = (res + (ll)s1 * fpow(s2, P - 2) % P) % P;
	}
	return res;
}

int main() {
	scanf("%d%d", &n, &d), m = n + 1;
	for(int i = 2; i <= n; i++) scanf("%d", &fa[i]), add(fa[i], i);
	dfs(1);
	for(int i = 1; i <= m; i++) X[i] = i, Y[i] = f[1][i];
	printf("%d\n", Lagrange(d));
}

决策单调性优化 \(dp\)

这样的转移式一般写为 \(f_i = f_j + w_{j,i}\)
若能证明这样的 \(w\) 满足四边形不等式 \(w_{a,d}+w_{b,c} \ge w_{a,c}+w_{b,d}\)
则转移具有决策单调性
当然也可以证明 \(w_{a,b+1}+w_{a+1,b} \ge w_{a,b}+w_{a+1,b+1}\)
然后就可以用单调栈或单调队列维护决策点了
具体维护一些三元组 \((x,l,r)\) 表示决策点为 \(x\),它是点 \([l,r]\) 的最优决策点
由单调性发现可以二分确定 \([l,r]\)

\(\text{P1912 [NOI2009]}\) 诗人小G

打表发现得了。。。
维护单调队列,先弹掉队首不能够到当前点 \(i\) 的决策点
然后计算这个点的 \(dp\) 值,考虑当前点作为决策点对单调队列的影响
完了

\(\text{Code}\)

$\text{Code}$
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#define IN inline
using namespace std;

template <typename T>
IN void read(T &x) {
	x = 0; char ch = getchar(); int f = 0;
	for(; !isdigit(ch); f = (ch == '-' ? 1 : f), ch = getchar());
	for(; isdigit(ch); x = (x<<3)+(x<<1)+(ch^48), ch = getchar());
	if (f) x = ~x + 1;
}

typedef long double db;
const int N = 1e5 + 5;
int n, L, p, g[N];
db f[N], s[N];
char str[N][33];
struct node{int x, l, r;}Q[N];

IN db fpow(db x, int y) {
	db s = 1;
	for(; y; y >>= 1, x = x * x) (y & 1) && (s = s * x);
	return s;
}
IN db calc(int j, int i) {return f[j] + fpow(fabs(s[i] - s[j] - 1 - L), p);}
IN int find(int x, int y, int l) {
	int r = n, mid = l + r >> 1, res = l;
	for(; l <= r; mid = l + r >> 1)
		if (calc(x, mid) < calc(y, mid)) res = mid, l = mid + 1;
		else r = mid - 1;
	return res;
}

int main() {
	int T;
	for(read(T); T; --T, puts("--------------------")) {
		read(n), read(L), read(p);
		for(int i = 1; i <= n; i++)
			scanf("%s", str[i]), s[i] = s[i - 1] + strlen(str[i]) + 1;
		int h = 1, t = 1; Q[1] = node{0, 1, n};
		for(int i = 1; i <= n; i++) {
			for(; h < t && Q[h].r < i; ++h);
			f[i] = calc(Q[h].x, i), g[i] = Q[h].x;
			if (calc(i, Q[t].r) < calc(Q[t].x, Q[t].r)) {
				for(; h < t && calc(i, Q[t].l) <= calc(Q[t].x, Q[t].l); --t);
				Q[t].r = find(Q[t].x, i, Q[t].l), ++t, Q[t] = node{i, Q[t - 1].r + 1, n};
			}
		}
		if (f[n] > 1e18) {puts("Too hard to arrange"); continue;}
		printf("%.0Lf\n", f[n]);
		t = 0;
		for(int i = n; i; i = g[i]) Q[++t] = node{0, g[i] + 1, i};
		for(; t; --t)
			for(int i = Q[t].l; i <= Q[t].r; ++i) printf("%s%c", str[i], " \n"[i == Q[t].r]);
	}
}

\(\text{CF868F Yet Another Minimization Problem}\)

非常简单的 \(dp\),问题是区间贡献不好计算,二分栈或者二分队列不可行了
另一种实现方式:分治
计算当前的 \(f[mid]\),依靠缩小决策点区间减小复杂度,此时的区间贡献用类似莫队的指针暴力移动计算

\(\text{Code}\)

$\text{Code}$
#include <bits/stdc++.h>
#define IN inline
using namespace std;

typedef long long LL;
const int N = 1e5 + 5;
const LL INF = 1e18;
int a[N], n, K, nowl = 1, nowr, cnt[N];
LL f[23][N], Ans;

IN void update(int x, int v) {
	Ans -= (LL)cnt[a[x]] * (cnt[a[x]] - 1) / 2;
	cnt[a[x]] += v, Ans += (LL)cnt[a[x]] * (cnt[a[x]] - 1) / 2;
}
IN LL calc(int l, int r) {
	while (nowl < l) update(nowl++, -1);
	while (nowl > l) update(--nowl, 1);
	while (nowr < r) update(++nowr, 1);
	while (nowr > r) update(nowr--, -1);
	return Ans;
}

void solve(int p, int l, int r, int L, int R) {
	if (l > r || L > R) return;
	int mid = l + r >> 1, Mid = 0; LL res = INF;
	for(int i = L; i <= min(mid - 1, R); i++) {
		LL w = calc(i + 1, mid);
		if (f[p - 1][i] + w < res) res = f[p - 1][i] + w, Mid = i;
	}
	f[p][mid] = res, solve(p, l, mid - 1, L, Mid), solve(p, mid + 1, r, Mid, R);
}

int main() {
	scanf("%d%d", &n, &K);
	for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for(int i = 1; i <= n; i++) f[0][i] = INF;
	for(int i = 1; i <= K; i++) solve(i, 1, n, 0, n - 1);
	printf("%lld\n", f[K][n]);
}

杨氏矩阵

又称杨图
勾长:左边格子数加下面格子数加一
勾长公式:即把 \(1\)\(n\) 填入杨表的方案数等于 \(n!\) 除以所有方格的勾长的乘积
关于杨氏矩阵的一些性质:
第一行长度为该排列的最长上升子序列长度
第一列长度为该排列的最长下降子序列长度
对于一个格数为 \(n\) 的杨表,所有形态下的方案数的平方等于 \(n!\)

\(\text{P4484 [BJWC2018]}\) 最长上升子序列

考虑第一行为 \(k\) 的杨表,那么贡献是 \(k \times s^2\)\(s\)为该形态杨表的填充方案数
于是只要找到 \(n\) 的所有整数分拆形式,勾长公式计算贡献即可

\(\text{Code}\)

$\text{Code}$
#include <cstdio>
#include <iostream>
#define IN inline
using namespace std;

template <typename T>
IN void read(T &x) {
	x = 0; char ch = getchar(); int f = 0;
	for(; !isdigit(ch); f = (ch == '-' ? 1 : f), ch = getchar());
	for(; isdigit(ch); x = (x<<3)+(x<<1)+(ch^48), ch = getchar());
	if (f) x = ~x + 1;
}

typedef long long ll;
const int P = 998244353;
int n, a[31], ans, b[31][31], fac[31];

IN int fpow(int x, int y) {
	int s = 1;
	for(; y; y >>= 1, x = (ll)x * x % P) (y & 1) && (s = (ll)s * x % P);
	return s;
}
void dfs(int x, int s) {
	if (s >= n) {
		for(int i = 1; i < x; i++)
			for(int j = 1; j <= a[i]; j++) b[i][j] = 0;
		for(int i = 2; i < x; i++)
			for(int j = 1; j <= a[i - 1]; j++) b[i][j] = b[i - 1][j] + 1;
		int s = 1;
		for(int i = 1; i < x; i++)
			for(int j = 1; j <= a[i]; j++) s = (ll)s * (b[i][j] + a[i] - j + 1) % P;
		s =  (ll)fpow(s, P - 2) * fac[n] % P, ans = (ans + (ll)s * s % P * a[x - 1] % P) % P;
		return;
	}
	for(int i = max(a[x - 1], 1); s + i <= n; ++i) a[x] = i, dfs(x + 1, s + i);
}

int main() {
	read(n), fac[0] = 1;
	for(int i = 1; i <= n; i++) fac[i] = (ll)fac[i - 1] * i % P;
	dfs(1, 0), printf("%d\n", (ll)ans * fpow(fac[n], P - 2) % P);
}
posted @ 2022-08-14 19:37  leiyuanze  阅读(39)  评论(0)    收藏  举报