atcoder ABC 439 E
原题大意
(https://atcoder.jp/contests/abc439/tasks/abc439_e)
其实挺简答的,感觉没有atocder problem中的那么高
分析
每一个点都有一个Ai和一个Bi,这个Ai和Bi连成一条线,要求的其实就是,这几条边中,让所有线都没有交点,包括端点,比如(2,3)和(2,5)也是不可以同时存在的,因为这两个点的2这个端点重合了
其实有一个名字叫什么LIS算法,就是最长上升子序列,比如[1, 7, 3, 5, 9, 4, 8]中的最长上升子序列就是1 3 5 8长度为4,这一题只要求我们求出长度就可以了,没有说具体到哪一条边
我们根据题目可以很清楚的得出的结论就是:要让这两条边不会产生交点的话,那么就是要Ai<Aj&&Bi<Bj 或者Aj<Ai&&Bj<Bi
我们可以先把这些边按照A的大小进行排序,然后,就剩下了B了,B就可以用LIS算法进行计算
你可能会问,为什么就想到了把A进行排序?因为这些边终归是要复现在二维坐标图上的,只要把图画出来,就可以很容易的想到把这些边按照A排序
具体是怎么实现的
关键的sort函数
A当然是按照升序排序的
在例子中,我们肯定会发现就是有A相同但是B不相同的,这个时候,假设我们把B小的放在前面的话,注意上面的加粗的字,就知道为什么是把B降序排列了
sort(all(ab),[&](pair<int,int>&a,pair<int,int>&b){
if(a.first==b.first)return a.second>b.second;
return a.first<b.first;
});
结合贪心思想
当我们在这个dp里找不到比b大的的位置的时候,我们就可以把这个加到我们的dp里,这个可以给我们"最多人一起放风筝"的人数增加人数
当我们找到了这个dp里比当前b大的数字的时候,那么就把这个位置替换成b,这个是贪心的思想,为了后面可以增加更多的"一起放风筝"的人的数量
小疑惑
你可能会问出这样的问题,就是,假设我的例子是[10,20,5]呢?那你的结果不就是[5,20]了?但是[5,20]也不是正确的啊,最后面的5怎么可能可以和第二个的20一起放风筝呢?
他们两个肯定有交点,那你的结果不就是非法的了嘛?
我们来思考这样一个问题:这个解题的思路是不是合理的?答案当然是,这个是我们的贪心策略,那这个贪心策略是为什么呢?这里就不得不提左神在讲贪心时说过的话了,证明贪心没有意义,因为贪心千变万化
左神还说过,贪心时最接近人类智慧的算法,当我们没有看到这个已经有的dp数组里有比当前这个数字大的,就把这个数字加入到dp,如果有,就把当前这个位置换成这个更小的b,这本身就非常符合人性(
假设例子是[10,20,5,7,8]呢?
[10]->[10,20]->[5,20]->[5,7]->[5,7,8]正是我们把第一个的10贪心的换成了5,后面就有了更多的机会,才得出了正确的答案,上面那个看似错误的例子,但是答案输出来后,就是正确的2啊
我们为什么把10替换成5?因为这个5留着更有前途,这个5更有潜力,哪怕到最后没用上,但是这有做事正确的,因为把更小一点的5留下,比把10留下更有价值
for(auto[_,b]:ab){
int i=lower_bound(all(dp),b)-dp.begin();
if(i==dp.size())dp.push_back(b);//如果这个dp里没有大于等于b的数字的话,说明这个b可以放到dp里面,可以和前面的人一起放风筝
else dp[i]=b;//贪心,LIS算法
}
完整代码
#include<bits/stdc++.h>
using namespace std;
//"O campeão tem nome, e se chama Charles Oliveira!"
#define int long long
#define endl '\n'
#define ep emplace
#define pob
#define ll long long
#define pb push_back
#define pof pop_front
#define pob pop_back
#define all(a) a.begin(),a.end()
#define rall(a) a.rbegin(),a.rend()
#define mod 998244353
#define MOD 1000000007
const int N=200005;
using ld = long double;
using ui = unsigned;
using ull = unsigned long long;
using i128 = __int128;
int fastpow(int x,int y){
int temp=1;
while(y){
if(y&1)temp*=x;
x*=x;
y>>=1;
}
return temp;
}
void solve(){
int n;cin>>n;
int cnt[n]{};
vector<pair<int,int>>ab(n);
for(int i=0;i<n;i++){
cin>>ab[i].first>>ab[i].second;
}
sort(all(ab),[&](pair<int,int>&a,pair<int,int>&b){
if(a.first==b.first)return a.second>b.second;
return a.first<b.first;
});
//当a相同的时候,要把b的值大的放在前面,因为,两个人的线段是不可以有交点的,就算是端点也不可以
//例如:(2,3)和(2,5),如果是升序排列的话,那么按照下面的算法,就是两个人可以一起放风筝了,但是正确答案是只有一个
vector<int>dp;
for(auto[_,b]:ab){
int i=lower_bound(all(dp),b)-dp.begin();
if(i==dp.size())dp.push_back(b);//如果这个dp里没有大于等于b的数字的话,说明这个b可以放到dp里面,可以和前面的人一起放风筝
else dp[i]=b;//贪心,LIS算法
}
cout<<dp.size()<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t=1;
//cin>>t;
while(t--)solve();
}

浙公网安备 33010602011771号