【集训队作业】IOI 2020 集训队作业 试题泛做 13

Codeforces 679E Bear and Bad Powers of 42

不难发现数列中的元素不可能达到很大,我们只需要考虑 42 42 42 的前若干个幂。

考虑没有赋值操作的做法,则可用线段树维护区间中最接近 42 42 42 的下一个幂的数与这个幂的差值,在区间加时,只需要在线段树上 DFS 找到差值变负的位置更新其与 42 42 42 下一个幂的差值即可。

由于一个数导致进行 DFS 的次数是 O ( L o g 42 V ) O(Log_{42}V) O(Log42V) 的,因此这个算法的复杂度是有保证的。

存在赋值操作时,我们会将区间中整段的元素赋为同一个数。

注意到区间加操作至多破开两个整段,其余整段仍然可以被看做是整体,只需要再额外处理一下满足所有数都相同的区间即可保证复杂度正确。

时间复杂度 O ( ( N + Q ) L o g N × L o g 42 V ) O((N+Q)LogN\times Log_{42}V) O((N+Q)LogN×Log42V)

#include
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
const ll keys[11] = {1, 42, 1764, 74088, 3111696, 130691232, 5489031744ll, 230539333248ll, 9682651996416ll, 406671383849472ll, 17080198121677824ll};
ll nxt(ll x) {return (*upper_bound(keys, keys + 11, x)) - x; }
struct SegmentTree {
	struct Node {
		int lc, rc;
		ll tag, all, Min;
	} a[MAXN * 2];
	int n, root, size;
	void update(int root) {
		a[root].Min = min(a[a[root].lc].Min, a[a[root].rc].Min);
		a[root].all = (a[a[root].lc].all == a[a[root].rc].all) ? a[a[root].lc].all : 0;
	}
	void build(int &root, int l, int r, int *b) {
		root = ++size;
		if (l == r) {
			a[root].all = b[l];
			a[root].Min = nxt(b[l]);
			return;
		}
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid, b);
		build(a[root].rc, mid + 1, r, b);
		update(root);
	}
	void init(int x, int *b) {
		n = x, root = size = 0;
		build(root, 1, n, b);
	}
	void pushdown(int root) {
		if (a[root].all) {
			a[a[root].lc].all = a[root].all;
			a[a[root].lc].Min = a[root].Min;
			a[a[root].rc].all = a[root].all;
			a[a[root].rc].Min = a[root].Min;
		} else if (a[root].tag) {
			if (a[a[root].lc].all) a[a[root].lc].all += a[root].tag;
			else a[a[root].lc].tag += a[root].tag;
			a[a[root].lc].Min -= a[root].tag;
			if (a[a[root].rc].all) a[a[root].rc].all += a[root].tag;
			else a[a[root].rc].tag += a[root].tag;
			a[a[root].rc].Min -= a[root].tag;
		}
		a[root].tag = 0;
	}
	bool modify(int root, int l, int r, int ql, int qr, int d) {
		if (l == ql && r == qr) {
			if (a[root].Min > d) {
				a[root].Min -= d;
				if (a[root].all) a[root].all += d;
				else a[root].tag += d;
				return false;
			}
			if (l == r || a[root].all) {
				bool ans = false;
				if (nxt(a[root].all + d - 1) == 1) ans = true;
				a[root].all += d;
				a[root].Min = nxt(a[root].all);
				return ans;
			}
			pushdown(root);
			int mid = (l + r) / 2; bool ans = false;
			ans |= modify(a[root].lc, l, mid, l, mid, d);
			ans |= modify(a[root].rc, mid + 1, r, mid + 1, r, d);
			update(root);
			return ans;
		}
		pushdown(root);
		int mid = (l + r) / 2; bool ans = false;
		if (mid >= ql) ans |= modify(a[root].lc, l, mid, ql, min(mid, qr), d);
		if (mid + 1 <= qr) ans |= modify(a[root].rc, mid + 1, r, max(mid + 1, ql), qr, d);
		update(root);
		return ans;
	}
	void modify(int l, int r, int d) {
		bool flg = true;
		while (flg) flg = modify(root, 1, n, l, r, d);
	}
	void gvalue(int root, int l, int r, int ql, int qr, int d) {
		if (l == ql && r == qr) {
			a[root].all = d;
			a[root].Min = nxt(d);
			return;
		}
		pushdown(root);
		int mid = (l + r) / 2;
		if (mid >= ql) gvalue(a[root].lc, l, mid, ql, min(mid, qr), d);
		if (mid + 1 <= qr) gvalue(a[root].rc, mid + 1, r, max(mid + 1, ql), qr, d);
		update(root);
	}
	void gvalue(int l, int r, int d) {
		gvalue(root, 1, n, l, r, d);
	}
	ll query(int root, int l, int r, int pos) {
		if (l == r) return a[root].all;
		int mid = (l + r) / 2; pushdown(root);
		if (mid >= pos) return query(a[root].lc, l, mid, pos);
		else return query(a[root].rc, mid + 1, r, pos);
		update(root);
	}
	ll query(int pos) {
		return query(root, 1, n, pos);
	}
} ST;
int n, m, a[MAXN];
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	ST.init(n, a);
	for (int i = 1; i <= m; i++) {
		int opt; read(opt);
		if (opt == 1) {
			int x; read(x);
			printf("%lld\n", ST.query(x));
		} else if (opt == 2) {
			int l, r, x; read(l), read(r), read(x);
			ST.gvalue(l, r, x);
		} else {
			int l, r, x; read(l), read(r), read(x);
			ST.modify(l, r, x);
		}
	}
	return 0;
}

Codeforces 685C Optimal Point

二分答案,考虑如何判断。

注意到不等式 ∣ x − y ∣ ≤ z |x-y|\leq z xyz 等价于 x − y ≤ z , y − x ≤ z x-y\leq z,y-x\leq z xyz,yxz ,可以将限制拆分为 2 3 2^3 23 个不含绝对值的限制。

因此,我们可以得到 x + y + z , x + y − z , x − y + z , x − y − z x+y+z,x+y-z,x-y+z,x-y-z x+y+z,x+yz,xy+z,xyz 的取值范围。

首先,对于确定的 x + y + z , x + y − z , x − y + z , x − y − z x+y+z,x+y-z,x-y+z,x-y-z x+y+z,x+yz,xy+z,xyz ,只需要它们奇偶性相同便可以得到一组整数解,可以先枚举这个奇偶性。

2 z = ( x + y + z ) − ( x + y − z ) = ( x − y + z ) − ( x − y − z ) 2z=(x+y+z)-(x+y-z)=(x-y+z)-(x-y-z) 2z=(x+y+z)(x+yz)=(xy+z)(xyz) ,我们可以得到 z z z 的取值范围,若为空,则显然无解,否则,取任意一个 z z z 的值代入计算即可得到一组合法解。

时间复杂度 O ( ∑ N L o g V ) O(\sum NLogV) O(NLogV)

