当前位置: 首页 > news >正文

题解:AT_ttpc2015_o 数列色ぬり -数形结合法

前言

数形结合百般好,隔离分家万事非。

——华罗庚

这是一篇用数形结合的思想解决的此问题的题解,也是 官方题解 中的“速い解法(快速解法)”(后面还有一个“速い解法その2”)。

题意

给定一个排列,将该排列染成红色或蓝色,红色的构成上子序列,蓝色的构成下降子序列。求最多能染多少个数。

解法

看到题目描述,很容易联想到最长上升子序列(LIS)。类似地,我们可以定义最长下降子序列(LDS)。显然,LIS 和 LDS 可能不只一个,但长度是唯一的。
为了方便,以下我们记 \(|LIS|\) 为 LIS 的长度,\(|LDS|\) 为 LDS 的长度。
首先一个很理想的情况,我们可以取 \(|LIS|+|LDS|\) 个元素。但分析第一个样例就发现不可以,因为 LIS 可能会与 LDS 有重复元素。
于是我们可以考虑所有 LIS 和 LDS 中至少重复的元素个数。可以证明,这样的元素至多有一个。

证明 假设 LIS 存在两个元素 $a_i,a_j (i\lt j)$,那么由 LIS 的定义, $a_i \lt a_j$,所以 LDS 中一定不可能同时存在 $a_i$ 和 $a_j$。

我们可以转换成一个判断性问题,即是否存在一个 LIS 和一个 LDS,使得 \(LIS \cap LDS = \varnothing\)
一个很自然的想法是将这个排列放到二维平面上,即得到 \((i,a_i)\) 这些点。
为了是问题更加直观,我们把所有 LIS 用红色的折线连接,所有的 LDS 用蓝色的折线连接,如图。
1
注本图为

10
7 4 2 3 9 6 5 8 1 10

