C.狩猎

C.狩猎

题面依旧有bug,依旧是题目链接:http://10.1.6.216/problem/109

我才是狩猎大赛的冠军——gfmc

题目背景

众所周知,mc

是五虎上将之一,武艺高强。他高超的武艺源于夜以继日地刻苦练习。现在,mc正在练习杀鸡。同时为了锻炼反应能力,mc

需按一定的要求杀鸡,才能达到智勇双全的境界。

题目描述

对于mc

的练习,给出一排鸡,pi表示一只鸡的血量,qi表示一只鸡的营养价值。mc并不想干得不偿失的事情,所以mc杀的鸡都是精心挑选过的,营养价值都要大于杀它所需的血量。mc只能按排列顺序杀,不能吃回头草。同时为了锻炼他强大的计算能力,mc又定义了一个概念——神鸡:第i只鸡是神鸡,当且仅当2q2i+p2k>2pkpi1kn)满足所有的k时,这一只鸡是神鸡。mc他一边杀鸡还要一边计算这只鸡是不是神鸡(只杀神鸡),这让他本就头昏脑胀。同时,他还想杀尽可能多的鸡。这还不算完,刚刚做完数学题的hx也跃跃欲试,他也找了一排完全相同的鸡,但却被打乱了顺序。mc杀掉的鸡hx也要杀,但是同时hx

也不能杀“回头鸡”,也不能改变排列顺序,求此时最多的杀鸡数目。

翻译一下题意:给定两排数pi

qi,保证qi>pi 。当且仅当2q2i+p2k>2pkpi1kn)这只鸡可以杀。同时要按照排列顺序杀。hx有一排一样的鸡,即pi与qi均相同。但是顺序被打乱。hx和mc杀的鸡是相同的,同时在hx要杀的这一排鸡里也要按照排列顺序杀。求满足mc和hx杀鸡规则时,最多可以杀掉几只鸡。同时,由于每一只鸡都是互不相同的,所以保证pi

互不相同。

输入格式

第一行有一个整数n

,表示一排有n

只鸡。

第二行有n

个整数,第i个整数表示mc杀的那排鸡的pi

第三行有n

个整数,第i个整数表示mc杀的那排鸡的qi

第四行有n

个整数,第i个整数表示hx杀的那排鸡的pi

输出格式

一个整数,表示满足条件的前提下,能杀的最多的鸡的个数。

输入输出样例

样例输入1

5
1 2 3 4 5
2 3 4 5 6
3 2 4 1 5

样例输出1

3

样例输入2

10
10 2 16 5 11 6 14 15 12 9 
335 486 532 362 285 516 108 219 999 957 
16 14 2 5 6 15 9 10 11 12 

样例输出2

5

数据范围

对于20

的数据,n10,0pi<qi1000

对于50

的数据,n2000,0pi<qi1000000

对于100

的数据,n200000,0pi<qi1000000000,保证所有的pi互不相同。

 

 

解题历程:
一开始觉得这个公式挺难的,然后狠下心来逼自己用心算一算,后来神奇的发现其实这个公式只需要用一个均值不等式就可以推出qi>pi,所以这个题的题目含义只是来吓唬一下你,实际题目要求如以下所示:
给定给定两排数pi qi,同时给定另一排与pi 中所有元素都相同但是顺序被打乱的数ci ,则求pi 和ci 的最长公共子序列。

之后我们就很开心的从头开始遍历数组,先访问pi 数组,标记pi 中访问过的元素,因为hx的杀鸡方法是先看mc杀了什么鸡他再杀,所以我们标记了mc杀的鸡之后,访问hx的序列ci,判断这个鸡有没有被hx杀过,然后进行杀鸡。但是很可惜这种方法有点bug,因为最长公共子序列不一定是从头开始的(其实一开始我没有发现这是最长公共子序列),所以中贪心的方法一定是错的,但是神奇的是这个题居然可以贪心到20分?(数据有点水)

 

题目解析:

