CSP-S 模拟 19/10/17

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,ijaiaj)
把第二个移一下项, i − a i ≤ j − a j i-a_i\le j-a_j iaijaj
a i , i − a i a_i,i-a_i ai,iai作为点,就是一个二维平面的 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 CSPS 模拟,不会考三维偏序
如果有 a i > a j a_i>a_j ai>aj i − a i ≥ j − a j i-a_i\ge j-a_j iaijaj
那么一定有 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,iaijaj)
a i a_i ai 排序,树状数组查 i − a i i-a_i iai 的前缀 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 xjxi 的点连一条 x i − x j x_i-x_j xixj 的边,向所有 x j > x i x_j>x_i xj>xi 的点连一条 x j − x i x_j-x_i xjxi 的边,发现边权不统一,无法线段树建图
然后考虑到对 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 个点连一下边
看似是乱搞,其实很有水平,值得学习

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值