TWO SMALL PROBLEM

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TWO SMALL PROBLEM相关的知识,希望对你有一定的参考价值。

TWO SMALL PROBLEMS

Move

题面

[题目描述]

有一串数字,现在每将其中的一个数移动到最前或最后,记做一次操作,求最少的能使这一串数字成为从小到大排列的操作次数

[输入输出格式]

输入格式

两行,第一行是 N,第二行有N个int范围的整数

输出格式

一行,最少的操作次数

[输入输出样例]

输入

5

2 1 3 5 4

输出

2

 

题解

第一阶段想法(主要是搜索。。。)

FIRST(逆序对个数)

         刚看到这道题,乍一看就像一个求逆序对的题目。

         然后非常兴奋的试了几组数据。。。

         最后,发现尴尬了。。。

         此想法WRONG!

SECOND(DFS/BFS暴搜)

         后来,想了一下。感觉要不先写一个暴搜吧。

         思路大概是这样:

                   由起始状态向后推,一层一层推,每个状态的每个点,有两种移法,可以移到最前面,或移到最后面。这样自依次向后推。具体实现的时间复杂度大概是

之间。

         额,显然,顶多过N<=3的数据。。。

         此想法PASS!

THIRD(厉害的A*)

         额,其实我还是没有放弃。记得某学军大神讲过,有些玄学的搜索会有异常显著的效果。

So 这道题的A*的想法就诞生了。

         思路如下:

                  A*算法的核心就是启发函数H(n)

                   用启发函数可以省去一些多余的情况。

                   嘿嘿,那么我那wrong的FIRST的想法就可以派上用场了。

                   启发函数可定义为该状态的逆序对数。

                   每次都向转移后逆序对数最少的状态转移。

                   求逆序对的时间复杂度是,后面的搜索的时间复杂度大概是

         SO 总时间复杂度大概是

         比SECOND快一点,可以过到N<=15的数据。。。

         额,感觉没好多少。

第二阶段想法(主要是脑洞。。。)

FIRST(分组框法(额,姑且这样讲先))

         从头往后找,

         先找到两个位置的数据,这两个位置上的数据的排名是相邻的。

         将其中的数据框住。

接着再找两个位置的数据,再框住,若有交集,判断一下,再改一下答案。

否则不用理它。

最后还没框住的,就是答案。

可惜到最后还是不知道该怎么判断。。。。

SECOND(二分移动)

         从中间开始转移,往两边二分,像搜索一样判断,接着用看起来像搜索的方法拓展。

         时间复杂度,只能过N<=5。。。。

THIRD(二分框)

         综合FIRST与SECOND的想法,而创出的瞎搞的二分框算法。。。。

         由中间开始框,接着二分着去框。

         理论时间复杂度

         可惜,跟FIRST一样,不知道怎么判断。。。

         So PASS。

第三阶段想法(这才是真正正常的想法)

FIRST(不考虑重复)

         首先,你会发现,其实将原状态转移至目标状态。就是将插在排名连续的几个数中的数,按一定的次序移动,就可以达成。

         所以,只需求这个最长的排名连续的序列的长度就行。

         可惜貌似2 3 3 2

                   与1 2 3 4 5 4 7 8这两个数据不能同时过。

         因为不知道当有重复数字时,重复数字的序号该怎么排。

(算相同,第二个数据过不了,算不同,第一个数据过不了。。。)

(有可能我陷入了误区。。。)

         该算法时间复杂度

         附上代码:(被屏蔽的是我挣扎的结果)

 

     

//做法:标序后最长连续上升子序列 (不考虑重复) 
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

struct si
{
    int x,y,z;//x:值,y:原来的次序,z:排名 
}s[100005];

int n; 
int a[100005];
int f[100005];

bool cmp(si a,si b)
{
    //if(a.x==b.x)    
        //return a.y<b.y;
    return a.x<b.x;
}

int main()
{
    //freopen("1.in","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&s[i].x);
        s[i].y=i;
    }
    sort(s+1,s+n+1,cmp);
    s[1].z=1;
    for(int i=2;i<=n;i++)
        //if(s[i].x==s[i-1].x)
        //    s[i].z=s[i-1].z;
        //else
            s[i].z=s[i-1].z+1;
    for(int i=1;i<=n;i++)
        a[s[i].y]=s[i].z;
    f[1]=1;
    int longest=1;
    for(int i=2;i<=n;i++)
    {
        f[i]=1;
        for(int j=1;j<i;j++)
            if(a[i]==a[j]+1||a[i]==a[j])
                f[i]=max(f[i],f[j]+1);
        if(longest<f[i])
            longest=f[i];
    }
    printf("%d\n",n-longest);
    return 0;
}

 

SECOND(考虑重复)

         其实如果再仔细想想的话,这种情况的解法也是很清楚的。

         首先,修改一下FIRST中的想法。

         将原状态转移至目标状态。就是将插在排名连续或相同的几个数中的数,按一定的次序移动,就可以达成。

         当有重复数字时,记录一下当前数字被作为不动的连续的数的个数。

