信息学科普基地Noip2017%你题第二试总结

Author Avatar
Llf0703 9月 19, 2018
  • 在其它设备中阅读本文章

题目

题解

1.爬山(mountain)

水题,直接搜索即可。

需要注意的是要用一个vis数组记录是否走过,否则在相等的时候会在两者之间不停地走下去,最后爆炸。我就是因为这个原因只有90分

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>

using namespace std;

int h[205][205],n,m,sx,sy,fx[4]={0,1,0,-1},fy[4]={1,0,-1,0},ans;
bool vis[205][205];

inline int read()
{
    char ch=getchar();
    int f=1,x=0;
    while (ch<'0' || ch>'9')
    {
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0' && ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return f*x;
}

void dfs(int x,int y)
{
    vis[x][y]=1;
    bool hier=0;
    for (int i=0;i<4;i++)
    {
        int tx=x+fx[i],ty=y+fy[i];
        if (!vis[tx][ty] && h[tx][ty]>=h[x][y])
        {
            hier=1;
            dfs(tx,ty);
        }
    }
    if (!hier) ans=max(ans,h[x][y]);
    return;
}

int main()
{
    freopen("mountain.in","r",stdin);
    freopen("mountain.out","w",stdout);
    n=read(); m=read(); sx=read(); sy=read();
    memset(h,-1,sizeof(h));
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
            h[i][j]=read();
    dfs(sx,sy);
    printf("%d",ans);
    return 0;
}

2.字符串距离(string)

这道题暴力就可以70 要什么正解系列

这道题的正解其实是找规律。显而易见,对于 $ T $ 中每个元素,它都会与 $ S $ 中的部分元素进行比较,只要确定与哪些元素比较即可。

下面就来推一下:

S: a a a b b
T: b a b
a[1]: {1}
a[2]: {2}
a[3]: {3}

这是最开始的情况,其中a[i]代表 $ T $ 中第i个元素需要在 $ S $ 中比较哪几个元素。

S: a a a b b
T:   b a b
a[1]: {1,2}
a[2]: {2,3}
a[3]: {3,4}

往后推一步。

S: a a a b b
T:     b a b
a[1]: {1,2,3}
a[2]: {2,3,4}
a[3]: {3,4,5}

其实这个测试数据有点误导性,我一开始就搞错了。下面换一组数据再来推一下:

S: a a a b b b b
T: b a b b a
a[1] {1,2,3}
a[2] {2,3,4}
a[3] {3,4,5}
a[4] {4,5,6}
a[5] {5,6,7}

完了。规律应该是显而易见了吧,对于 $ S $ 中第 $ i $ 个元素,它要与 $ T $ 中第 $ i $ ~ $ i+S.length()-T.length() $ 这些元素进行比较。之所以说容易被误导就是搞错成 $ i $ ~ $ i+T.length()-1 $ 进行比较。

我们可以用一个变量sum来记录当前长度为 $ S.length()-T.length()+1 $ 的区间内 $ a $ 的个数。当我们把 $ T $ 中元素往后推时,将区间同时后移一位。而区间中 $ a $ 的个数的变化只会发生在第 $ i-1 $ 和 $ i+T.length() $ 这两个元素中发生,只需要对应加减即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

int main()
{
    freopen("string.in","r",stdin);
    freopen("string.out","w",stdout);
    string s,t;
    cin>>s>>t;
    int ls=s.length(),lt=t.length();
    int sum=0,ans=0,len=ls-lt+1;
    for (int i=0;i<len;i++)
        if (s[i]=='a') sum++;
    int j=0;
    if (t[j]=='a') ans+=len-sum;
    else ans+=sum;
    for (int i=len;i<ls;i++)
    {
        if (s[i]=='a') sum++;
        if (s[i-len]=='a') sum--;
        j++;
        if (t[j]=='a') ans+=len-sum;
        else ans+=sum;
    }
    printf("%d",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

跳石头(stone)

先 STO cyc大佬。

据大佬们说两次LIS+贪心就可以80分,但我却爆0了,果然还是太蒻了。

cyc大佬给我们讲了一个玄学的做法,至今也没有证明,不过就是能过这道题。而且仔细想想好像也挺有道理,也举不出反例来,所以就把它暂时当作正解吧。

解法就是维护两个数组s1[]s2[]。对于每一个石头s[i],如果它大于s1[]中的最后一个元素,则把他加在后面;否则就在s1[]中找到一个它能够替换的值,并把它放进去。

能被替换的元素就是刚好要比当前石头s[i]大一些,而且加进去以后不会影响s1[]中单调性的元素,即 被替换元素 的前一个元素要小于s[i]。因为可以保证的是s1[]一定单增,所以这个被替换元素可以通过二分来查找到。

然后,将当前石头放入s1[],然后将被替换的元素当作当前的石头与s2[]进行操作,与s1[]的操作同理。

最后,s1[]s2[]中元素个数之和即为答案。


我刚写完cyc大佬就给我讲了证明方法,虽然不能严格证明,但是大致可以明白这个算法的道理。

大致这些操作可以分为两种情况:

对于每一个石头s[i],如果它大于s1[]中的最后一个元素,则把他加在后面

这种情况下就可以将答案+1,没有任何问题

否则就在s1[]中找到一个它能够替换的值,并把它放进去

我最开始就产生了疑问:s[i]的位置是肯定在此前就加进去的元素的后面的,如果用它替换掉中间一个值怎么行呢?

其实,在这种情况下,答案是没有变化的。所以,我们其实并没有更新答案,这就是理解这种方法的精髓所在。

当我们将s[i]给替换到 $ left $ 这个位置时,可以保证 $ 1..left $ 这个区间内是真真正正确实可以跳到的石头。前面说过,我们并没有更新答案,所以后面那些元素根本不用管。

在最后一个元素被替换掉之前,后面即使再加入元素也只是之前情况下的答案+1,和当前情况没有任何关系,所以最后就可以取整个数组的元素个数作为答案;如果当前最后一个元素后来被替换掉了,则证明当前情况并不是最优解,而替换最后一个也不会产生顺序上的任何问题。

而第二个数组操作和第一个数组相同,证明也就同理了。

综上,这个算法是正确的,并不是玄学的。不得不感叹这个算法的巧妙性和cyc大佬的强大。


先放我的长但是自认为好懂些的代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

inline int read()
{
    char ch=getchar();
    int f=1,x=0;
    while (ch<'0' || ch>'9')
    {
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0' && ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return f*x;
}

int s[1005],n,m,ans,s1[1005],s2[1005];

int main()
{
    freopen("stone.in","r",stdin);
    freopen("stone.out","w",stdout);
    n=read();
    for (int i=1;i<=n;i++) s[i]=read();
    int cnt1=0,cnt2=0;
    s1[++cnt1]=1;
    ans=1;
    for (int i=2;i<=n;i++)
    {
        int x=s1[cnt1];
        if (s[i]>=s[x]) s1[++cnt1]=i,ans++; //直接放入,在这里就可以顺便统计ans
        else
        {
            int left; //被替换的元素在s1中的便号,注意不是s中的
            int l=1,r=cnt1;
            while (l<=r) //二分查找被替换元素
            {
                int mid=(l+r)/2;
                if (s[s1[mid]]>s[i]) left=mid,r=mid-1;
                else l=mid+1;
            }
            int leftpos=s1[left]; //这是被替换元素在s中的编号,用来下面比较大小
            s1[left]=i; //将当前石头替换进去
            int y=s2[cnt2];
            if (s[leftpos]>=s[y]) s2[++cnt2]=leftpos,ans++; //直接放入,统计ans
            else
            {
                int left2; //s2中被替换元素的编号
                int l=1,r=cnt2;
                while (l<=r)
                {
                    int mid=(l+r)/2;
                    if (s[s2[mid]]>s[leftpos]) left2=mid,r=mid-1;
                    else l=mid+1;
                }
                s2[left2]=leftpos; //替换。因为不需要统计更多的,所以到这就行了
            }
        }
    }
    printf("%d",ans);
    return 0;
}

下面放一个cyc大佬极简代码,据他说他还有更短的版本。 %cyc

// By Dalao cyc,%cyc
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
#define N 5005
int n, i, x, t[N]; 
int q[N][N];

void up(int p, int x) {
    if (x >= q[p][t[p]]) { q[p][++t[p]] = x; return; }
    int l = 1, r = t[p], mid, u;
    while (l <= r) {
        if (q[p][mid = (l + r) >> 1] > x) {
            u = mid; r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    up(p + 1, q[p][u]), q[p][u] = x;
}

int main() {
    scanf("%d", &n);
    for (i = 1; i <= n; ++ i) scanf("%d",&x), up(1,x);
    for (int i = 1; ; ++ i) {
        t[i] += t[i - 1];
        if (t[i] == n) break;
    }
    printf("%d", t[2]);
    return 0;
}

显而易见,cyc大佬还要统计后续数组的值,时间效率上肯定会差点,空间也会开大点。但是代码又短又巧妙,实在是太强辣!

总结

我个人的第一次提交只有160,得分为90+70+0,使用的算法的最高得分为100+70+80=250.

各题总结:

  1. 第一题情况考虑不完整,想到了相等的请况却没想到后续的问题
  2. 第二题没有找出规律,最后暴力没有失分,没什么非知识性的问题
  3. 第三题想到80分做法但写错,应该是对LIS算法的掌握不够熟练

总体失分原因:

  1. 对细节的思考不够到位
  2. 错误地追求每题AC,导致第二题花了很长时间没想到正解就心态比较爆炸,可能也是第三题爆炸的原因
  3. 对于部分算法掌握还不够熟练,人笨就要多刷题

总体来说这套题打的不理想,但是也发现了很多问题(我太菜了),所以还是很有价值的。

以上。

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
This work is licensed under a CC BY-NC-SA 4.0 International License.

本文链接:https://llf0703.com/p/contest-summary-1.html