#include
using namespace std;
const int MAXN = 2e5 + 5;
const long long INF = 4e18;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n; ll rx, ry, rz, x[MAXN], y[MAXN], z[MAXN];
bool found(pair  pp, pair  pm, pair  mp, pair  mm, bool parity) {
	if ((pp.first % 2 == 0) ^ parity) pp.first++;
	if ((pm.first % 2 == 0) ^ parity) pm.first++;
	if ((mp.first % 2 == 0) ^ parity) mp.first++;
	if ((mm.first % 2 == 0) ^ parity) mm.first++;
	if ((pp.second % 2 == 0) ^ parity) pp.second--;
	if ((pm.second % 2 == 0) ^ parity) pm.second--;
	if ((mp.second % 2 == 0) ^ parity) mp.second--;
	if ((mm.second % 2 == 0) ^ parity) mm.second--;
	
	if (pp.first > pp.second) return false;
	if (pm.first > pm.second) return false;
	if (mp.first > mp.second) return false;
	if (mm.first > mm.second) return false;
	
	pair  rng = make_pair(-INF, INF);
	chkmax(rng.first, pp.first - pm.second);
	chkmin(rng.second, pp.second - pm.first);
	chkmax(rng.first, mp.first - mm.second);
	chkmin(rng.second, mp.second - mm.first);
	
	if (rng.first > rng.second) return false;
	rng.first /= 2, rng.second /= 2, rz = rng.first;
	
	pair  p = make_pair(-INF, INF);
	chkmax(p.first, pp.first - rz);
	chkmin(p.second, pp.second - rz);
	chkmax(p.first, pm.first + rz);
	chkmin(p.second, pm.second + rz);
	
	pair  m = make_pair(-INF, INF);
	chkmax(m.first, mp.first - rz);
	chkmin(m.second, mp.second - rz);
	chkmax(m.first, mm.first + rz);
	chkmin(m.second, mm.second + rz);
	
	rx = p.first / 2 + m.first / 2;
	if (p.first & 1) {
		if (p.first > 0 && m.first > 0) rx++;
		if (p.first < 0 && m.first < 0) rx--;
	}
	ry = p.first - rx;
	return true;
}
bool check(ll mid) {
	pair  pp = make_pair(-INF, INF), pm = make_pair(-INF, INF);
	pair  mp = make_pair(-INF, INF), mm = make_pair(-INF, INF);
	for (int i = 1; i <= n; i++) {
		chkmax(pp.first, x[i] + y[i] + z[i] - mid);
		chkmin(pp.second, x[i] + y[i] + z[i] + mid);
		
		chkmax(pm.first, x[i] + y[i] - z[i] - mid);
		chkmin(pm.second, x[i] + y[i] - z[i] + mid);
		
		chkmax(mp.first, x[i] - y[i] + z[i] - mid);
		chkmin(mp.second, x[i] - y[i] + z[i] + mid);
		
		chkmax(mm.first, x[i] - y[i] - z[i] - mid);
		chkmin(mm.second, x[i] - y[i] - z[i] + mid);
	}
	if (found(pp, pm, mp, mm, 0)) return true;
	if (found(pp, pm, mp, mm, 1)) return true;
	return false;
}
int main() {
	int T; read(T);
	while (T--) {
		read(n);
		for (int i = 1; i <= n; i++)
			read(x[i]), read(y[i]), read(z[i]);
		ll l = 0, r = 3e18;
		while (l < r) {
			ll mid = (l + r) / 2;
			if (check(mid)) r = mid;
			else l = mid + 1;
		}
		check(l), printf("%lld %lld %lld\n", rx, ry, rz);
	}
	return 0;
}

Codeforces 696F …Dary!

二分答案,考虑如何判定答案可行。

对于一个确定的边集,要求再凸多边形中选择一个点,覆盖这个边集,可以用半平面交求出这个点的取值范围,或是判定无解。那么,剩余的问题便在于如何将多边形的边恰当地分为两个边集。

**引理:**只需要考虑将边分为两个环上的区间的情况

**证明:**假设选中了多边形内的两个点,对坐标系进行平移、旋转,使得这两个点均在 X 轴上,且关于原点对称。那么,对于每一条边所在的直线 l l l ,若 l l l 与 X 轴交于正半轴,则更靠近正半轴的点,否则,其更靠近负半轴的点,由多边形的凸性,更靠近两点之一的边是一个区间。

那么,只需要用双指针计算出对于每个左端点 l l l ,最大的 r r r 使得边集 [ l , r ] [l,r] [l,r] 可以被覆盖,即可判断。

在计算半平面交时采用合并有序表排序,时间复杂度 O ( N 2 L o g V ) O(N^2LogV) O(N2LogV)

以下代码没有使用合并有序表排序,时间复杂度 O ( N 2 L o g N L o g V ) O(N^2LogNLogV) O(N2LogNLogV)

#include
using namespace std;
const int MAXN = 2005;
const double eps = 1e-10;
const double pi = acos(-1);
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
struct point {double x, y; };
struct line {point a, b; double alpha; };
point operator + (point a, point b) {return (point) {a.x + b.x, a.y + b.y}; }
point operator - (point a, point b) {return (point) {a.x - b.x, a.y - b.y}; }
point operator * (point a, double b) {return (point) {a.x * b, a.y * b}; }
double operator * (point a, point b) {return a.x * b.y - a.y * b.x; }
double moo(point a) {return sqrt(a.x * a.x + a.y * a.y); }
double PolarAngle(point a) {return atan2(a.y, a.x); }
point unit(point a) {return a * (1.0 / moo(a)); }
point intersect(const line &x, const line &y) {
	double tmp = (y.a - x.a) * (y.b - x.a);
	double tnp = (y.b - x.b) * (y.a - x.b);
	return (x.a * tnp + x.b * tmp) * (1 / (tmp + tnp));
}
bool onright(const line &l, const point &p) {
	if ((p - l.a) * (l.b - l.a) > -eps) return true;
	else return false;
}
bool onrightcmp(const line &l, const point &p) {
	if ((p - l.a) * (l.b - l.a) > eps) return true;
	else return false;
}
bool cmp(const line &a, const line &b) {
	if (fabs(a.alpha - b.alpha) > eps) return a.alpha < b.alpha;
	else return onrightcmp(a, b.a);
}
bool HalfPlane(int n, line *a, point &res) {
	static point p[MAXN];
	static line  q[MAXN];
	sort(a + 1, a + n + 1, cmp);
	int l = 0, r = -1;
	for (int i = 1; i <= n; i++) {
		while (l < r && onright(a[i], p[r])) r--;
		while (l < r && onright(a[i], p[l + 1])) l++;
		if (fabs(a[i].alpha - q[r].alpha) <= eps) continue;
		if (a[i].alpha - q[r].alpha >= pi - eps) return false;
		q[++r] = a[i];
		if (l < r) p[r] = intersect(q[r], q[r - 1]);
	}
	while (l < r && onright(q[l], p[r])) r--;
	while (l < r && onright(q[r], p[l + 1])) l++;
	if (r - l <= 1) return false;
	res = intersect(q[l], q[r]);
	return true;
}
int n; point a[MAXN]; line b[MAXN]; point vec[MAXN]; double p[MAXN];
bool check(int l, int r, double d, point &res) {
	static line c[MAXN]; int tot = 0;
	for (int i = 1; i <= n; i++)
		c[++tot] = b[i];
	for (int i = l; i <= r; i++)
		c[++tot] = (line) {a[i + 1] + vec[i] * d, a[i] + vec[i] * d, p[i]};
	return HalfPlane(tot, c, res);
}
bool check(double mid) {
	static int nxt[MAXN]; int pos = 1; point tmp;
	for (int i = 1; i <= 2 * n; i++) {
		while (pos < 2 * n && check(i, pos + 1, mid, tmp)) pos++;
		nxt[i] = pos;
	}
	nxt[2 * n + 1] = 2 * n + 1;
	for (int i = 1; i <= 2 * n; i++)
		if (nxt[nxt[i] + 1] >= i + n - 1) return true;
	return false;
}
void getans(double mid) {
	static int nxt[MAXN]; int pos = 1;
	static point ans[MAXN]; point tmp;
	for (int i = 1; i <= 2 * n; i++) {
		while (pos < 2 * n && check(i, pos + 1, mid, tmp)) pos++;
		nxt[i] = pos; assert(check(i, pos, mid, ans[i]));
	}
	nxt[2 * n + 1] = 2 * n + 1;
	for (int i = 1; i <= 2 * n; i++)
		if (nxt[nxt[i] + 1] >= i + n - 1) {
			printf("%.10lf %.10lf\n", ans[i].x, ans[i].y);
			printf("%.10lf %.10lf\n", ans[nxt[i] + 1].x, ans[nxt[i] + 1].y);
			return;
		}
	assert(false);
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++) {
		read(a[i].x), read(a[i].y);
		a[i + n] = a[i];
	}
	a[n * 2 + 1] = a[1];
	for (int i = 1; i <= 2 * n; i++) {
		b[i] = (line) {a[i], a[i + 1], PolarAngle(a[i + 1] - a[i])};
		vec[i] = a[i + 1] - a[i];
		swap(vec[i].x, vec[i].y), vec[i].x *= -1;
		vec[i] = vec[i] * (1 / moo(vec[i]));
		p[i] = PolarAngle(a[i] - a[i + 1]);
	}
	double l = 0, r = 1e5;
	while (l + eps < r) {
		double mid = (l + r) / 2;
		if (check(mid)) r = mid;
		else l = mid;
	}
	printf("%.10lf\n", r);
	getans(r);
	return 0;
}

