T1:有点 boring
T2:挺好的一道题
题意:给一个序列,你可以任意删数,问最后
a
i
=
i
a_i=i
ai=i 的个数最大是多少
考虑转换题意,变成选出最多的
a
i
a_i
ai出来,使得每一个
a
i
a_i
ai 在第
i
i
i 位
然后我就
n
a
i
v
e
naive
naive 的想这不是
L
I
S
LIS
LIS吗,成功 wa 掉大样例
然后发现,当
a
i
>
i
a_i>i
ai>i 时,它一定不会回到第
i
i
i 位,可以直接甩掉
写了一发,成功 wa 掉大样例
接着写了个暴力对拍,发现出了 bug,像
.
.
.
35...
...35...
...35... 这样的序列,3 和 5 是不能同时选的
于是就有了
n
2
n^2
n2 的暴力
d
p
dp
dp
f
i
=
m
a
x
(
f
j
)
+
1
(
i
<
j
,
a
j
<
a
i
,
i
−
j
≥
a
i
−
a
j
)
f_i=max(f_j)+1(i<j,a_j<a_i,i-j\ge a_i-a_j)
fi=max(fj)+1(i<j,aj<ai,i−j≥ai−aj)
把第二个移一下项,
i
−
a
i
≤
j
−
a
j
i-a_i\le j-a_j
i−ai≤j−aj
把
a
i
,
i
−
a
i
a_i,i-a_i
ai,i−ai作为点,就是一个二维平面的
m
a
x
max
max,树套树随便做
打了一半,发现空间要炸,改成了
c
d
q
cdq
cdq
用
[
l
,
m
i
d
]
[l,mid]
[l,mid] 的 dp 值去更新
(
m
i
d
,
r
]
(mid,r]
(mid,r] 的 dp 值
把
[
l
,
m
i
d
]
[l,mid]
[l,mid] 的按 x 排序,
(
m
i
d
,
r
]
(mid,r]
(mid,r] 按 x 排序,指针扫 x,y 用树状数组查
等等??怎么回退树状数组
于是改成了线段树,暴力打
t
a
g
tag
tag 清空
#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
cs int N = 1e5 + 5;
typedef long long ll;
int n, tot, f[N], a[N];
// 转移条件:a[from] < a[i] && from-a[from] < i-a[i]
struct data{
int id, x, y;
} x[N], tpl[N], tpr[N]; // tmp
bool cmpx(data a, data b){ return a.x < b.x;}
struct Segmentree{
int mx[N << 2], cov[N << 2];
void pushcov(int x){ mx[x] = -1; cov[x] = 1;}
void pushup(int x){ mx[x] = max(mx[x<<1], mx[x<<1|1]);}
void pushdown(int x){ if(cov[x]) pushcov(x<<1), pushcov(x<<1|1), cov[x] = 0;}
void modify(int x, int l, int r, int p, int v){
if(l == r){ mx[x] = max(mx[x], v); return;}
pushdown(x); int mid = (l+r) >> 1;
if(p <= mid) modify(x<<1, l, mid, p, v);
else modify(x<<1|1, mid+1, r, p, v);
pushup(x);
}
int query(int x, int l, int r, int L, int R){
if(L<=l && r<=R) return mx[x];
pushdown(x); int mid = (l+r) >> 1, ans = 0;
if(L<=mid) ans = max(ans, query(x<<1, l, mid, L, R));
if(R>mid) ans = max(ans, query(x<<1|1, mid+1, r, L, R));
return ans;
}
void add(int p, int v){ modify(1, 1, n, p, v); }
int ask(int p){ return query(1, 1, n, 1, p);}
}T;
// cdq
void cdq(int l, int r){
if(l == r){ f[l] = max(f[l], 1); return;}
int mid = (l+r) >> 1;
cdq(l, mid);
int ret = 0;
for(int i = l; i <= mid; i++) tpl[++ret] = x[i];
sort(tpl+1, tpl+ret+1, cmpx);
int res = 0;
for(int i = mid+1; i <= r; i++) tpr[++res] = x[i];
sort(tpr+1, tpr+res+1, cmpx);
int fuk = 1; // 双指针
for(int i = 1; i <= res; i++){
while(fuk <= ret && tpl[fuk].x < tpr[i].x) T.add(tpl[fuk].y, f[tpl[fuk].id]), fuk++;
f[tpr[i].id] = max(f[tpr[i].id], T.ask(tpr[i].y) + 1);
}
T.pushcov(1);
cdq(mid+1, r);
}
int main(){
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
n = read();
for(int i = 1; i <= n; i++){
int x = read(); a[i] = x;
}
for(int i = 1; i <= n; i++){
if(a[i] > i) continue; tot++;
x[tot] = (data){tot, a[i], i - a[i] + 1};
}
cdq(1, tot);
int ans = 0;
for(int i = 1; i <= tot; i++) ans = max(ans, f[i]);
cout << ans;
return 0;
}
正解:考虑到是
C
S
P
−
S
CSP-S
CSP−S 模拟,不会考三维偏序
如果有
a
i
>
a
j
a_i>a_j
ai>aj,
i
−
a
i
≥
j
−
a
j
i-a_i\ge j-a_j
i−ai≥j−aj
那么一定有
i
>
j
i>j
i>j
于是转移变成
f
i
=
m
a
x
(
f
j
)
+
1
(
a
j
<
a
i
,
i
−
a
i
≥
j
−
a
j
)
f_i=max(f_j)+1(a_j<a_i,i-a_i\ge j-a_j)
fi=max(fj)+1(aj<ai,i−ai≥j−aj)
按
a
i
a_i
ai 排序,树状数组查
i
−
a
i
i-a_i
i−ai 的前缀
m
a
x
max
max即可
反思:因为稍微多学了一点东西,凭运气过了
以后还是要仔细探究题目条件,去除无用条件,化简问题
#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
cs int N = 1e5 + 5;
typedef long long ll;
int n, f[N], c[N]; vector<int> v[N];
void add(int x, int v){ for(;x<=n;x+=x&-x) c[x] = max(c[x],v); }
int ask(int x){ int ans=0; for(;x;x-=x&-x) ans=max(ans,c[x]); return ans;}
int main(){
n = read();
for(int i = 1; i <= n; i++){ int x = read(); if(x > i) continue; v[x].push_back(i - x + 1);}
int idx = 0;
for(int i = 1; i <= n; i++){
int np = idx; for(int j = 0; j < v[i].size(); j++) f[++np] = ask(v[i][j]) + 1;
np = idx; for(int j = 0; j < v[i].size(); j++) add(v[i][j], f[++np]);
idx = np;
} int ans = 0; for(int i = 1; i <= idx; i++) ans = max(ans, f[i]); cout << ans;
return 0;
}
T3:
考场暴力线段树优化建图得了 90 分,还是说一说这个不是特别优秀的做法
考虑如果对
x
,
y
x,y
x,y 分别考虑,最短路一定是取近的那一个,并不需要关系
m
i
n
min
min 的问题
考虑将一个点的
x
i
x_i
xi,向所有
x
j
≤
x
i
x_j\le x_i
xj≤xi 的点连一条
x
i
−
x
j
x_i-x_j
xi−xj 的边,向所有
x
j
>
x
i
x_j>x_i
xj>xi 的点连一条
x
j
−
x
i
x_j-x_i
xj−xi 的边,发现边权不统一,无法线段树建图
然后考虑到对
x
i
,
x
j
x_i,x_j
xi,xj 分别考虑,开两棵线段树,一个的最底层向
i
i
i 连
−
x
i
-x_i
−xi 的边权
一棵向
i
i
i 连
x
i
x_i
xi 的边权,然后建图的时候先向第一棵线段树区间
[
1
,
x
i
]
[1,x_i]
[1,xi]的点连
x
i
x_i
xi 的边
再向
[
x
i
+
1
,
n
]
[x_i+1,n]
[xi+1,n] 连
−
x
i
-x_i
−xi 的边,与原问题等价
然后 y 同理,x 和 y 的线段树底层都连向原来的
i
i
i 点,这样就把x,y 串通起来了
由于有负边,强行
s
p
f
a
spfa
spfa
#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
cs int K = 2e5 + 5, N = 3e6 + 5, M = 1e7 + 5;
typedef long long ll;
int n, x[K], y[K], bx[K], by[K], node, nx[K], ny[K];
int flg1 = 1; // for xi = yi, 5 pts
int flg2 = 1; // for xi, yi, xi < xj if(i < j) 35 pts
void FSY(){ cout << min(abs(x[n] - x[1]), abs(y[n] - y[1])); } // xi = yi
void Yolanda(){
int ans = 0;
for(int i = 1; i < n; i++) ans += min(abs(x[i+1] - x[i]), abs(y[i+1] - y[i]));
cout << ans;
}
int first[N], nxt[M], to[M], w[M], tot;
void add(int x, int y, int z){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y, w[tot] = z;}
ll dis[N]; bool vis[N];
struct Segmentree{
int nd[N];
#define mid ((l+r)>>1)
void modify(int x, int l, int r, int p, int v, int id){
if(!nd[x]) nd[x] = ++node;
if(l == r){ add(nd[x], id, v); return; }
if(p <= mid) modify(x<<1, l, mid, p, v, id);
else modify(x<<1|1, mid+1, r, p, v, id);
}
void build(int x, int l, int r){
if(!nd[x]) return;
if(l == r) return;
build(x<<1, l, mid); build(x<<1|1, mid+1, r);
add(nd[x], nd[x << 1], 0); add(nd[x], nd[x << 1|1], 0);
}
void Adde(int x, int l, int r, int L, int R, int v, int p){
if(L<=l && r<=R){ add(p, nd[x], v); return; }
if(L<=mid) Adde(x<<1, l, mid, L, R, v, p);
if(R>mid) Adde(x<<1|1, mid+1, r, L, R, v, p);
}
#undef mid
}T[4];
void spfa(){
memset(dis, 0x3f, sizeof(dis));
queue<int> q; dis[1] = 0; q.push(1);
while(!q.empty()){
int x = q.front(); q.pop(); vis[x] = 0;
for(int i = first[x]; i; i = nxt[i]){
int t = to[i]; if(dis[t] > dis[x] + w[i]){
dis[t] = dis[x] + w[i]; if(!vis[t]) vis[t] = 1, q.push(t);
}
}
} cout << dis[n];
}
int main(){
freopen("shortest.in","r",stdin);
freopen("shortest.out","w",stdout);
n = node = read();
int prex = -1, prey = -1;
for(int i = 1; i <= n; i++){
x[i] = bx[i] = read(); y[i] = by[i] = read();
if(x[i] ^ y[i]) flg1 = 0;
if(x[i] < prex || y[i] < prey) flg2 = 0;
prex = x[i]; prey = y[i];
}
if(flg1){ FSY(); return 0;}
if(flg2){ Yolanda(); return 0;}
sort(bx + 1, bx + n + 1);
sort(by + 1, by + n + 1);
int sx = unique(bx + 1, bx + n + 1) - (bx + 1);
int sy = unique(by + 1, by + n + 1) - (by + 1);
for(int i = 1; i <= n; i++){
nx[i] = lower_bound(bx + 1, bx + sx + 1, x[i]) - bx;
ny[i] = lower_bound(by + 1, by + sy + 1, y[i]) - by;
T[0].modify(1, 1, n, nx[i], x[i], i);
T[1].modify(1, 1, n, nx[i], -x[i], i);
T[2].modify(1, 1, n, ny[i], y[i], i);
T[3].modify(1, 1, n, ny[i], -y[i], i);
} for(int i = 0; i < 4; i++) T[i].build(1, 1, n);
for(int i = 1; i <= n; i++){
T[1].Adde(1, 1, n, 1, nx[i], x[i], i);
T[0].Adde(1, 1, n, nx[i] + 1, n, -x[i], i);
T[3].Adde(1, 1, n, 1, ny[i], y[i], i);
T[2].Adde(1, 1, n, ny[i] + 1, n, -y[i], i);
} spfa(); return 0;
}
正解:考虑到
(
x
i
,
y
i
)
(x_i,y_i)
(xi,yi) 走到
(
x
j
,
y
j
)
(x_j,y_j)
(xj,yj),一定可以找到一条路径是以以下方式存在的
选一个
x
x
x 最近的点,走过去,选一个
y
y
y 最近的点,走过去,再选一个
x
x
x 最近的点,走过去…
你可能会想,如果我选的不是
x
x
x 最近的点,而选的是第二近的点走过去,万一更优呢?
你能更优吗?x 先走到离它最近的点,再从那个点走到下一个离它最近的点,不就与 x 走到离它第二近的点等价吗
于是先按
x
x
x 排序,相邻连边,再按
y
y
y 排序,相邻连边,最短路即可
#include<bits/stdc++.h>
#define cs const
using namespace std;
int read(){
int cnt = 0, f = 1; char ch = 0;
while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
return cnt * f;
}
cs int N = 2e5 + 5, M = 1e6 + 5;
typedef long long ll;
int n;
struct Node{
int x, y, id;
}a[N];
bool cmpx(Node a, Node b){ return a.x < b.x;}
bool cmpy(Node a, Node b){ return a.y < b.y;}
int first[N], nxt[M], to[M], w[M], tot;
void add(int x, int y, int z){
nxt[++tot] = first[x], first[x] = tot, to[tot] = y, w[tot] = z;
nxt[++tot] = first[y], first[y] = tot, to[tot] = x, w[tot] = z;
}
ll dis[N]; bool vis[N];
void spfa(){
memset(dis, 0x3f, sizeof(dis));
queue<int> q; dis[1] = 0; q.push(1);
while(!q.empty()){
int x = q.front(); q.pop(); vis[x] = 0;
for(int i = first[x]; i; i = nxt[i]){
int t = to[i]; if(dis[t] > dis[x] + w[i]){
dis[t] = dis[x] + w[i]; if(!vis[t]) vis[t] = 1, q.push(t);
}
}
} cout << dis[n];
}
int main(){
n = read();
for(int i = 1; i <= n; i++) a[i].x = read(), a[i].y = read(), a[i].id = i;
sort(a+1, a+n+1, cmpx);
for(int i = 1; i < n; i++) add(a[i].id, a[i+1].id, a[i+1].x - a[i].x);
sort(a+1, a+n+1, cmpy);
for(int i = 1; i < n; i++) add(a[i].id, a[i+1].id, a[i+1].y - a[i].y);
spfa(); return 0;
}
正解之外的收获:from gigo
考虑到
x
,
y
x,y
x,y 如果差得太远,不会最优,于是我们就排序,相邻的 10 个点连一下边
看似是乱搞,其实很有水平,值得学习