若该数字没有被用完,那么只能最多将到他之前的它排名前一位的数与其相连。

若用完了,则可以正常的相连。

具体的判定语句如下:

 

   

//其中use[i]代表第i位上的数字在以第i位结尾的排名连续或相同的序列中被使用的次数
if(a[i]==a[j])//若排名相同
{
    if(f[j]+1>f[i])//若更大
    {
        f[i]=f[j]+1;
        use[i]-=la;
        use[i]+=use[j];//修改当前该位数字的使用次数
        la=use[j];
    }
}
else if(a[i]==a[j]+1)//若排名不同
{
    use[i]=1;
    if(use[j]!=backet[a[j]])//若没有被用完
        f[i]=max(f[i],use[j]+1);
    else//若用完了
        f[i]=max(f[i],f[j]+1);//直接连上去 
}

 

该算法时间复杂度
完整代码如下:

 

   

//做法:最长连续上升子序列 (考虑重复)
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

struct si
{
    int x,y,z;
}s[10005];

int n; 
int a[10005];
int use[10005];
int    backet[10005];
int f[10005];

bool cmp(si a,si b)
{
    return a.x<b.x;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&s[i].x);
        s[i].y=i;
    }
    sort(s+1,s+n+1,cmp);
    s[1].z=1;
    for(int i=2;i<=n;i++)
        if(s[i].x==s[i-1].x)
            s[i].z=s[i-1].z;
        else
            s[i].z=s[i-1].z+1;
    for(int i=1;i<=n;i++)
        a[s[i].y]=s[i].z;
    for(int i=1;i<=n;i++)
        backet[a[i]]++;
    f[1]=1;
    use[1]=1;
    int longest=1,la;
    for(int i=2;i<=n;i++)
    {
        f[i]=1;
        use[i]=1;
        la=0;
        for(int j=1;j<i;j++)
        {
            if(a[i]==a[j])
            {
                if(f[j]+1>f[i])
                {
                    f[i]=f[j]+1;
                    use[i]-=la;
                    use[i]+=use[j];
                    la=use[j];
                }
            }
            else if(a[i]==a[j]+1)
            {
                use[i]=1;
                if(use[j]!=backet[a[j]])
                    f[i]=max(f[i],use[j]+1);
                else
                    f[i]=max(f[i],f[j]+1);
            }
        }
        if(longest<f[i])
            longest=f[i];
    }
    printf("%d\n",n-longest);
    return 0;
}

 

Kangroo

题面

[题目描述]

有一群袋鼠,为了对抗夜晚的寒冷,它们必须钻到别的袋鼠的袋子里去耐寒,每只袋鼠的体重为m,那么这只袋鼠所能带下的最大体重为m/2的袋鼠(每一个袋子只能装一个袋鼠,且被装入袋子的袋鼠不能再带袋鼠),求最少的没有被装入袋子的袋鼠。

[输入输出格式]

输入格式

两行,第一行是N,第二行有N个表示袋鼠体重的数(m均为整数)

输出格式

一行,最少的没有被装入袋子的袋鼠数量。

[输入输出样例]

输入

6

1 2 3 7 8 9

输出

3

题解

搜索

         显然这是一个正常的想法。

         去尝试每一种吃法。

         时间复杂度大概为。。。

         一看就过不了。。。

贪心

FIRST(从大到小贪)

         从大到小,每一只袋鼠尽可能的吃当前能吃的最大的,并标记已经吃的和被吃的。

         可惜对于8 4 2 1不符合。。。。

         答案应为2,但用这种贪心策略答案输出为1.。。

         So 这种方法WRONG!

SECOND(从小到大贪)

         与FIRST相反,从小到大,每一只袋鼠尽可能被最大的袋鼠吃,并标记已经吃的和被吃的。

         可惜对于8 4 2 1仍不符合。。。。

         答案应为2,但用这种贪心策略答案输出仍为1.。。

         So 这种方法也WRONG!

THIRD(n/2)

         先将袋鼠排序,以N/2为分界线,将袋鼠分为两堆。

         用两个指针,往两边推,使较小的一堆中的袋鼠只能被较大的一堆的袋鼠吃。。。

         时间复杂度O(NlogN)

         下面是正确性证明:

                   首先,所剩下的袋鼠不可能小于N/2。假设不是在那两堆之间吃,而是一个堆里面自己吃。那么就一定会有一种同等优的解法。

                   假设小的那堆中,有a袋鼠吃掉了b袋鼠。那么在较大的那堆里一定有两只袋鼠可以吃掉这两只袋鼠的两只袋鼠或者没有能同时吃掉两只袋鼠的两只袋鼠。

                   若是第一种情况,显然,比一堆里面自己吃更优。

                   若是第二种情况,可以用大的一堆中一只较小并且是能吃掉那只袋鼠的最小的袋鼠吃掉小的一堆中的那只大的,显然答案至少与一堆里面自己吃相同,或者还有更多的吃法。

         附上代码:

          

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
int n,ans;
int a[100000],b[100000];

