首页 > 代码库 > 2016.10.31.第54套(集训1)

2016.10.31.第54套(集训1)

Mushroom的序列
【问题描述】
Mushroom手中有n个数排成一排,现在Mushroom想取一个连续的子序列,使得这个子序列满足:最多只改变一个数,使得这个连续的子序列是严格上升子序列,Mushroom想知道这个序列的最长长度是多少。
【输入格式】
第一行一个整数n,表示有n个数。
第二行为n个数。
【输出格式】
一个数,为最长长度。
【输入样例】
6
7 2 3 1 5 6
【输出样例】
5
【样例解释】
选择第2个数到第6个数,把1改变成4即可。
【数据范围】
对于30%的数据,n<=10
对于60%的数据,n<=1000
对于100%的数据,n<=100000

题解:

这道题其实不是很难,主要在于那个改变的数上,而我们可以确定的是这个数前后必有两个递增区间。所以思路转换过来,就成了找相邻两个递增区间,并且相接处可通过改变一个数的值连接为一个递增区间。这个思路就简化了。另外一种方法就是通过整体记录需不需要改变这个值来使区间最长,简而言之,头一遍dp,尾一遍dp,通过f[1\0][i]表示i这个点修不修改的两种结果。虽然本质差不多,却复杂了很多,因为递增区间整体只有前两个和最后两个是有意义的,中间不用管。当然区间长度为1的时候要特判。我个人在做这道题的时候,考虑的不够全面,当时没有相同前一个区间的最后一个数改变和后一个区间的第一个值改变有啥子区别,于是就GG了,嗨呀,气。当然主要问题还是自己的思维不够严谨,没有意识到问题的本质。

