【笔记】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\}\),所以我们对每组向量求凸包,然后直接求闵可夫斯基和即可。
(又被卡科技了