Codeforces 698D Limak and Shooting Points

将怪物点集对各个传送位置进行极角排序,处理出各个怪物与传送位置连线上下一个怪物。

考虑每个怪物是否能被击中,则对于这只需要击中的怪物,可以枚举攻击它的位置,将连线上的怪物加入需要攻击的集合,由此搜索即可。

时间复杂度 O ( N × k ! × k ) O(N\times k!\times k) O(N×k!×k)

#include
using namespace std;
const int MAXN = 15;
const int MAXM = 1005;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
struct point {int x, y; };
point operator + (point a, point b) {return (point) {a.x + b.x, a.y + b.y}; }
point operator - (point a, point b) {return (point) {a.x - b.x, a.y - b.y}; }
point operator * (point a, int b) {return (point) {a.x * b, a.y * b}; }
long long operator * (point a, point b) {return 1ll * a.x * b.y - 1ll * a.y * b.x; }
long long moo(point a) {return 1ll * a.x * a.x + 1ll * a.y * a.y; }
point a[MAXM], p[MAXM]; bool flg, visa[MAXN], visp[MAXM];
int n, m, top, res[MAXN], nxt[MAXN][MAXM];
pair  b[MAXM];
void work(int depth) {
	if (top > n) return;
	if (depth > top) {
		flg = true;
		return;
	}
	for (int i = 1; i <= n; i++)
		if (!visa[i]) {
			visa[i] = true;
			int old = top, pos = nxt[i][res[depth]];
			while (pos != 0 && top <= n) {
				if (!visp[pos]) {
					visp[pos] = true;
					res[++top] = pos;
				}
				pos = nxt[i][pos];
			}
			work(depth + 1);
			while (top > old) visp[res[top--]] = false;
			visa[i] = false;
		}
}
bool where(point a) {
	return a.x > 0 || (a.x == 0 && a.y > 0);
}
bool cmp(pair  a, pair  b) {
	if (where(a.first) == where(b.first)) {
		if (a.first * b.first == 0) return moo(a.first) > moo(b.first);
		else return a.first * b.first > 0;
	} else return where(a.first) < where(b.first);
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
		read(a[i].x), read(a[i].y);
	for (int i = 1; i <= m; i++)
		read(p[i].x), read(p[i].y);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++)
			b[j] = make_pair(p[j] - a[i], j);
		sort(b + 1, b + m + 1, cmp);
		for (int j = 1; j <= m - 1; j++)
			if (where(b[j].first) == where(b[j + 1].first) && b[j].first * b[j + 1].first == 0)
				nxt[i][b[j].second] = b[j + 1].second;
	}
	int ans = 0;
	for (int i = 1; i <= m; i++) {
		flg = false;
		visp[i] = true;
		res[top = 1] = i;
		work(1), ans += flg;
		visp[i] = false;
	}
	printf("%d\n", ans);
	return 0;
}

Codeforces 700E Cool Slogans

不计复杂度,考虑如下做法:

枚举 x x x ,令 s 1 s_1 s1 为第 x x x 个字符,每次找到 s i s_i si 的下一个出现位置 s i ′ s_i' si ,令 s i + 1 s_{i+1} si+1 为恰好包含 s i , s i ′ s_i,s_i' si,si 的字符串,直到不存在 s i ′ s_i' si ,对所达到的 i i i 取最大值输出。

这个做法是正确的,因为对于任意的 s i + 1 s_{i+1} si+1 ,若 s i , s i ′ s_i,s_{i}' si,si 不分别是 s i + 1 s_{i+1} si+1 的前、后缀,则可以将 s i + 1 s_{i+1} si+1 缩短,从而可以认为 s i s_i si s i + 1 s_{i+1} si+1 的一个 border ,上面枚举的 x x x 相当于枚举了最终串的开头位置。

考虑用后缀数组优化这个做法,由字符串周期理论,字符串的 border 是由至多 O ( L o g N ) O(LogN) O(LogN) 段等差数列构成的,也就是说,我们只要能够较快地处理每一段等差数列即可。

那么,我们只需要支持找到 s i ′ s_i' si ,计算 s i s_i si 开头的后缀和 s i ′ s_i' si 开头的后缀的 LCP 即可,可以分别用后缀数组和可持久化线段树解决。

时间复杂度 O ( N L o g 2 N ) O(NLog^2N) O(NLog2N)