一开始我的思路已经讲过了,虽然这个是一个错误的方法,但是为了表示纪念以及增长经验,我们还是对这种方法进行一下整理:

 

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
ll p[200010],q[200010];
int n;
ll a[200010];
ll mc[20010],hx[20010];
struct node{
    ll p,q;
    bool b;
}ji[20010];
unsigned long long sum1,sum2;
bool vis[20010];
bool flag[20100];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) cin>>p[i];
    for(int i=1;i<=n;i++) cin>>q[i];
    for(int i=1;i<=n;i++) cin>>hx[i];
    /*for(int i=1;i<=n;i++)
            if(q[i]>p[i])
            {
                vis[p[i]]=1;
            }*/
        int ans=0;
        int maxn=-9999;
    for(int j=1;j<=n;j++)
    {
        ans=0;
        memset(flag,0,sizeof(flag));
        int s=j;
        for(int i=1;i<=n;i++)
        {
    //        if(vis[p[i]]==1)
            if(s>=n) break;
            flag[p[s]]=1;s++;
            if(flag[hx[i]]==1) ans++;
        }
        maxn=max(maxn,ans);
    }
    
    
    cout<<maxn<<endl;
    return 0;
    
}

 

 

接下来讲一下正解的方法吧:

其实我们已经知道了这个题做法就是求最长公共子序列,而且其实两个数列的最长公共子序列是可以转化成最长上升子序列的,具体怎么转换呢?接下来让我们来一起复习一下吧:

首先我们先来造几组数据:(不是题目数据,而仅仅是针对最长上升子序列)

10 2 16 5 11 4 14 15 12 9

16 14 2 5 6 15 9 10 11 12

我们要求的是两个数列的最长公共子序列,那么我们想,其实这个题和我上面说的错误思想其实也差不多,不过就是先访问数组1,标记已经访问过的元素,然后再同时访问数组2,判断这个数在之前有没有访问过。既然如此,我们会产生一个自然而然的想法:不妨将数组1的所有值都给他一个编号,表示访问顺序,那么数据1就变成了:1 2 3 4 5 6 7 8 9 10,这样的话我们下面的元素就相应的变成了:3 7 2 4 6 8 10 1 5 9  这样的变化有什么好处呢?这样其实就是把元素变成了有序元素,我们要求最长公共子序列,而其中一个的序列变成了有序上升序列1~10,那么另一个序列不就转变成了求它的最长上升子序列吗?所以我们成功的把求最长公共子序列变成了求一个序列的最长上升子序列。这样的话,这道题不就好做了吗?我们只需要求最长上升子序列就好了。

最长上升子序列的基本思想是dp:
还是变化后的序列 3 7 2 4 6 8 10 1 5 9

我们求最长上升子序列,就是判断后一个元素是否大于前一个元素呗。但是问题又来了,如果后面一个数大于前面一个数,那好说,我们直接依次遍历就好了,但是如果遇到后面的数小于 前面的数怎么办呢?按照我们正常手动模拟过程,如果遇到后面的数大于前面的数,我们当然是先找到比这个数小的数,然后判断到底是包含小数的最长上升子序列大,还是包含大数的最长上升子序列大,是不是?这样的话我们把这种思想拿到程序上来,如果当前元素比上一个元素小,那么我们就找到第一个大于当前元素的数,然后用当前元素覆盖那个元素。是不是很迷惑?不要着急,我们来一个个解决你的疑惑:

为什么是找第一个大于当前元素的数而不是最后一个小于当前元素的数?毕竟我们在手动模拟的时候不是先找到最后一个小于当前元素(不行,这四个字太长了,我们还是用x来代替当前元素,y表示上一个元素,max表示第一个比他大的元素,min表示最后一个比他小的元素吧),然后再判断到底是包含小数的最长上升子序列大,还是包含大数的最长上升子序列大吗?

同志们,我们手动模拟的过程的确是那样没错,但是我们是要把它写成程序来实现的啊,写成程序的话,过程我们也至少得变化一下吧,所以我们的变化来了。来听一下我个人的理解吧:

