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;
}
时间: 2024-10-09 11:36:07

TWO SMALL PROBLEM的相关文章

A Math Problem

A Math Problem Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 237    Accepted Submission(s): 117 Problem Description You are given a positive integer n, please count how many positive integers

Water Problem

water problem 发布时间: 2015年10月10日 15:34   时间限制: 1000ms   内存限制: 256M 描述 题意很简单 给你N个数, Q个查询 每次查询给你一个区间[L, R] 你要找出 [L, R] 这个区间里面取模M后的最大值. 输入 第一行一个T,表示测试数据组数.第二行两个整数N, M (1<=N<=10^5, 1<=M<=10^9).第三行给你N个整数 整数范围在1到10^9之间.第四行给你一个整数Q. ( 1<=Q<=10^5)

FOJ Problem 2261 浪里个浪

                                                                                                                                                           Problem 2261 浪里个浪 Accept: 40    Submit: 106Time Limit: 1500 mSec    Memory Limit : 32768 KB Pro

XJTUOJ wmq的A&#215;B Problem FFT

wmq的A×B Problem 发布时间: 2017年4月9日 17:06   最后更新: 2017年4月9日 17:07   时间限制: 3000ms   内存限制: 512M 描述 这是一个非常简单的问题. wmq如今开始学习乘法了!他为了训练自己的乘法计算能力,写出了n个整数,并且对每两个数a,b都求出了它们的乘积a×b.现在他想知道,在求出的n(n−1)2个乘积中,除以给定的质数m余数为k(0≤k<m)的有多少个. 输入 第一行为测试数据的组数. 对于每组测试数据,第一行为2个正整数n,

hidden node and exposed node problem

Exposed node problem In wireless networks, theexposed node problem occurs when a node is prevented from sending packets to other nodes because of a neighboring transmitter. Consider an example of 4 nodes labeled R1, S1, S2, and R2, where the two rece

南阳524 A-B Problem

A-B Problem 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 A+B问题早已经被大家所熟知了,是不是很无聊呢?现在大家来做一下A-B吧. 现在有两个实数A和B,聪明的你,能不能判断出A-B的值是否等于0呢? 输入 有多组测试数据.每组数据包括两行,分别代表A和B. 它们的位数小于100,且每个数字前中可能包含+,- 号. 每个数字前面和后面都可能有多余的0. 每组测试数据后有一空行. 输出 对于每组数据,输出一行. 如果A-B=0,输出YES,否则输出NO

Lab 1: Write a java program for the triangle problem and test the program with Junit.

Tasks: 1. Install Junit(4.12), Hamcrest(1.3) with Eclipse 将两个jar包添加到工程中 2. Install Eclemma with Eclipse 3. Write a java program for the triangle problem and test the program with Junit. [Description of triangle problem]Function triangle takes three i

HDU 1016 Prime Ring Problem(DFS)

题目链接 Problem Description A ring is compose of n circles as shown in diagram. Put natural number 1, 2, ..., n into each circle separately, and the sum of numbers in two adjacent circles should be a prime. Note: the number of first circle should always

poj 3320 Jessica&#39;s Reading Problem(尺取法+map/hash)

题目:http://poj.org/problem?id=3320 题意:给定N个元素的数组,找出最短的一段区间使得区间里面的元素种类等于整个数组的元素种类. 分析:暴力枚举区间的起点x,然后找到最小的y,使得区间[x,y]满足条件,x向有移位后变成x',现在的y'肯定不至于在y的左边.存状态的话map和hash都可以. map代码: #include <iostream> #include <set> #include <map> #include <cstdi

hdu 5293 Tree chain problem(树链剖分+树形dp)

题目链接:hdu 5293 Tree chain problem 维护dp[u], sum[u],dp[u]表示以u为根节点的子树的最优值.sum[u]表示以u节点的所有子节点的dp[v]之和.对于边a,b,w,在LCA(a,b)节点的时候进行考虑.dp[u] = min{dp[u], Sum(a,b) - Dp(a,b) + sum[u] | (ab链上的点,不包括u } #pragma comment(linker, "/STACK:1024000000,1024000000")