#include
using namespace std;
const int MAXN = 2e5 + 5;
const int MAXP = 5e6 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
namespace SuffixArray {
	const int MAXN = 2e5 + 5;
	const int MAXLOG = 20;
	const int MAXC = 256;
	int sa[MAXN], rnk[MAXN], height[MAXN];
	int Min[MAXN][MAXLOG], bit[MAXN], N;
	void init(char *a, int n) {
		N = n;
		static int x[MAXN], y[MAXN], cnt[MAXN], rk[MAXN];
		memset(cnt, 0, sizeof(cnt));
		for (int i = 1; i <= n; i++)
			cnt[(int) a[i]]++;
		for (int i = 1; i <= MAXC; i++)
			cnt[i] += cnt[i - 1];
		for (int i = n; i >= 1; i--)
			sa[cnt[(int) a[i]]--] = i;
		rnk[sa[1]] = 1;
		for (int i = 2; i <= n; i++)
			rnk[sa[i]] = rnk[sa[i - 1]] + (a[sa[i]] != a[sa[i - 1]]);
		for (int k = 1; rnk[sa[n]] != n; k <<= 1) {
			for (int i = 1; i <= n; i++) {
				x[i] = rnk[i];
				y[i] = (i + k <= n) ? rnk[i + k] : 0;
			}
			memset(cnt, 0, sizeof(cnt));
			for (int i = 1; i <= n; i++)
				cnt[y[i]]++;
			for (int i = 1; i <= n; i++)
				cnt[i] += cnt[i - 1];
			for (int i = n; i >= 1; i--)
				rk[cnt[y[i]]--] = i;
			memset(cnt, 0, sizeof(cnt));
			for (int i = 1; i <= n; i++)
				cnt[x[i]]++;
			for (int i = 1; i <= n; i++)
				cnt[i] += cnt[i - 1];
			for (int i = n; i >= 1; i--)
				sa[cnt[x[rk[i]]]--] = rk[i];
			rnk[sa[1]] = 1;
			for (int i = 2; i <= n; i++)
				rnk[sa[i]] = rnk[sa[i - 1]] + (x[sa[i]] != x[sa[i - 1]] || y[sa[i]] != y[sa[i - 1]]);		
		}
		int now = 0;
		for (int i = 1; i <= n; i++) {
			if (now) now--;
			while (a[i + now] == a[sa[rnk[i] + 1] + now]) now++;
			height[rnk[i]] = now;
		}
		for (int i = 1; i <= n; i++)
			Min[i][0] = height[i];
		for (int p = 1; p < MAXLOG; p++) {
			int tmp = 1 << (p - 1);
			for (int i = 1, j = tmp + 1; j <= n; i++, j++)
				Min[i][p] = min(Min[i][p - 1], Min[i + tmp][p - 1]);
		}
		for (int i = 1; i <= n; i++) {
			bit[i] = bit[i - 1];
			if (i >= 1 << (bit[i] + 1)) bit[i]++;
		}
	}
	int lcp(int x, int y) {
		if (x == y) return N - x + 1;
		x = rnk[x], y = rnk[y];
		if (x > y) swap(x, y);
		int tmp = bit[y - x];
		return min(Min[x][tmp], Min[y - (1 << tmp)][tmp]);
	}
}
struct SegmentTree {
	struct Node {
		int lc, rc;
		int sum;
	} a[MAXP];
	int n, size, root[MAXN];
	void build(int &root, int l, int r) {
		if (root == 0) root = ++size;
		if (l == r) return;
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid);
		build(a[root].rc, mid + 1, r);
	}
	void init(int x) {
		n = x; size = 0;
		build(root[0], 1, n);
	}
	int modify(int root, int l, int r, int pos, int delta) {
		int ans = ++size;
		a[ans] = a[root];
		a[ans].sum += delta;
		if (l == r) return ans;
		int mid = (l + r) / 2;
		if (mid >= pos) a[ans].lc = modify(a[root].lc, l, mid, pos, delta);
		else a[ans].rc = modify(a[root].rc, mid + 1, r, pos, delta);
		return ans;
	}
	void extend(int ver, int pos, int delta) {
		root[ver] = modify(root[ver - 1], 1, n, pos, delta);
	}
	int findnxt(int rootl, int rootr, int l, int r, int pos) {
		if (a[rootr].sum - a[rootl].sum == 0 || r <= pos) return -1;
		if (l == r) return l;
		int mid = (l + r) / 2, ans = -1;
		ans = findnxt(a[rootl].lc, a[rootr].lc, l, mid, pos);
		if (ans != -1) return ans;
		ans = findnxt(a[rootl].rc, a[rootr].rc, mid + 1, r, pos);
		return ans;
	}
	int findnxt(int l, int r, int pos) {
		return findnxt(root[l - 1], root[r], 1, n, pos);
	}
} ST;
int n; char s[MAXN];
int getans(int l) {
	int len = 1, ans = 1;
	using namespace SuffixArray;
	while (true) {
		int ql = 1, qr = rnk[l];
		while (ql < qr) {
			int mid = (ql + qr) / 2;
			if (lcp(sa[mid], l) >= len) qr = mid;
			else ql = mid + 1;
		}
		int tl = ql;
		ql = rnk[l], qr = n;
		while (ql < qr) {
			int mid = (ql + qr + 1) / 2;
			if (lcp(sa[mid], l) >= len) ql = mid;
			else qr = mid - 1;
		}
		int tr = ql;
		int nxt = ST.findnxt(tl, tr, l);
		if (nxt == -1) return ans;
		int lcpnxt = lcp(l, nxt), delta = nxt - l;
		int cnt = (lcpnxt + delta - len) / delta;
		ans += cnt, len += cnt * delta;
	}
	return -1;
}
int main() {
	read(n), scanf("\n%s", s + 1);
	SuffixArray :: init(s, n), ST.init(n);
	using namespace SuffixArray;
	for (int i = 1; i <= n; i++)
		ST.extend(i, sa[i], 1);
	int ans = 0;
	for (int i = 1; i <= n; i++)
		chkmax(ans, getans(i));
	printf("%d\n", ans);
	return 0;
}

Codeforces 704B Ant Man

对于 s > t s>t s>t 的情况,可以翻转序列,下令 s < t ss<t

代价中 ∣ x i − x j ∣ |x_i-x_j| xixj 的部分可以计入 a i , b i , c i , d i a_i,b_i,c_i,d_i ai,bi,ci,di ,从而不考虑这部分代价。

考虑从左向右 DP , i i i 号点左侧路径的轮廓线是由至多一个路径开头、一个路径结尾和若干条子路径组成的,路径开头在 i > s i>s i>s 时出现,路径结尾在 i < t ii<t 时出现。

因此,在状态中计入子路径的条数,即可 O ( 1 ) O(1) O(1) 转移。

时间复杂度 O ( N 2 ) O(N^2) O(N2)

#include
using namespace std;
const int MAXN = 5005;
const long long INF = 1e18;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
int n, s, t; ll dp[MAXN][MAXN];
int x[MAXN], a[MAXN], b[MAXN], c[MAXN], d[MAXN];
int main() {
	read(n), read(s), read(t);
	for (int i = 1; i <= n; i++)
		read(x[i]);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	for (int i = 1; i <= n; i++)
		read(b[i]);
	for (int i = 1; i <= n; i++)
		read(c[i]);
	for (int i = 1; i <= n; i++)
		read(d[i]);
	if (s > t) {
		s = n - s + 1;
		t = n - t + 1;
		for (int i = 1; i <= n; i++)
			x[i] = x[n] - x[i];
		reverse(x + 1, x + n + 1);
		reverse(a + 1, a + n + 1);
		reverse(b + 1, b + n + 1);
		reverse(c + 1, c + n + 1);
		reverse(d + 1, d + n + 1);
		swap(a, b), swap(c, d);
	}
	for (int i = 1; i <= n; i++) {
		a[i] += x[i];
		b[i] -= x[i];
		c[i] += x[i];
		d[i] -= x[i];
	}
	for (int i = 1; i <= n + 1; i++)
	for (int j = 0; j <= n; j++)
		dp[i][j] = INF;
	dp[1][0] = 0;
	for (int i = 1; i <= n; i++) {
		if (i == s) {
			for (int j = 1 - (i == 1); j <= n; j++) {
				chkmin(dp[i + 1][j + 1], dp[i][j] + d[i]);
				if (j != 0) chkmin(dp[i + 1][j], dp[i][j] + c[i]);
			}
		} else if (i == t) {
			for (int j = 1 - (i == 1); j <= n; j++) {
				chkmin(dp[i + 1][j], dp[i][j] + b[i]);
				if (j != 0) chkmin(dp[i + 1][j - 1], dp[i][j] + a[i]);
			}
		} else {
			for (int j = 1 - (i == 1); j <= n; j++) {
				chkmin(dp[i + 1][j + 1], dp[i][j] + b[i] + d[i]);
				if (j != 0) {
					chkmin(dp[i + 1][j], dp[i][j] + a[i] + d[i]);
					chkmin(dp[i + 1][j - 1], dp[i][j] + a[i] + c[i]);
					if (j != 1 || i < s || i > t) chkmin(dp[i + 1][j], dp[i][j] + b[i] + c[i]);
				}
			}
		}
	}
	cout << dp[n + 1][0] << endl;
	return 0;
}