bool cmp(int a,int b)
{return a<b;}

int main()
{
    freopen("t2.txt","r",stdin);
    while(scanf("%d",&n)&&n!=0){
    ans=n;
    int i;
    for(i=1;i<=n;i++) scanf("%d",&a[i]);
    sort(a+1,a+n+1,cmp);
    for(i=1;i<=n;i++) b[i]=a[i]*2;
    int t=(n/2)+(n%2?2:1);
    int ta=t,tb=1;
    while(tb<t)
    {
        while(ta<=n&&(a[ta]<b[tb]&&ta>=tb))
            ta++;
        if(a[ta]>=b[tb]&&tb<=n&&ta<=n)
            ans--;
        tb++;
        ta++;
    }
    printf("%d\n",ans);
    }
    return 0;
}

 

 

 

动规

         这道题其实也有想过动规的做法,但发现其实跟暴搜是同等意义的。

         而且也没有较好的转移方法和记录状态的方法。

         So PASS!

网络流

         这是一种较自然的想法。

         就是先弄一个超级源,超级汇。

         建两排点,若可以吃掉,就由第一排的袋鼠往第二排的袋鼠连边。

         跑最大流,求出ANS

         答案就为N-ANS

         但这有一个问题就是,比如说有8->4,4->1这两条边。但这两条边显然就是不能同时流的。那该怎么办呢?

         这里有一种奇奇怪怪的解法。

         就是在最后输出答案时,判定一下,是否超过一半,超过一半的话就输出n-n/2

         但我不能证明它是对的。。。。

         时间复杂度

         附上代码:
           

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
const int inf=0x3f3f3f;

struct edge
{
    int next,v,to;
}e[400005];

int head[10005],cur[10005];
int n,m,s,t,cnt=1,ans=0;
int h[10005],q[10005];

void addedge(int x,int y,int w)
{
    e[++cnt].to=y;e[cnt].next=head[x];head[x]=cnt;e[cnt].v=w;
    e[++cnt].to=x;e[cnt].next=head[y];head[y]=cnt;e[cnt].v=0;
}

bool bfs()
{
    int he=0,tail=1,now;
    for(int i=0;i<=n;i++)
        h[i]=-1;
    q[0]=s;h[s]=0;
    while(he!=tail)
    {
        now=q[he];
        he++;
        for(int i=head[now];i!=-1;i=e[i].next)
            if(e[i].v&&h[e[i].to]==-1)
            {
                h[e[i].to]=h[now]+1;
                q[tail++]=e[i].to;
            }
    }
    return h[t]!=-1;
}

int dfs(int x,int f)
{
    if(x==t)
       return f;
    int w,used=0;
    for(int i=cur[x];i!=-1;i=e[i].next)
        if(h[e[i].to]==h[x]+1)
        {
            w=f-used;
            w=dfs(e[i].to,min(w,e[i].v));
            e[i].v-=w;e[i^1].v+=w;
            used+=w;
            if(e[i].v)cur[x]=i;
            if(used==f)
               return f;
        }
    if(!used) h[x]=-1;
    return used;
}

void Dinic()
{
    while(bfs())
    {
          for(int i=1;i<=n;i++)
            cur[i]=head[i];
          ans+=dfs(s,inf);
    }
}

int a[10005];

int main()
{
    int x,y,w,i,j,n1;
    scanf("%d",&n1);
    for(i=1;i<=n1;i++)
        scanf("%d",&a[i]);    
    n=2*n1+2;
    s=1;
    t=2*n1+2;
    for(i=0;i<=n;i++)
        head[i]=-1;
    for(i=1;i<=n1;i++)
    {
        addedge(s,i+1,1);
        addedge(i+n1+1,t,1);
    }
    for(i=1;i<=n1;i++)
    {
        for(j=1;j<=n1;j++)
            if(j!=i&&a[j]<=a[i]/2)
                addedge(i+1,j+n1+1,1);
    }
    /*for(i=1;i<=n;i++)
    {
        printf("%d:",i);
        for(j=head[i];j!=-1;j=e[j].next)
            printf("->%d",e[j].to);
        printf("\n"); 
     } */
    Dinic();
    if(ans>n1/2)
        printf("%d\n",n1-n1/2);
    else
        printf("%d\n",n1-ans);
    return 0;
}

 

以上是关于TWO SMALL PROBLEM的主要内容,如果未能解决你的问题,请参考以下文章

small team two

CF1213E Two Small Strings

拥有的50个CSS代码片段(上)

LeetCode Problem 1.Two Sum

LeetCode Problem 2.Add Two Numbers

problem 2: add two numbers