修改后代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#define MN 100005
using namespace std;
int n,ans,a[MN],b[MN],c[MN],tot,r[MN],l[MN],len;
bool con(int x,int y)
{
    if(a[r[x]]<a[l[y]+1]-1) return true;
    if(a[r[x]-1]<a[l[y]]-1) return true;
    if(l[x]==r[x]||l[y]==r[y]) return true;
    return false;
}
void depa()
{
    for(int j=1;j<tot;j++)
    if(con(j,j+1))
    {
        len=r[j+1]-l[j]+1;
        ans=max(ans,len);
    }
}
int main()
{
    freopen("seq.in","r",stdin);
    freopen("seq.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {scanf("%d",&a[i]); b[i]=a[i];}
    c[1]=1;
    for(int i=2;i<=n;i++)
    if(a[i]>a[i-1]) c[i]=c[i-1]+1,ans=max(ans,c[i]+1);
    else c[i]=1;
    for(int i=1;i<=n;i++)
    if(c[i]==1) tot++,l[tot]=i,r[tot]=i;
    else r[tot]=i;
    depa();
    if(tot==1) ans=n;
    cout<<ans<<endl;
    return 0;
}

Mushroom的区间
【题目描述】
Mushroom有一行数,初始时全部是0。现在Mushroom有m个区间[L,R],他希望用以下操作得到新的序列。
从m个给定区间中选择一个区间[s,t],把区间中的数对应元素全部翻转。(0变1,1变0)
请告诉Mushroom他能得到多少区间。(模10^9+7)
【输入格式】
第一行包含两个整数n,m。表示n个数和m个区间。
接下来m行是所表示的区间。
【输出格式】
一个整数,表示能得到的区间数。
【样例输入】
3 3
1 1
2 2
3 3
【样例输出】
8
【数据范围】
对于30%的数据,n,m<=20
对于60%的数据,n,m<=100
对于100%的数据,n,m<=100000
【样例解释】
每个位置都可以单个修改,所以有8种可能。

题解:

考试的时候GG了=-=,那个时候20分钟水完了,因为没有想到方法,连暴力也没有。后来分析,还是没有看破本质,自己太垃圾。其实后来懂了,真的敲简单,眼泪落下来。其实可以发现就是题中给的区间的排列组合,然饿要注意重复,如图,只有两种重复情况:

技术分享

啊,比较好理解吧,然后我们只需要吧这种重复的找出来,然后,不管那个浅绿的大区间,因为其他区间可以组合为它,所以没有啥子意义。只需要找出这些不重复的x条边,那么答案就是2的x次方。然后再说如何找这个重复的区间。cx大神是把区间的给端点的建边,不是特别懂,就是建图然后dfs一遍,数边数。正解用的并查集,六飞了,懂了过后写起来爆简单。就是检查区间头尾是不是一个爸爸,是的话就说明重复的,不是的话,x++。

修改后代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#define p 1000000007 
#define MN 100005
using namespace std;
int ans=1,n,m,l,r,a[MN],fa[MN],tot,lf,rf;
int findfather(int a)
{
    if(fa[a]==a) return a;
    return fa[a]=findfather(fa[a]);
}
int main()
{
    freopen("seg.in","r",stdin);
    freopen("seg.out","w",stdout);
    cin>>n>>m;
    for(int i=1;i<=n+1;i++) fa[i]=i;
    for(int i=1;i<=m;i++) 
    {
        cin>>l>>r;
        lf=findfather(l);
        rf=findfather(r+1);
        if(lf!=rf)
        fa[lf]=fa[rf],tot++;
    }
    while(tot)
    {
        ans=(ans*2)%p;
        tot--;
    }
    cout<<ans<<endl;
    return 0;
}

来自风平浪静的明天
【题目描述】
冬眠了五年,光终于从梦中醒来。
千咲、要,大家都在。
隐约记得“昨天”的海船祭,爱花意外成为贡女,沉入海底。
海面冰封,却有丝丝暖流在冰面之下涌动。
此时,爱花沉睡在祭海女神的墓地。她的胞衣在一点点脱落,化作一簇簇暖流,夹杂着她的感情,向海面上涌去。
爱花,你在哪里?
五年之后,纺已经成为海洋学研究科的大学生。
在纺的帮助下,光得知了海面下海流的情况。//没有啥子意义的废话=-=单身狗受到重击
纺告诉光,暖流一旦产生,就会不断地向四周扩散,直到遇到海中的岩石。
红腹海牛,快告诉光,爱花在哪里。
纺帮你绘制了一张海流情况图,长度为N,宽度为M。
海很大,一边有沙滩,一边一望无际,但长度和宽度都不会超过300。沙滩是金黄色的,所以用Y表示。海是蓝色的,所以用B表示。暖流很暖和,所以用H表示
海中有大大小小的石头。石头很危险,所以用X表示
光相信自己一定能找到爱花(爱花的位置只有一种可能)
【输入格式】
第一行包括两个整数N,M。
接下来N行,每行M个字符。
【输出格式】
仅一行,表示爱花的位置(如果你无能为力,请输出 -1 ,只要你尽力,光不会责怪你)
【样例输入】
5 5
YYYHB
YYHHH
YHHXB
BBHBB
BBBBB
【样例输出】
2 3
【数据范围】
对于30%的数据,n,m<=10
对于70%的数据,n,m<=100
对于100%的数据,n,m<=300
【样例解释】
在(2,3)出现第一个H后,经过3s后,出现样例输入的地图。
P.S. Mushroom拜托他GF出的这题= =

题解:

可怕,直接暴力的BFS居然还是正解=-=。然饿,TLE了一个点,后面说。啊,其实这道题特别想之前学过的水的漫延,对伐。只不过是给了你某时刻暖流的漫延情况,喊你退回去起点而已。所以说标答给了一个从边界开始退回去的方法,然饿,有多个答案:

BBHHXX

XXXHXX

XXHHXH   哈!浅蓝色答案一!

XHHHHH   哟!浅蓝色答案二!

XHXHHH

BHHHHH

BBHHHB

BBBHBB

BBBBBB

所以说啊,这个方法算出来就是有多个答案,当然你也可以知道答案,因为答案一定是周围暖流最多的,因为多个答案出现的原因是周围石头作为边界没有计算。

然后,我当时想的,就是暴力模拟,列举每一个H点作为开始点,然后bfs一层一层的搜下去,遇到B就return ;因为这是不成立的,其他的不能走continue,能走就进队。然后需要加一个时间节点,就是标记一下这一层是不是完了,然后一层漫延完,再检查有没有够H的数量,刚好,那就输出起始坐标,否则继续。(不存在比H数量多的情况,因为前面遇到B是不会走的,会return)。差不多就这样,最后一点是,queue用STL 的,会TLE一个点,但是手写就不存在这个问题了。啊,好像STL库里面都要比手写的慢一点。以后暴力最好手写。

修改后代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#define MN 305
using namespace std;
int dire[2][5]={{0,1,-1,0,0},{0,0,0,1,-1}};
int n,m,num,num0,hx,hy,fx,fy;
int qx[MN*MN],qy[MN*MN];
bool flag=false,visited[MN][MN];
char a[MN][MN],b[MN][MN];
bool can(int x0,int y0)
{
    if(a[x0][y0]==H||b[x0][y0]!=B) return true;
    return false;
}
void bfs(int x0,int y0)
{
    int sx,sy,x,y;
    hx=hy=1;
    fy=2,fx=3;
    qx[1]=x0;
    qx[2]=-100;
    qy[1]=y0;
    while(hy!=fy)
    {
        sx=qx[hx]; hx++;
        sy=qy[hy]; hy++;
        for(int i=1;i<=4;i++)
        {
            x=sx+dire[0][i];
            y=sy+dire[1][i];
            if(visited[x][y]) continue ;
            if(!can(x,y)) return ;
            if(a[x][y]==H)
            {
                num--;
                qx[fx]=x;
                qy[fy]=y;
                fx++,fy++;
                visited[x][y]=true;
            }
        }
        if(qx[hx]==-100)
        {
            hx++;
            qx[fx]=-100;
            fx++;
            if(num==0) {flag=true; return ;}
        }
    }
}
int main()
{
    freopen("calm.in","r",stdin);
    freopen("calm.out","w",stdout);
    cin>>n>>m;
    getchar();
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
      {
          a[i][j]=getchar();
          if(a[i][j]==H) b[i][j]=B,num0++;
          else b[i][j]=a[i][j];
      }
      getchar();
    }
      
    for(int i=1;i<=n;i++)
       for(int j=1;j<=m;j++)
       {
           if(a[i][j]==H)
       {
              memset(visited,false,sizeof(visited));
              memset(qx,0,sizeof(qx));
              memset(qy,0,sizeof(qy));
              visited[i][j]=true;
              num=num0-1;
              bfs(i,j);//暴力模拟 
              if(flag) {cout<<i<< <<j<<endl; return 0;}
       }
       }
       
       cout<<-1<<endl;
    return 0;
}

2016.10.31.第54套(集训1)