Codeforces 704C Black Widow

考虑将 k = 2 k=2 k=2 的限制看做图中的一条边,则图中只存在路径和环。

分别在路径和环上 DP 即可。

时间复杂度 O ( N + M ) O(N+M) O(N+M)

#include
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 1e9 + 7;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
struct info {
	int dest, home;
	bool x, y;
};
vector  a[MAXN];
int n, m, ans[2], cur[2][2][2];
bool vis[MAXN], res[MAXN][2];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
void work(int pos, int fa) {
	vis[pos] = true;
	for (auto x : a[pos])
		if (x.home != fa) {
			if (vis[x.dest]) {
				int tmp[2] = {0, 0};
				for (int i = 0; i <= 1; i++)
				for (int j = 0; j <= 1; j++)
				for (int k = 0; k <= 1; k++)
					update(tmp[k ^ ((j ^ x.x) || (i ^ x.y))], cur[i][j][k]);
				int res[2] = {0, 0};
				for (int i = 0; i <= 1; i++)
				for (int j = 0; j <= 1; j++)
					update(res[i ^ j], 1ll * tmp[i] * ans[j] % P);
				ans[0] = res[0];
				ans[1] = res[1];
			} else {
				int nxt[2][2][2];
				memset(nxt, 0, sizeof(nxt));
				for (int i = 0; i <= 1; i++)
				for (int j = 0; j <= 1; j++)
				for (int k = 0; k <= 1; k++)
				for (int t = 0; t <= 1; t++)
					update(nxt[i][t][k ^ res[x.dest][t ^ 1] ^ ((j ^ x.x) || (t ^ x.y))], cur[i][j][k]);
				memcpy(cur, nxt, sizeof(nxt));
				work(x.dest, x.home);
			}
			return;
		}
	int tmp[2] = {0, 0};
	for (int i = 0; i <= 1; i++)
	for (int j = 0; j <= 1; j++)
	for (int k = 0; k <= 1; k++)
		update(tmp[k], cur[i][j][k]);
	int res[2] = {0, 0};
	for (int i = 0; i <= 1; i++)
	for (int j = 0; j <= 1; j++)
		update(res[i ^ j], 1ll * tmp[i] * ans[j] % P);
	ans[0] = res[0];
	ans[1] = res[1];
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++) {
		int k, x[3] = {0, 0, 0}; read(k);
		for (int j = 1; j <= k; j++)
			read(x[j]);
		if (k == 1) res[abs(x[1])][x[1] < 0] ^= true;
		else {
			int b = abs(x[1]), c = abs(x[2]);
			a[b].push_back((info) {c, i, x[1] < 0, x[2] < 0});
			a[c].push_back((info) {b, i, x[2] < 0, x[1] < 0});
		}
	}
	ans[0] = 1, ans[1] = 0;
	for (int i = 1; i <= m; i++)
		if (!vis[i] && a[i].size() <= 1) {
			memset(cur, 0, sizeof(cur));
			cur[0][0][res[i][1]] = 1;
			cur[1][1][res[i][0]] = 1;
			work(i, 0);
		}
	for (int i = 1; i <= m; i++)
		if (!vis[i]) {
			memset(cur, 0, sizeof(cur));
			cur[0][0][res[i][1]] = 1;
			cur[1][1][res[i][0]] = 1;
			work(i, 0);
		}
	cout << ans[1] << endl;
	return 0;
}

Codeforces 704D Captain America

首先,定义 t t t 次成功率为 p p p 的操作恰好成功 x x x 次的概率为 f t , p ( x ) f_{t,p}(x) ft,p(x) ,有 f t , p ( x ) = p x ( 1 − p ) t − x ( t x ) f_{t,p}(x)=p^x(1-p)^{t-x}\binom{t}{x} ft,p(x)=px(1p)tx(xt)

对于任意一行,留下的部分是区间 [ l , r ] [l,r] [l,r] 的概率为 f t , p ( l − 1 ) × f t , p ( M − r ) f_{t,p}(l-1)\times f_{t,p}(M-r) ft,p(l1)×ft,p(Mr)

考虑一个直观的 O ( N M 2 ) O(NM^2) O(NM2) 的动态规划做法,记 d p i , j , k dp_{i,j,k} dpi,j,k 表示前 i i i 行连通,并且第 i i i 行留下的区间为 [ j , k ] [j,k] [j,k] 的概率,记 p r e i , x = ∑ j = 1 x ∑ k = j x d p i , j , k , s u f i , x = ∑ j = x M ∑ k = j M d p i , j , k pre_{i,x}=\sum_{j=1}^{x}\sum_{k=j}^{x}dp_{i,j,k},suf_{i,x}=\sum_{j=x}^{M}\sum_{k=j}^{M}dp_{i,j,k} prei,x=j=1xk=jxdpi,j,k,sufi,x=j=xMk=jMdpi,j,k ,显然有转移
d p i , j , k = f t , p ( j − 1 ) × f t , p ( M − k ) × ( p r e i − 1 , M − p r e i − 1 , j − 1 − s u f i − 1 , k + 1 ) dp_{i,j,k}=f_{t,p}(j-1)\times f_{t,p}(M-k)\times (pre_{i-1,M}-pre_{i-1,j-1}-suf_{i-1,k+1}) dpi,j,k=ft,p(j1)×ft,p(Mk)×(prei1,Mprei1,j1sufi1,k+1)

我们发现转移时并不需要知道 d p i , j , k dp_{i,j,k} dpi,j,k ,只需要知道 p r e i , x pre_{i,x} prei,x s u f i , x suf_{i,x} sufi,x 即可。

考虑直接利用 p r e i , x pre_{i,x} prei,x s u f i , x suf_{i,x} sufi,x 进行动态规划,以计算 p r e i , x pre_{i,x} prei,x 为例,记 p r e i , x ′ = ∑ j = 1 x d p i , j , x pre'_{i,x}=\sum_{j=1}^{x}dp_{i,j,x} prei,x=j=1xdpi,j,x ,显然 p r e i , x pre_{i,x} prei,x 就是 p r e i , x ′ pre'_{i,x} prei,x 的前缀和。那么根据上面的计算式,有
p r e i , k ′ = ∑ j = 1 k f t , p ( j − 1 ) × f t , p ( M − k ) × ( p r e i − 1 , M − p r e i − 1 , j − 1 − s u f i − 1 , k + 1 ) pre'_{i,k}=\sum_{j=1}^{k}f_{t,p}(j-1)\times f_{t,p}(M-k)\times (pre_{i-1,M}-pre_{i-1,j-1}-suf_{i-1,k+1}) prei,k=j=1kft,p(j1)×ft,p(Mk)×(prei1,Mprei1,j1sufi1,k+1)

可以发现,若将括号拆开,每一项都可以表示为只和 i , k i,k i,k 相关的一个量乘以只和 i , j i,j i,j 相关的一个量,可以通过部分和进行优化。

时间复杂度为 O ( N M + K ) O(NM+K) O(NM+K)