以下我选择以样例一和样例三为例。(为什么?因为第一组的答案是 \(|LIS|+|LDS|-1\),第三组是 \(|LIS|+|LDS|\)
样例一
2
样例三
3

可以观察到,在样例一的图中红线和蓝线只在端点处相交,样例三的图在红线和蓝线在非端点“十字交叉”了(以下简称“十字交叉”,应该就是官方题解中的“strictに交差”吧)。
由“形”,我们大胆猜测如果答案为 \(|LIS|+|LDS|\),其充要条件为存在一条红线和蓝线“十字交叉”。这也很好理解,由定义可知,红线斜率为正,蓝线斜率为负,一个 LIS 对应一条红色的折线,一个 LDS 对应一条蓝色的折线。两条折线在某处“十字交叉”,那么其左边的红线上的点纵坐标都比它小,蓝线上的点纵坐标都比它大。右侧同理。
这样我们得到了一个 \(O(N^2)\) 的做法,用朴素的 \(O(N^2)\) 求 LIS 和 LDS,转移时标记一下可以从哪转移,如有多个可以转移的都记录一下。在枚举一下即可。(我没有写,可能实际复杂度会高一些)

考虑优化,以上做法慢的重要原因是总共可能牵出 \(O(N^2)\) 条线,这个我们就接受不了。我们需要减少线的数量。
由 LIS 和 LDS 的定义可知,在以某一条红线或蓝线为对角线,与 \(x\) 轴和 \(y\) 轴平行的矩形中不存在其他点,如下图。

4

因此,红线和蓝线相交等价于两个矩形相交(相切不算相交)。
而两个矩形相交只有如下两种情况:
5
进一步转化为两条线段(下图中的实线)相交。
6
这样一个点只会向两个方向牵出线段,我们只用保留最长的即可。这样总线段数就是 \(O(N)\)
为了方便实现,我们还是将样例一中的图画一下。
7
这种情况红线转移端点 \(\min{i}\),蓝线端点 \(\max{a_i}\)
8
这种情况红线转移端点 \(\max{a_i}\),蓝线端点 \(\max{i}\)
然后就是蓝线和红线是否相交的问题,因为所有线都与坐标轴平行,用扫描线和树状数组即可。
至于求 LIS,我用的是树状数组优化 DP。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
template<typename tp> inline void cmax(tp &a,tp b){(a<b)&&(a=b);}
template<typename tp> inline void cmin(tp &a,tp b){(a>b)&&(a=b);}
template<typename tp> inline tp pmin(tp a,tp b){return a<b?a:b;}
template<typename tp> inline tp pmax(tp a,tp b){return a>b?a:b;}
bool _a;
const int N=1e5+10;
int n;
int a[N];
int f[N],ffr[N];//LIS
int g[N],gfr[N];//LDS 
int rf[N],rfr[N],rg[N],rgr[N];
//反着求LIS 、 LDS。 
bool onlis[N],onlds[N];
class BIT{//DP转移用的树状数组 
private:pii tr[N];//第一关键字为DP值,第二关键字为下标pii merge(pii x,pii y){return x.first!=y.first?pmax(x,y):make_pair(x.first,pmin(x.second,y.second));}//dp 值不同取 dp较大的,相同取下标更小的 pii mg(pii &x,pii y){return x=merge(x,y);}
public:void init(int tt){fill(tr+1,tr+n+1,make_pair(0,tt));}void upd(int x,pii t){for(int i=x;i<N;i+=i&(-i)){mg(tr[i],t);}}pii qry(int x){pii t={0,0};for(int i=x;i;i-=i&(-i)){mg(t,tr[i]);}return t;}
};
BIT T;
struct line{//扫描线 int op,x,y1,y2;//查询形如{0,x,y1,y2}//修改形如{1,x,y,0} 和{-1,x,y,0} bool operator<(line o)const{if(x!=o.x) return x<o.x;else return op>o.op;//保证先增再查最后删,避免出错 } 
}ls[N*3];//一条红线对应两次修改,一条蓝线对应一次查询 
struct BIT2{//反正树状数组不难写,再写一遍int tr[N];void init(){memset(tr,0,sizeof tr);}void upd(int x,int t){for(int i=x;i<N;i+=i&(-i)){tr[i]+=t;}}int qry(int x){int t=0;for(int i=x;i;i-=i&(-i)){t+=tr[i];}return t;}
}T2;
int tot=0;
bool is_xiangjiao(){//判断相交的 sort(ls+1,ls+tot+1);T2.init();for(int i=1;i<=tot;i++){if(ls[i].op==0){int l=ls[i].y1,r=ls[i].y2;if(T2.qry(r)-T2.qry(l-1)>0) {return true;}}else{T2.upd(ls[i].y1,ls[i].op);}}return false;
}
void solve(){memset(onlis,0,sizeof onlis);memset(onlds,0,sizeof onlds);T.init(0);for(int i=1;i<=n;i++){//树状数组 pii res=T.qry(a[i]);f[i]=res.first+1;ffr[i]=res.second;T.upd(a[i],make_pair(f[i],i));}T.init(0);for(int i=1;i<=n;i++){pii res=T.qry(n-a[i]+1);g[i]=res.first+1;gfr[i]=-res.second;//很常见的办法吧,相反数的最小值 <=> 最大值的相反数 T.upd(n-a[i]+1,make_pair(g[i],-a[i]));}T.init(0);for(int i=n;i>=1;i--){pii res=T.qry(n-a[i]+1);rf[i]=res.first+1;rfr[i]=-res.second;T.upd(n-a[i]+1,make_pair(rf[i],-a[i]));//求后缀的LIS,一是为了判断点是否在LIS上,二是为例求处点后缀的 max{a_i},为下面扫描线做准备。 }T.init(0);for(int i=n;i>=1;i--){pii res=T.qry(a[i]);rg[i]=res.first+1;rgr[i]=-res.second;T.upd(a[i],make_pair(rg[i],-i));}int lis=0,lds=0;//lis,lds的长度 for(int i=1;i<=n;i++){cmax(lis,f[i]);cmax(lds,g[i]);}for(int i=1;i<=n;i++){if(f[i]+rf[i]-1==lis) onlis[i]=1;//判断是否 在LIS上,别忘了减去本身 if(g[i]+rg[i]-1==lds) onlds[i]=1;}//红线: (ffr[i],a[i]) - (i,a[i])//蓝线: (i,a[i]) - (i,gfr[i])//选定从左往右进行扫描,注意端点处不算 bool F=0;tot=0;for(int i=1;i<=n;i++){if(!onlis[i]||ffr[i]==0) continue;if(ffr[i]==i-1) continue; ls[++tot]={1,ffr[i]+1,a[i],0};ls[++tot]={-1,i-1,a[i],0};}for(int i=1;i<=n;i++){if(!onlds[i]||gfr[i]==0) continue;if(a[i]==gfr[i]-1) continue;ls[++tot]={0,i,a[i]+1,gfr[i]-1};}F=is_xiangjiao();if(F) goto out;//已经相交了,没必要在检查了,直接输出 //第二种情况: //红线 (i,a[i])-(i,rfr[i])//蓝线 (i,a[i])-(rgr[i],a[i]) tot=0;for(int i=1;i<=n;i++){if(!onlis[i]||rfr[i]==0) continue;if(rfr[i]==a[i]+1) continue;ls[++tot]={0,i,a[i]+1,rfr[i]-1};} for(int i=1;i<=n;i++){if(!onlds[i]||rgr[i]==0) continue;if(rgr[i]==i+1) continue;ls[++tot]={1,i+1,a[i],0};ls[++tot]={-1,rgr[i]-1,a[i],0};}F|=is_xiangjiao();out:;if(F) printf("%d\n",lis+lds);else printf("%d\n",lis+lds-1);
}
bool _b;
int main(){int _T;
//	freopen("color.in","r",stdin);
//	freopen("color.out","w",stdout);ios::sync_with_stdio(0);cin.tie(0);
//	cin>>_T;_T=1;while(_T--){cin>>n;for(int i=1;i<=n;i++){cin>>a[i];}solve();}return 0;
}

代码有点长,但写起来不难。最后放上在 ATcoder 上的 AC 记录。
至于“速い解法その2”,可以参考别人的题解。

http://www.jsqmd.com/news/382562/

相关文章:

  • 详细介绍:opencv基础(读取图片与视频)
  • 第11届新加坡国际亚新艺术节圆满落幕 700余选手共赴艺术盛宴
  • 大数据架构中的数据生命周期管理策略
  • 方达炬〖发明未知种品〗:合股利润增加值
  • Zig介绍
  • 因果推理在AI决策系统中的实现与应用
  • 大数据时代:如何打造高价值数据产品的10个关键步骤
  • 2026年知名的环保地暖板,高抗压地暖板厂家行业实力名录 - 品牌鉴赏师
  • 移动话费充值卡回收时需要注意哪些问题呢? - 京顺回收
  • 安装Java (Linxu 和 Windows 环境)
  • 2026年有实力的外墙挤塑板,室内挤塑板厂家品牌推荐榜单 - 品牌鉴赏师
  • MongoDB助力大数据高效存储与处理
  • 2026年2月石墨聚苯板制造厂家推荐,节能保温板材生产实力解析 - 品牌鉴赏师
  • 2026年优秀的模塑聚苯板,外墙石墨板厂家行业精选名录 - 品牌鉴赏师
  • 2026年诚信的室内岩棉板,憎水岩棉板厂家选购推荐手册 - 品牌鉴赏师
  • SIEMENS西门子杯 2021初赛电梯最终版:西门子六部十层电梯程序跑分解析
  • 【毕业设计】SpringBoot+Vue+MySQL 火锅店管理系统平台源码+数据库+论文+部署文档
  • SpringBoot+Vue 交通管理在线服务系统管理平台源码【适合毕设/课设/学习】Java+MySQL
  • AI绘画风格迁移:用Z-Image-Turbo快捷模仿大师作品技法
  • unity 实现3D空间音效特性:从0到1避坑指南(附完整代码)
  • Selenium EdgeDriver深度解析
  • Selenium GeckoDriver深度解析
  • 寒假第18天
  • 【CTFshow-pwn系列】03_栈溢出【pwn 046】详解:Ret2Libc 之 64位动态泄露
  • Selenium ChromeDriver深度解析
  • 摸鱼神器,大神开发
  • 如何借助腾讯云防护直播云服务器?
  • Python Web 开发进阶实战:无障碍深度集成 —— 构建真正包容的 Flask + Vue 应用 - 指南
  • Java 多进程/多线程管理 vs PHP-FPM
  • Rust 宏 ! - 教程