Loading

【笔记】USACO22JAN

My Blog

T1

直接从 \(1\)\(n\) 枚举每一位能填的最小数,可以做到 \(\mathcal{O}(N^2)\) 复杂度。

这本质上是求满足当前数小于前缀最大数的位置,考虑线段树,支持单点修改和整体查询。线段树每个节点维护两个 set,\(s\)\(t\),分别表示区间中可用的位置集合,与区间中候选的位置集合。均摊复杂度 \(\mathcal{O}(N\log ^2 N)\)

#define N 100005
int n, k, u[N];
struct node{
	int l, r, mn, mx;
	set<Pr>s, t;
}a[N << 2];
#define ls (x << 1)
#define rs (ls | 1)
#define L a[x].l
#define R a[x].r
void ins(int x,Pr y){
	a[x].s.insert(y);
	while(!(x & 1))x >>= 1, a[x].s.insert(y);
	if(x != 1)a[x >> 1].t.insert(y);
}
void upd(int x){
	a[x].mn = min(a[ls].mn, a[rs].mn);
	a[x].mx = max(a[ls].mx, a[rs].mx);
	vector<Pr>dl;
	int l = a[ls].mx - k, r = a[ls].mn + k;
	auto cur = a[x].t.lower_bound(mp(l, 0));
	while(cur != a[x].t.end()){
		if((*cur).fi > r)break;
		dl.pb(*cur), cur++;
	}
	go(y, dl)a[x].t.erase(y), ins(x, y);
}
void build(int x,int l,int r){
	L = l, R = r;
	if(l == r){
		a[x].mx = a[x].mn = u[l], ins(x, mp(u[l], l));
	}
	else{
		int mid = (l + r) >> 1;
		build(ls, l, mid), 
		build(rs, mid + 1, r),
		upd(x);
	}
}
void del(int x,int pos){
	a[x].s.erase(mp(u[pos], pos));
	if(L == R)a[x].mn = inf / 2, a[x].mx = 0;
	else{
		int mid = (L + R) >> 1;
		if(mid >= pos)del(ls, pos);
		else del(rs, pos);
		upd(x);
	}
}
int main() {
	read(n), read(k);
	rp(i, n)read(u[i]);
	build(1, 1, n);
	rp(i, n){
		int p = (*a[1].s.begin()).se;
		printf("%d\n", u[p]), del(1, p);
	}
	return 0;
}

T2

对于排列中的两个位置 \(i,j\),如果 \(|h_i - h_j| \ge 2\),那么这两个数的相对位置永远不会改变。

考虑 \(h_i\le 3\) 的情况,我们将 \(1,3\) 先排好,\(2\) 随便填,答案就是 \(\binom{n}{size_2}\)

再考虑 \(h_i\le 4\) 的情况,我们将 \(1,3\) 排好,\(2,4\) 排好,得到两个序列,那么答案就是归并两个序列的方案,使得 \(1,4\) 之间的相对位置不变。这我们只用对每个 \(1,4\) 记录在另一个序列中的可行区间即可。

扩展到任意情况,我们将 \(h_i\) 为偶数和奇数的位分开,得到两个序列,然后类似上述方法归并。对于每个数记录在另一个序列中的可行区间即可。

DP 的状态为 \(f_{i,j}\) 表示偶数序列匹配到了第 \(i\) 位,奇数序列第 \(j\) 位的方案数,时间复杂度 \(\mathcal{O}(N^2)\)

#define N 5005
int n, u[N], a[N], b[N], mat[N], c[N], d[N], f[N][N];
void solve(){
	read(n);
	int l = 0, r = 0;
	rp(i, n){
		read(u[i]);
		if(1 & u[i])a[++l] = i, mat[i] = l;
		else b[++r] = i, mat[i] = r;
	}
	mat[n + 1] = n;
	rp(i, n){
		int j = i;
		while(j && ((!(1 & (u[i] ^ u[j]))) || abs(u[i] - u[j]) <= 1))j--;
		c[i] = mat[j];
	}
	pr(i, n){
		int j = i;
		while(j <= n && (!(1 & (u[i] ^ u[j])) || abs(u[i] - u[j]) <= 1))j ++;
		d[i] = mat[j];
	}
	rep(i, 0, l)rep(j, 0, r)f[i][j] = 0;
	f[0][0] = 1;
	rep(i, 0, l)rep(j, 0, r){
		if(i + j == n)printf("%d\n", f[i][j]);
		else{
			if(i < l && c[a[i + 1]] <= j && j <= d[a[i + 1]])ad(f[i + 1][j], f[i][j]);
			if(j < r && c[b[j + 1]] <= i && i <= d[b[j + 1]])ad(f[i][j + 1], f[i][j]);
		}
	}
}

int main() {
	int T; read(T);
	while(T--)solve();
	return 0;
}

T3

考虑计算出答案向量的方向。

假设方向为 \(x\),那么对于每一组,肯定选择在该方向上投影长度最大的向量。

直接以 \(1e-3\) 精度枚举方向可以过 Sub1。

我们记以 \(x\) 为方向算出的答案为 \(f(x)\)\(f(x)\) 大概是几个三角函数叠加取 \(\max\),分几段三分一下可以过 Sub2。

Sub3 不会,回头填。

Updata:事实上,这个题本质上就是求闵可夫斯基和。

根据闵可夫斯基和的定义,\(C=\{a+b|a\in A,b\in B\}\),所以我们对每组向量求凸包,然后直接求闵可夫斯基和即可。

(又被卡科技了

posted @ 2022-02-02 13:52  7KByte  阅读(91)  评论(0编辑  收藏  举报