#include
using namespace std;
const int MAXM = 2e5 + 5;
const int MAXN = 3005;
const int P = 1e9 + 7;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template  void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template  void writeln(T x) {
	write(x);
	puts("");
}
int pre[2][MAXN], suf[2][MAXN];
int n, m, p, q, t, lp[MAXN], rp[MAXN];
int fac[MAXM], inv[MAXM], powp[MAXM], powq[MAXM];
void update(int &x, int y) {
	x = (x + y >= P) ? (x + y - P) : (x + y);
}
void getdp() {
	static int sumlp[MAXN], sumrp[MAXN];
	for (int i = 1; i <= m; i++) {
		sumlp[i] = (sumlp[i - 1] + lp[i]) % P;
		pre[1][i] = 1ll * sumlp[i] * rp[i] % P;
		update(pre[1][i], pre[1][i - 1]);
	}
	for (int i = m; i >= 1; i--) {
		sumrp[i] = (sumrp[i + 1] + rp[i]) % P;
		suf[1][i] = 1ll * sumrp[i] * lp[i] % P;
		update(suf[1][i], suf[1][i + 1]);
	}
	for (int i = 2, now = 0, from = 1; i <= n; i++, swap(now, from)) {
		int tmp = 0;
		for (int j = 1; j <= m; j++) {
			pre[now][j] = 1ll * sumlp[j] * pre[from][m] % P;
			update(tmp, 1ll * pre[from][j - 1] * lp[j] % P);
			update(pre[now][j], P - tmp);
			update(pre[now][j], P - 1ll * sumlp[j] * suf[from][j + 1] % P);
			pre[now][j] = 1ll * pre[now][j] * rp[j] % P;
			update(pre[now][j], pre[now][j - 1]);
		}
		tmp = 0;
		for (int j = m; j >= 1; j--) {
			suf[now][j] = 1ll * sumrp[j] * pre[from][m] % P;
			update(tmp, 1ll * suf[from][j + 1] * rp[j] % P);
			update(suf[now][j], P - tmp);
			update(suf[now][j], P - 1ll * sumrp[j] * pre[from][j - 1] % P);
			suf[now][j] = 1ll * suf[now][j] * lp[j] % P;
			update(suf[now][j], suf[now][j + 1]);
		}
	}
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int getc(int x, int y) {
	if (y > x) return 0;
	else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
int getp(int x) {
	return 1ll * getc(t, x) * powp[x] % P * powq[t - x] % P;
}
void init(int n) {
	fac[0] = powp[0] = powq[0] = 1;
	for (int i = 1; i <= n; i++) {
		fac[i] = 1ll * fac[i - 1] * i % P;
		powp[i] = 1ll * powp[i - 1] * p % P;
		powq[i] = 1ll * powq[i - 1] * q % P;
	}
	inv[n] = power(fac[n], P - 2);
	for (int i = n - 1; i >= 0; i--)
		inv[i] = inv[i + 1] * (i + 1ll) % P;
	for (int i = 1; i <= m; i++) {
		lp[i] = getp(i - 1);
		rp[i] = getp(m - i);
	}
}
int main() {
	read(n), read(m);
	read(p), read(q);
	p = 1ll * p * power(q, P - 2) % P;
	q = (P + 1 - p) % P;
	read(t), init(t), getdp();
	printf("%d\n", pre[n & 1][m]);
	return 0;
}

Codeforces 704E Iron Man

考虑链上做法,每个人的坐标是一个关于时间的一次函数。

注意到当且仅当两人相碰,两人位置的相对顺序会发生改变,换言之,在两人相碰之前,所有人位置的相对顺序不变。

排序所有人出现,消失的事件,用平衡树维护所有人的相对位置即可。

回到原题,对原树进行树链剖分,对每条重链和轻边都运行链上做法即可。

时间复杂度 O ( N + M L o g 2 N ) O(N+MLog^2N) O(N+MLog2N)

本题中,涉及除法的地方不多,分子和分母的乘积始终在六十四位整型范围内,可以通过手写有理数类来避免实数运算。

#include
using namespace std;
const int MAXN = 2e5 + 5;
const int INF = 2e4;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template  void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template  void writeln(T x) {
	write(x);
	puts("");
}
struct frac {ll x, y; };
ll absll(ll x) {return x >= 0 ? x : -x; }
frac make(int x) {return (frac) {x, 1}; }
frac normal(frac x) {
	ll g = __gcd(absll(x.x), absll(x.y));
	if (x.y < 0) g = -g;
	return (frac) {x.x / g, x.y / g};
}
frac operator + (frac a, frac b) {return normal((frac) {a.x * b.y + a.y * b.x, a.y * b.y}); }
frac operator - (frac a, frac b) {return normal((frac) {a.x * b.y - a.y * b.x, a.y * b.y}); }
frac operator * (frac a, frac b) {return normal((frac) {a.x * b.x, a.y * b.y}); }
frac operator / (frac a, frac b) {return normal((frac) {a.x * b.y, a.y * b.x}); }
bool operator < (frac a, frac b) {return a.x * b.y < b.x * a.y; }
bool operator > (frac a, frac b) {return a.x * b.y > b.x * a.y; }
bool operator <= (frac a, frac b) {return a.x * b.y <= b.x * a.y; }
bool operator >= (frac a, frac b) {return a.x * b.y >= b.x * a.y; }
bool operator == (frac a, frac b) {return a.x * b.y == b.x * a.y; }
int n, m, depth[MAXN], size[MAXN], son[MAXN];
int timer, father[MAXN], dfn[MAXN], up[MAXN];
vector  a[MAXN]; double ans;
int lca(int x, int y) {
	while (up[x] != up[y]) {
		if (depth[up[x]] < depth[up[y]]) swap(x, y);
		x = father[up[x]];
	}
	if (depth[x] < depth[y]) return x;
	else return y;
}
frac dist(int x, int y) {
	int z = lca(x, y);
	return (frac) {depth[x] + depth[y] - 2 * depth[z], 1};
}
struct event {frac k, b, l, r; };
frac Timer, Goal;
bool operator < (event a, event b) {
	if (a.k * Timer + a.b == b.k * Timer + b.b) {
		if (a.l == b.l) {
			if (a.r == b.r) return a.k < b.k;
			else return a.r < b.r;
		} else return a.l < b.l;
	} else return a.k * Timer + a.b < b.k * Timer + b.b;
}
multiset  st;
bool cmp(pair  x, pair  y) {
	frac tx = x.second ? x.first.l : x.first.r;
	frac ty = y.second ? y.first.l : y.first.r;
	if (tx == ty) return x.second > y.second;
	else return tx < ty;
}
frac inter(event a, event b) {
	if (a.k == b.k) {
		if (a.b == b.b) return max(a.l, b.l);
		else return make(INF);
	}
	frac res = (b.b - a.b) / (a.k - b.k);
	if (res >= max(a.l, b.l) && res <= min(a.r, b.r)) return res;
	else return make(INF);
}
void work(vector  &a) {
	st.clear(), Goal = make(INF);
	vector > b;
	for (auto x : a) {
		b.emplace_back(x, true);
		b.emplace_back(x, false);
	}
	sort(b.begin(), b.end(), cmp);
	for (auto x : b) {
		frac now = x.second ? x.first.l : x.first.r;
		if (now >= Goal) {
			chkmin(ans, 1.0 * Goal.x / Goal.y);
			return;
		}
		Timer = now;
		if (x.second) {
			multiset  :: iterator tmp = st.insert(x.first);
			multiset  :: iterator pre = tmp, suf = tmp;
			if (pre != st.begin()) {
				pre--;
				chkmin(Goal, inter(*pre, *tmp));
			}
			if (++suf != st.end()) chkmin(Goal, inter(*tmp, *suf));
		} else {
			multiset  :: iterator tmp = st.lower_bound(x.first);
			multiset  :: iterator pre = tmp, suf = tmp;
			if (++suf != st.end() && pre != st.begin()) {
				pre--;
				chkmin(Goal, inter(*pre, *suf));
			}
			st.erase(tmp);
		}
	}
	chkmin(ans, 1.0 * Goal.x / Goal.y);
}
vector  heavy[MAXN], light[MAXN];
void addquery(int x, int y, frac s, frac v) {
	frac t = s + dist(x, y) / v;
	while (up[x] != up[y]) {
		if (depth[up[x]] > depth[up[y]]) {
			int f = up[x];
			heavy[f].push_back((event) {make(0) - v, make(depth[x]) + v * s, s, s + make(depth[x] - depth[f]) / v});
			s = s + make(depth[x] - depth[f]) / v;
			x = up[x], f = father[x];
			light[x].push_back((event) {make(0) - v, make(depth[x]) + v * s, s, s + make(depth[x] - depth[f]) / v});
			s = s + make(depth[x] - depth[f]) / v;
			x = father[x];
		} else {
			int f = up[y];
			heavy[f].push_back((event) {v, make(depth[y]) - v * t, t - make(depth[y] - depth[f]) / v, t});
			t = t - make(depth[y] - depth[f]) / v;
			y = up[y], f = father[y];
			light[y].push_back((event) {v, make(depth[y]) - v * t, t - make(depth[y] - depth[f]) / v, t});
			t = t - make(depth[y] - depth[f]) / v;
			y = father[y];
		}
	}
	int f = up[x];
	if (depth[x] > depth[y]) heavy[f].push_back((event) {make(0) - v, make(depth[x]) + v * s, s, t});
	else heavy[f].push_back((event) {v, make(depth[y]) - v * t, s, t});
}
void dfs(int pos, int fa) {
	depth[pos] = depth[fa] + 1;
	size[pos] = 1, son[pos] = 0;
	for (auto x : a[pos])
		if (x != fa) {
			dfs(x, pos);
			size[pos] += size[x];
			if (size[x] > size[son[pos]]) son[pos] = x;
		}
}
void efs(int pos, int fa, int from) {
	up[pos] = from;
	father[pos] = fa;
	dfn[pos] = ++timer;
	if (son[pos]) efs(son[pos], pos, from);
	for (auto x : a[pos])
		if (x != son[pos] && x != fa)
			efs(x, pos, x);
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n - 1; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	dfs(1, 0);
	efs(1, 0, 1);
	for (int i = 1; i <= m; i++) {
		frac t, v; int x, y;
		read(t.x), t.y = 1;
		read(v.x), v.y = 1;
		read(x), read(y);
		addquery(x, y, t, v);
	}
	ans = INF;
	for (int i = 1; i <= n; i++) {
		work(heavy[i]);
		work(light[i]);
	}
	if (ans >= INF) puts("-1");
	else printf("%.10lf\n", ans);
	return 0;
}

Codeforces 708D Incorrect Flow

设立超级源点 s s ss ss ,超级汇点 s t st st 。计算各个点出入度的差 d i d_i di ,若为正则令 s s ss ss 连向 i i i ,若为负则令 i i i 连向 s t st st ,费用为 0 0 0 ,容量为 ∣ d i ∣ |d_i| di

我们的目标是超级源到超级汇满流。

对于原图中的每一条边:

( 1 ) (1) (1) 、若 f ≤ c f\leq c fc
则将 f f f 改小的代价是 1 1 1 ,上限为 f f f
f f f 改大的代价是 1 1 1 ,上限为 c − f c-f cf
f f f 进一步改大的代价是 2 2 2 ,上限为 + ∞ +\infty +

( 2 ) (2) (2) 、若 f > c f>c f>c
则必然存在的花费为 f − c f-c fc
f f f 改小的代价是 0 0 0 ,上限为 f − c f-c fc
f f f 进一步改小的代价是 1 1 1 ,上限为 c c c
f f f 改大的代价是 2 2 2 ,上限为 + ∞ +\infty +

再增设一条汇点到源点的边,容量无穷,费用为零。

用最小费用最大流计算答案即可。

时间复杂度 O ( M i n C o s t F l o w ( N , M ) ) O(MinCostFlow(N,M)) O(MinCostFlow(N,M))

#include
using namespace std;
const int MAXN = 105;
const int INF  = 1e9 + 7;
typedef long long ll;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
namespace MincostFlow {
	const int MAXP = 105;
	const int MAXQ = 2e4 + 5;
	const int INF  = 2e9;
	struct edge {int dest, flow, pos, cost; };
	vector  a[MAXP];
	int n, m, s, t, tot, flow; ll cost;
	int dist[MAXP], path[MAXP], home[MAXP];
	void FlowPath() {
		int p = t, ans = INF;
		while (p != s) {
			ans = min(ans, a[path[p]][home[p]].flow);
			p = path[p];
		}
		flow += ans;
		cost += 1ll * ans * dist[t];
		p = t;
		while (p != s) {
			a[path[p]][home[p]].flow -= ans;
			a[p][a[path[p]][home[p]].pos].flow += ans;
			p = path[p];
		}
	}
	bool spfa() {
		static int q[MAXQ];
		static bool inq[MAXP];
		static int l = 0, r = 0;
		for (int i = 0; i <= r; i++)
			dist[q[i]] = INF;
		q[l = r = 0] = s, dist[s] = 0, inq[s] = true;
		while (l <= r) {
			int tmp = q[l];
			for (unsigned i = 0; i < a[tmp].size(); i++)
				if (a[tmp][i].flow != 0 && dist[tmp] + a[tmp][i].cost < dist[a[tmp][i].dest]) {
					dist[a[tmp][i].dest] = dist[tmp] + a[tmp][i].cost;
					path[a[tmp][i].dest] = tmp;
					home[a[tmp][i].dest] = i;
					if (!inq[a[tmp][i].dest]) {
						q[++r] = a[tmp][i].dest;
						inq[q[r]] = true;
					}
				}
			l++, inq[tmp] = false;
		}
		return dist[t] != INF;
	}
	void addedge(int x, int y, int z, int c) {
		a[x].push_back((edge){y, z, a[y].size(), c});
		a[y].push_back((edge){x, 0, a[x].size() - 1, -c});
	}
	ll work(int n, int x, int y) {
		s = x, t = y;
		for (int i = 1; i <= n; i++)
			dist[i] = INF;
		while (spfa()) FlowPath();
		return cost;
	}
}
int n, m, x[MAXN], y[MAXN], d[MAXN], c[MAXN], f[MAXN];
int main() {
	read(n), read(m);
	for (int i = 1; i <= m; i++) {
		read(x[i]), read(y[i]);
		read(c[i]), read(f[i]);
		d[x[i]] -= f[i];
		d[y[i]] += f[i];
	}
	int s = n + 1, t = n + 2;
	MincostFlow :: addedge(n, 1, INF, 0);
	for (int i = 1; i <= n; i++) {
		if (d[i] > 0) MincostFlow :: addedge(s, i, d[i], 0);
		else MincostFlow :: addedge(i, t, -d[i], 0);
	}
	ll ans = 0;
	for (int i = 1; i <= m; i++) {
		if (f[i] <= c[i]) {
			MincostFlow :: addedge(y[i], x[i], f[i], 1);
			MincostFlow :: addedge(x[i], y[i], c[i] - f[i], 1);
			MincostFlow :: addedge(x[i], y[i], INF, 2);
		} else {
			ans += f[i] - c[i];
			MincostFlow :: addedge(y[i], x[i], c[i], 1);
			MincostFlow :: addedge(y[i], x[i], f[i] - c[i], 0);
			MincostFlow :: addedge(x[i], y[i], INF, 2);
		}
	}
	cout << ans + MincostFlow :: work(n + 2, s, t) << endl;
	return 0;
}

Codeforces 708E Student’s Camp

首先,定义 t t t 次成功率为 p p p 的操作恰好成功 x x x 次的概率为 f t , p ( x ) f_{t,p}(x) ft,p(x) ,有 f t , p ( x ) = p x ( 1 − p ) t − x ( t x ) f_{t,p}(x)=p^x(1-p)^{t-x}\binom{t}{x} ft,p(x)=px(1p)tx(xt)

对于任意一行,留下的部分是区间 [ l , r ] [l,r] [l,r] 的概率为 f t , p ( l − 1 ) × f t , p ( M − r ) f_{t,p}(l-1)\times f_{t,p}(M-r) ft,p(l1)×ft,p(Mr)

考虑一个直观的 O ( N M 2 ) O(NM^2) O(NM2) 的动态规划做法,记 d p i , j , k dp_{i,j,k} dpi,j,k 表示前 i i i 行连通,并且第 i i i 行留下的区间为 [ j , k ] [j,k] [j,k] 的概率,记 p r e i , x = ∑ j = 1 x ∑ k = j x d p i , j , k , s u f i , x = ∑ j = x M ∑ k = j M d p i , j , k pre_{i,x}=\sum_{j=1}^{x}\sum_{k=j}^{x}dp_{i,j,k},suf_{i,x}=\sum_{j=x}^{M}\sum_{k=j}^{M}dp_{i,j,k} prei,x=j=1xk=jxdpi,j,k,sufi,x=j=xMk=jMdpi,j,k ,显然有转移
d p i , j , k = f t , p ( j − 1 ) × f t , p ( M − k ) × ( p r e i − 1 , M − p r e i − 1 , j − 1 − s u f i − 1 , k + 1 ) dp_{i,j,k}=f_{t,p}(j-1)\times f_{t,p}(M-k)\times (pre_{i-1,M}-pre_{i-1,j-1}-suf_{i-1,k+1}) dpi,j,k=ft,p(j1)×ft,p(Mk)×(prei1,Mprei1,j1sufi1,k+1)

我们发现转移时并不需要知道 d p i , j , k dp_{i,j,k} dpi,j,k ,只需要知道 p r e i , x pre_{i,x} prei,x s u f i , x suf_{i,x} sufi,x 即可。

考虑直接利用 p r e i , x pre_{i,x} prei,x s u f i , x suf_{i,x} sufi,x 进行动态规划,以计算 p r e i , x pre_{i,x} prei,x 为例,记 p r e i , x ′ = ∑ j = 1 x d p i , j , x pre'_{i,x}=\sum_{j=1}^{x}dp_{i,j,x} prei,x=j=1xdpi,j,x ,显然 p r e i , x pre_{i,x} prei,x 就是 p r e i , x ′ pre'_{i,x} prei,x 的前缀和。那么根据上面的计算式,有
p r e i , k ′ = ∑ j = 1 k f t , p ( j − 1 ) × f t , p ( M − k ) × ( p r e i − 1 , M − p r e i − 1 , j − 1 − s u f i − 1 , k + 1 ) pre'_{i,k}=\sum_{j=1}^{k}f_{t,p}(j-1)\times f_{t,p}(M-k)\times (pre_{i-1,M}-pre_{i-1,j-1}-suf_{i-1,k+1}) prei,k=j=1kft,p(j1)×ft,p(Mk)×(prei1,Mprei1,j1sufi1,k+1)

可以发现,若将括号拆开,每一项都可以表示为只和 i , k i,k i,k 相关的一个量乘以只和 i , j i,j i,j 相关的一个量,可以通过部分和进行优化。

时间复杂度为 O ( N M + K ) O(NM+K) O(NM+K)

#include
using namespace std;
const int MAXM = 2e5 + 5;
const int MAXN = 3005;
const int P = 1e9 + 7;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template  void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template  void writeln(T x) {
	write(x);
	puts("");
}
int pre[2][MAXN], suf[2][MAXN];
int n, m, p, q, t, lp[MAXN], rp[MAXN];
int fac[MAXM], inv[MAXM], powp[MAXM], powq[MAXM];
void update(int &x, int y) {
	x = (x + y >= P) ? (x + y - P) : (x + y);
}
void getdp() {
	static int sumlp[MAXN], sumrp[MAXN];
	for (int i = 1; i <= m; i++) {
		sumlp[i] = (sumlp[i - 1] + lp[i]) % P;
		pre[1][i] = 1ll * sumlp[i] * rp[i] % P;
		update(pre[1][i], pre[1][i - 1]);
	}
	for (int i = m; i >= 1; i--) {
		sumrp[i] = (sumrp[i + 1] + rp[i]) % P;
		suf[1][i] = 1ll * sumrp[i] * lp[i] % P;
		update(suf[1][i], suf[1][i + 1]);
	}
	for (int i = 2, now = 0, from = 1; i <= n; i++, swap(now, from)) {
		int tmp = 0;
		for (int j = 1; j <= m; j++) {
			pre[now][j] = 1ll * sumlp[j] * pre[from][m] % P;
			update(tmp, 1ll * pre[from][j - 1] * lp[j] % P);
			update(pre[now][j], P - tmp);
			update(pre[now][j], P - 1ll * sumlp[j] * suf[from][j + 1] % P);
			pre[now][j] = 1ll * pre[now][j] * rp[j] % P;
			update(pre[now][j], pre[now][j - 1]);
		}
		tmp = 0;
		for (int j = m; j >= 1; j--) {
			suf[now][j] = 1ll * sumrp[j] * pre[from][m] % P;
			update(tmp, 1ll * suf[from][j + 1] * rp[j] % P);
			update(suf[now][j], P - tmp);
			update(suf[now][j], P - 1ll * sumrp[j] * pre[from][j - 1] % P);
			suf[now][j] = 1ll * suf[now][j] * lp[j] % P;
			update(suf[now][j], suf[now][j + 1]);
		}
	}
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int getc(int x, int y) {
	if (y > x) return 0;
	else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
int getp(int x) {
	return 1ll * getc(t, x) * powp[x] % P * powq[t - x] % P;
}
void init(int n) {
	fac[0] = powp[0] = powq[0] = 1;
	for (int i = 1; i <= n; i++) {
		fac[i] = 1ll * fac[i - 1] * i % P;
		powp[i] = 1ll * powp[i - 1] * p % P;
		powq[i] = 1ll * powq[i - 1] * q % P;
	}
	inv[n] = power(fac[n], P - 2);
	for (int i = n - 1; i >= 0; i--)
		inv[i] = inv[i + 1] * (i + 1ll) % P;
	for (int i = 1; i <= m; i++) {
		lp[i] = getp(i - 1);
		rp[i] = getp(m - i);
	}
}
int main() {
	read(n), read(m);
	read(p), read(q);
	p = 1ll * p * power(q, P - 2) % P;
	q = (P + 1 - p) % P;
	read(t), init(t), getdp();
	printf("%d\n", pre[n & 1][m]);
	return 0;
}

你可能感兴趣的:(【OJ】CodeForces,【类型】做题记录)