题目大意
上下有两个长度为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 = 100005; int n, a[maxn], b[maxn]; int f[maxn][12], cnt[maxn], to[maxn]; int adj[maxn][12]; struct Tree { int maxv[maxn*4]; Tree() { memset(maxv, 0, sizeof(maxv)); } void pushup(int rt) { maxv[rt] = max(maxv[rt<<1], maxv[(rt<<1)+1]); } 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)>>1; if (p <= mid) update(rt<<1, l, mid, p, d); else update((rt<<1)+1, mid+1, 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)>>1, ret = 0; if (L <= mid) ret = max(ret, query(rt<<1, l, mid, L, R)); if (R > mid) ret = max(ret, query((rt<<1)+1, mid+1, r, L, R)); return ret; } }T; int main() { freopen("nocross.in", "r", stdin); freopen("nocross.out", "w", stdout); scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); for (int i = 1; i <= n; ++i) scanf("%d", &b[i]), to[b[i]] = i; for (int i = 1; i <= n; ++i) { int l = a[i]-4, r = a[i]+4; if (l < 0) l = 1; if (r > n) r = n; cnt[i] = 0; for (int j = l; j <= r; ++j) adj[i][++cnt[i]] = to[j]; sort(adj[i]+1, adj[i]+cnt[i]+1); } for (int i = 1; i <= n; ++i) { for (int j = 1; j <= cnt[i]; ++j) if (adj[i][j]-1 >= 1) f[i][j] = T.query(1, 1, n, 1, adj[i][j]-1)+1; else f[i][j] = 0; for (int j = 1; j <= cnt[i]; ++j) if (adj[i][j]-1 >= 1) T.update(1, 1, n, adj[i][j], f[i][j]); } printf("%d ", T.maxv[1]); return 0; }