2/9 P1020 最长不上升子序列 (3/16补档)(3/26补档)

放个链接先:https://www.luogu.com.cn/problem/P1020

做的时候(×)抄的时候(√)一直不懂为什么替换掉元素不会有影响,为什么len不用变,后面怎么改变len的值来着,真是太逊了呜呜

在deepseek的支持下得到了理解(以最长不上升子序列为例):

当一个数比当前的数大时,就把它替换到

dp [ upper_bound (dp+1 , dp+1+len , a[i] , greater<>( ) ) -dp ] greater<>()是为了把找到更大的变为找到更小的!

这时候末尾元素不变,len不变,我们要的就是这个效果!

因为我们不知道这个数 带来的新的可能性 会不会比当前的长,所以先放在那里

当我们不断往下找的时候,其他新的可能性也会不断加入到前面

最终有可能新的可能性追上了当前的长度len,这时候len同时记录新旧,下一个自然会过渡到更有优势的那一个可能性啦~

用DS的话来解释就是:
维护一个尽可能“有潜力”的序列(末尾元素尽量小/大),方便后续元素扩展
替换不影响最终长度:替换操作只优化后续扩展的可能性,不改变已计算的序列长度上限。

另外,upper_bound/lower_bound 是二分查找,会比较快!(其实就是不会自己写二分×)

AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
LL n,h[100010],dp1[100010],dp2[100010],len1,len2;

int main()
{
	while(scanf("%d",&h[++n])!=EOF);
	n--,len1=1,len2=1;
	dp1[1]=h[1],dp2[1]=h[1];
	for(LL i=2;i<=n;i++)
	{
		if(h[i]<=dp1[len1])
		{
			dp1[++len1]=h[i];
		}
		else
		{
			LL re=upper_bound(dp1+1,dp1+1+len1,h[i],greater<LL>())-dp1;
			dp1[re]=h[i];
		}
		if(h[i]>dp2[len2])
		{
			dp2[++len2]=h[i];
		}
		else
		{
			LL re=lower_bound(dp2+1,dp2+1+len2,h[i])-dp2;
			dp2[re]=h[i];
		}
	}
	
	cout<<len1<<endl<<len2;
	return 0;
}

(3/16补档如下)

题目

另一种方法:
使用树状数组

需要的两个基本函数:

查询前面最大值:

int qmax(int x)
{
	int ans=0;
	for(;x;x-=x&-x) ans=max(tr[x],ans);
	return ans;
}

插入元素:

void add(int x,int y)
{
	for(;x<=n;x+=x&-x) tr[x]=max(tr[x],y);
}

AC代码

    cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i],s[i]=a[i];
	sort(s+1,s+1+n);
	m=unique(s+1,s+1+n)-(s+1);

	for(int i=1;i<=n;i++) a[i]=lower_bound(s+1,s+1+n,a[i])-s;
	for(int i=1;i<=n;i++) f[i]=qmax(a[i])+1,add(a[i],f[i]);
	memset(tr,0,sizeof(tr));
	for(int i=n;i>=1;i--) g[i]=qmax(n-a[i]+1)+1,add(n-a[i]+1,g[i]);
	memset(tr,0,sizeof(tr));

	a[n+1]=m+1;
	for(int i=k+2;i<=n+1;i++)
	{
		add(a[i-k-1],f[i-k-1]),ans=max(ans,k+g[i]+qmax(a[i]));
	}

	if(k>=n-1) ans=n;
	cout<<ans;

原理:

用 s 记录排序后的原数据 a
将 a 变为排序后的位置的引索
(为方便说明,原数据的大小仍使用 a [ i ] 来说明)

之后以树状数组(以下简称 tree)为中转

每次先查询 a [ i ] 排序后的引索(以下简称 index )

sort(s+1,s+1+n);
a[i]=lower_bound(s+1,s+1+n,a[i])-s;

找到 tree [ index ] 在内(此时该还没有数据,实际上找的是之前的)往前最大的数 + 1 ,就得到了第 i 个数结尾的最长不下降子序列的长度,然后将这个长度放到 tree [ index ]

for(int i=1;i<=n;i++) f[i]=qmax(a[i])+1;add(a[i],f[i]);

解释如下:

我们是按照 原来的数据 的顺序一个一个进行查询的,也就是说,
每个数是依次被我们放进 tree 数组中的,
而我们放进去的位置 就是 数据按大小排序对应的位置
那么,
当我们放进一个新的数之后,前面记录的就是比自己小的数,并且按照大小排好序,并且是前面已经放好的数,
这是不是就是最长不下降子序列要求的? 自己在内的前面,按大小排序?


字符串的最长子序列

这一就是把数字换成了字符串而已,没什么不同,字符串的比较就是题目要求的字典序比较

但是蒻蒟看了半天发现输出ans写成了a,看了半天呜呜呜

#include <bits/stdc++.h>
using namespace std;
typedef long long int LL;
const int N=1e6+10;
LL d,t,m,len,num;
string a[N],dp[N],ans[N],s;
void read()
{
	cin>>s;
	for(int i=0;i<s.size();i++)
	{
		if(isupper(s[i]))
		{
			a[++num]=s[i];
		}
		else
		{
			a[num]+=s[i];
		}
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	read();
	dp[1]=a[1],ans[1]=a[1],len=1;
	for(int i=2;i<=num;i++)
	{
		if(a[i]>dp[len])
		{
			dp[++len]=a[i];
			ans[len]=ans[len-1]+a[i];
		}
		else
		{
			LL re=lower_bound(dp+1,dp+1+len,a[i])-dp;
			dp[re]=a[i];
			ans[re]=ans[re-1]+a[i];
		}
	}
	
	cout<<ans[len];
	return 0;
}
posted @ 2025-02-09 22:09  石磨豆浆  阅读(19)  评论(0)    收藏  举报