USACO 2017 FEB Platinum nocross DP

时间:2021-03-21 07:37:15

  题目大意

    上下有两个长度为n、位置对应的序列A、B,其中数的范围均为1~n。若abs(A[i]-B[j]) <= 4,则A[i]与B[j]间可以连一条边。现要求在边与边不相交的情况下的最大的连边数量。n <= 10^5。

  在Gold里,此题的数据范围是1000,我们完全可以用简单的最长公共连续子序列的DP方法来做。

  范围大了之后,可以观察到对于一个数A[i],它所能转移的状态最多只有9个,那么就可以顺序扫描A数组,设F[i][j]表示当前连得最后一条边为(A[i],B[to[i][j]])的最优解。to[i][j]即A[i]能转移到的B[i]的位置(顺序从小到大)。建立一棵线段树,表示最后连的边中的数B在B数组的位置时,所能得到的最优解。F[i][j]就可以直接logn查询,logn把F[i][j]更新到线段树中。

  

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream> using namespace std; const int maxn = ;
int n, a[maxn], b[maxn];
int f[maxn][], cnt[maxn], to[maxn];
int adj[maxn][];
struct Tree
{
int maxv[maxn*];
Tree()
{
memset(maxv, , sizeof(maxv));
}
void pushup(int rt)
{
maxv[rt] = max(maxv[rt<<], maxv[(rt<<)+]);
}
void update(int rt, int l, int r, int p, int d)
{
if (l == r)
{
maxv[rt] = max(maxv[rt], d);
return ;
}
int mid = (l+r)>>;
if (p <= mid)
update(rt<<, l, mid, p, d);
else
update((rt<<)+, mid+, r, p, d);
pushup(rt);
}
int query(int rt, int l, int r, int L, int R)
{
if (L <= l && r <= R)
return maxv[rt];
int mid = (l+r)>>, ret = ;
if (L <= mid)
ret = max(ret, query(rt<<, l, mid, L, R));
if (R > mid)
ret = max(ret, query((rt<<)+, mid+, r, L, R));
return ret;
}
}T; int main()
{
freopen("nocross.in", "r", stdin);
freopen("nocross.out", "w", stdout);
scanf("%d", &n);
for (int i = ; i <= n; ++i)
scanf("%d", &a[i]);
for (int i = ; i <= n; ++i)
scanf("%d", &b[i]), to[b[i]] = i;
for (int i = ; i <= n; ++i)
{
int l = a[i]-, r = a[i]+;
if (l < )
l = ;
if (r > n)
r = n;
cnt[i] = ;
for (int j = l; j <= r; ++j)
adj[i][++cnt[i]] = to[j];
sort(adj[i]+, adj[i]+cnt[i]+);
}
for (int i = ; i <= n; ++i)
{
for (int j = ; j <= cnt[i]; ++j)
if (adj[i][j]- >= )
f[i][j] = T.query(, , n, , adj[i][j]-)+;
else
f[i][j] = ;
for (int j = ; j <= cnt[i]; ++j)
if (adj[i][j]- >= )
T.update(, , n, adj[i][j], f[i][j]);
}
printf("%d\n", T.maxv[]);
return ;
}