题解:USACO 2021 OPEN Gold

T1 - United Cows of Farmer John

先维护一下每个元素前面和后面第一个相同的位置,分别记作 \(pre_i\)\(nxt_i\)

那么,一个区间 \([l, r]\) 满足题意的充要条件就是 \(nxt_l > r \And pre_r < l\)

考虑枚举左端点,用一个树状数组维护右端点的个数。

从左到右枚举 \(l\) , 对于每个 \(pre_r < l\)\(r\) ,将 \(r\) 这一位置在树状数组上加一,这一部分用双指针维护。

每次的答案就是树状数组上区间 \([l, nxt_l]\) 上的和。

时间复杂度 \(\mathcal O (n\log n)\)

int n, b[N], pre[N], nxt[N], ton[N], id[N];
inline bool cmp(int A, int B) {return pre[A] < pre[B];}
struct BIT {
	int val[N], A, R;
	inline void upd(int p, int k) {while(p <= R) val[p] += k, p += p & -p;}
	inline int qry(int p) {A = 0; while(p) A += val[p], p -= p & -p; return A;}
} ZT;
int main() {
	Rdn(n);
	forn(i,1,n) Rdn(b[i]), id[i] = i;
	forn(i,1,n) pre[i] = ton[b[i]], ton[b[i]] = i;
	forn(i,1,n) ton[i] = n + 1;
	form(i,n,1) nxt[i] = ton[b[i]], ton[b[i]] = i;
	ZT.R = n + 1;
	sort(id + 1, id + n + 1, cmp);
	int r = 1; i64 res = 0;
	forn(l,1,n) {
		while(r <= n && pre[id[r]] < l) ZT.upd(id[r], 1), ++r;
		res += ZT.qry(nxt[l]) - ZT.qry(l - 1) - 1; 
	}
	Wtn(res, '\n'); flush();
	return 0;
}

T2 - Portals

发现每个位置都能互相达到等价于每个传送门都能互相达到,然后发现这些传送门互相形成了很多个环状物。

考虑每次重新排列操作的影响,因为对于四个已经互相链接的传送门操作没有意义,不讨论。

发现重新排列后,相当于将两个小环合并成了一个大环,那么可以将这些环看成点,跑一遍类似于最小生成树的东西就好了。

struct dsu {
	int fa[N << 1];
	inline void init(int R) {forn(i,1,R) fa[i] = i;}
	int fnd(int u) {return fa[u] == u ? u : fa[u] = fnd(fa[u]);}
	inline bool Mrg(int u, int v) {
		u = fnd(u), v = fnd(v);
		if(u == v) return 0;
		return fa[v] = u, 1;
	}
} O; 
int n, p[4][N], c[N];
int u[M], v[M], w[M], tot, id[M];
inline bool cmp(int A, int B) {return w[A] < w[B];}
int main() {
	Rdn(n); O.init(n << 1);
	forn(i,1,n) {
		Rdn(c[i], p[0][i], p[1][i], p[2][i], p[3][i]);
		O.Mrg(p[0][i], p[1][i]), O.Mrg(p[2][i], p[3][i]);
		u[++tot] = p[0][i], v[tot] = p[2][i], w[tot] = c[i];
		id[tot] = tot;
	}
	sort(id + 1, id + tot + 1, cmp);
	int res = 0;
	forn(i,1,n) if(O.Mrg(u[id[i]], v[id[i]])) res += w[id[i]];
	Wtn(res, '\n'); flush();
	return 0;
}

T3 - Permutation

手膜发现每次形成的东西必定是一个大三角形里面装了很多个小三角形。

发现这个 \(N\) 的数据范围就是给 \(N ^ 5\) 的东西开的,直接设出 \(f(l, i, j, k)\) 表示长度为 \(l\) ,且构造出来的最外围的三角形的三个顶点分别是 \((i, j, k)\) 的合法排列个数。

然后发现,每次操作的转移,要么是外面的点新加进来,要么是加一个里面的点。

对于外面新加进来的点,先判断点 \(p\) 是否在三角形 \((i, j, k)\) 中,这一步可以用面积法解决,然后很自然的三个转移就出来了。

对于里面加的点,状态 \(f(l - 1, i, j, k)\) 中每个排列还剩下 \(sum - l + 4\) 个在三角形 \((i, j, k)\) 中的点没有被加入,其中 \(sum\) 表示在 \((i, j, k)\) 中的所有点多个数。

所以有转移:

\[f(l, i, j, k) \leftarrow f(l - 1, i, j, p) \\ \\ f(l, i, j, k) \leftarrow f(l - 1, i, k, p) \\ \\ f(l, i, j, k) \leftarrow f(l - 1, j, k, p) \\ \\ f(l, i, j, k) \leftarrow (sum - l + 1) \times f(l - 1, i, j, k) \\ \\ \]

对于每个相同三角形,为了状态表示不重复,所以在所有状态中,将 \((i, j, k)\) 升序排序。

于是就获得了一个自带 \(\frac{1}{6}\) 常数的 \(\mathcal O (N ^ 5)\) 的做法。

Mint dp[N][N][N][N]; int n, x[N], y[N];
inline i64 Area(int a, int b, int c) {
	return labs(1ll * (x[c] - x[a]) * (y[c] - y[b]) - 1ll * (x[c] - x[b]) * (y[c] - y[a]));
}
inline bool check(int a, int b, int c, int O) {
	return Area(a, b, c) == Area(a, b, O) + Area(a, c, O) + Area(b, c, O);
}
inline void QS(int& a, int& b, int& c) {
	if(a > b) swap(a, b);
	if(a > c) swap(a, c);
	if(b > c) swap(b, c);
}
int main() {
	Rdn(n);
	forn(i,1,n) Rdn(x[i], y[i]);
	forn(i,1,n) forn(j,i + 1,n) forn(k,j + 1,n)
		dp[3][i][j][k] = Mint(6);
	forn(l,4,n) forn(i,1,n) forn(j,i + 1,n) forn(k,j + 1,n) {
		int sum = 0;
		forn(p,1,n) if(p != i && p != j && p != k && check(i, j, k, p)) {
			static int x, y, z;
			x = i, y = j, z = p; QS(x, y, z);
			dp[l][i][j][k] += dp[l - 1][x][y][z];
			x = i, y = k, z = p; QS(x, y, z);
			dp[l][i][j][k] += dp[l - 1][x][y][z];
			x = j, y = k, z = p; QS(x, y, z);
			dp[l][i][j][k] += dp[l - 1][x][y][z];
			sum ++ ;
		}
		dp[l][i][j][k] += Mint(sum - l + 4) * dp[l - 1][i][j][k];
	}
	Mint res(0);
	forn(i,1,n) forn(j,i + 1,n) forn(k,j + 1,n) res += dp[n][i][j][k];
	Wtn(res.res, '\n'); flush();
	return 0;
}
posted @ 2021-09-12 21:25  AxDea  阅读(58)  评论(0编辑  收藏  举报