题目链接:ZOJ-4028 LIS
题意
有一个长度为 (n (1le n le 10^5)) 的序列 (a),对于所有的 (iin [1,n]) ,已知 (f_i, l_i, r_i) ,代表序列 (a) 中末尾元素为 (a_i) 的最长上升子序列长度为 (f_i) ,且 (l_i le a_i le r_i) ,求序列 (a) 。
思路
找出 (a_i) 的所有约束条件:
- 记 (i') 为 (i) 前面满足 (f_{i'}=f_i) 的离 (i) 最近的位置,(a_i) 的值需要满足 (a_i le a_{i'}) ;
- 记 (i'') 为 (i) 前面满足 (f_{i''}+1=f_i) 的离 (i) 最近的位置,(a_i) 的值需要满足 (a_i>a_{i''}) ;
- (l_ile a_ile r_i) 。
知道了每个 (a_i) 的约束条件,可以用差分约束求解(用 (e(u,v,w)) 代表从 (u) 到 (v) 权值为 (w) 的有向边):
- (a_ile a_{i'}) 可变形为 (a_ile a_{i'}+0) ,连边 (e(i',i,0)) ;
- (a_i>a_{i''}) 可变形为 (a_{i''}le a_i-1) ,连边 (e(i,i'',-1)) ;
- 新增一个源点 (s),令 (a_s=0) 。(l_ile a_ile r_i) 可变形为 (a_sle a_i-l_i, a_ile a_s+r_i) ,连边 (e(i,s,-l_i), e(s,i,r_i)) 。
建完图跑差分约束即可。
代码实现
#include <cstdio>
#include <cstring>
#include <queue>
using std::queue;
typedef long long LL;
const int maxn = 100010;
int head[maxn], tot;
LL dist[maxn];
int f[maxn], last[maxn];
bool inq[maxn];
struct Edge{
int to, nex, val;
} edge[maxn<<2];
void add_edge(int u, int v, int w) {
edge[++tot].nex = head[u];
edge[tot].to = v;
edge[tot].val = w;
head[u] = tot;
}
void bfs_spfa(int s, int n) {
memset(inq, 0, sizeof(bool) * (n+5));
memset(dist, 0x3f, sizeof(LL) * (n+5));
queue<int> que;
que.push(s);
inq[s] = true;
dist[s] = 0;
while (!que.empty()) {
int u = que.front();
que.pop();
inq[u] = false;
for (int i = head[u]; i; i = edge[i].nex) {
int v = edge[i].to;
if (dist[v] > dist[u] + edge[i].val) {
dist[v] = dist[u] + edge[i].val;
if (!inq[v]) {
inq[v] = true;
que.push(v);
}
}
}
}
for (int i = 1; i <= n; i++) printf("%lld%c", dist[i], "
"[i==n]);
}
int main() {
int T, n;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
memset(last, 0, sizeof(int) * (n+5));
memset(head, 0, sizeof(int) * (n+5));
tot = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", &f[i]);
if (last[f[i]]) add_edge(last[f[i]], i, 0);
if (f[i] > 1) add_edge(i, last[f[i]-1], -1);
last[f[i]] = i;
}
for (int i = 1, l, r; i <= n; i++) {
scanf("%d %d", &l, &r);
add_edge(0, i, r);
add_edge(i, 0, -l);
}
bfs_spfa(0, n);
}
return 0;
}