我们的dp原则是找到最长上升子序列,而这个最长上升子序列有可能存在于x所在的序列,也有可能存在于y所在的序列,如果我们用x来覆盖max,那么就表示对于y所在的序列,虽然元素改变了,但是元素个数没有改变啊,我们要求的是元素个数,所以即使元素内容改变,也依旧没有什么关系,而且对于后面的元素,如果用一个更小的数来更新较大的数,那么后面的元素就有更多机会来插入队列,这是一种更优的选择,比如我们来举一个例子:

.....7 11 20 3 4 5 8 9 10 ...... 

.....3 11 20 3 4 5 8 9 10 ......

来比较一下,在这个序列中我们把7(第一个大于3的数)改成3,这样的话对于后面的元素来说,在第一个序列中,最长上升子序列是

.....7 11 20 3 4 5 8 9 10 ...... 

 

 而对于下面的序列来说,最长上升子序列则是:

 .....3 11 20 3 4 5 8 9 10 ......

可以发现这样的最长上升子序列长度增加了。

可是又有同学问了:上面的序列是原来的序列啊,如果改成下面的序列,即使元素个数增加了,可以和原来的序列不就不一样了吗?话是这样说没错,可以你忘记了一点,那就是在实际操作过程中,如果后面的序列更优,那么我们是把这个7直接跳过不看的?想一下如果你手动模拟是不是这样?所以如果不改变max而是直接进行遍历,反而是错的。

 

 这样的话问题就解决了,然后回到我们一开始的问题,其实我们在上面的时候已经说过了,不过就是把原来的序列给它编个号,然后求编号的最长上升子序列罢了,std如下:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
map<int,int> mp;
int n,len;
int p1[200005],q1[200005],p2[200005],w[200005],ans[200005];
bool cmp(int a,int b){
    return a<b;
} 
int main()
{
//    freopen("mc10.in","r",stdin);
//    freopen("mc10.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++){
        p1[i]=read();
    }
    for(int i=1;i<=n;i++){
        q1[i]=read();
    }
    for(int i=1;i<=n;i++){
        p2[i]=read();
    }
    for(int i=1;i<=n;i++){
        mp[p1[i]]=i;
    }
    for(int i=1;i<=n;i++){
        w[i]=mp[p2[i]];
    }//问题转化为求w数组的最长上升子序列 
    len=1,ans[1]=w[1];
    for(int i=2;i<=n;i++){
        if(w[i]>ans[len]){
            ans[++len]=w[i];
        }
        else{
            int k=lower_bound(ans+1,ans+1+len,w[i])-ans;
            ans[k]=w[i];
        }
    }
    printf("%d\n",len);
    return 0;
}

 

本人AC代码如下:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
ll n,a[502000],b[502000],c[502000];
map<ll,ll> mp;
ll d[502000];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) 
    {
        cin>>a[i];
        mp[a[i]]=i;
    }
    for(int i=1;i<=n;i++) 
    {
        ll x;
        cin>>x;
    }
    
    for(int i=1;i<=n;i++) cin>>b[i];
    
    ll len=1;
    
    d[1]=mp[b[1]];
    for(int i=2;i<=n;i++)
    {
        if(mp[b[i]]<d[len]) 
        {
            int x=lower_bound(d+1,d+len+1,mp[b[i]])-d;
            d[x]=mp[b[i]];
        }
        else 
        {
            len++;
            d[len]=mp[b[i]];
        }
    }
    
    printf("%d\n",len);
    return 0;
}

 

对了,还有一点要提示的是因为数据范围很大,而我们给数组编号有相当于是一个映射关系,所以如果你用桶排序的方式来给编号赋值只能得50分,这里我们要引进一个stl map,map就相当于一个函数映射关系,并且他是类似于动态数组,妈妈再也不用担心我爆空间啦!!!

-----------end------------

 

posted @ 2020-11-30 17:32  yxr~  阅读(62)  评论(0编辑  收藏  举报