codeforces --- Round #244 (Div. 2) B. Prison Transfer

B. Prison Transfer

The prison of your city has n prisoners. As the prison can't accommodate all of them, the city mayor has decided to transfer c of the prisoners to a prison located in another city.

For this reason, he made the n prisoners to stand in a line, with a number written on their chests. The number is the severity of the crime he/she has committed. The greater the number, the more severe his/her crime was.

Then, the mayor told you to choose the c prisoners, who will be transferred to the other prison. He also imposed two conditions. They are,

  • The chosen c prisoners has to form a contiguous segment of prisoners.
  • Any of the chosen prisoner's crime level should not be greater then t. Because, that will make the prisoner a severe criminal and the mayor doesn't want to take the risk of his running away during the transfer.

Find the number of ways you can choose the c prisoners.

Input

The first line of input will contain three space separated integers n (1 ≤ n ≤ 2·105), t (0 ≤ t ≤ 109) and c (1 ≤ c ≤ n). The next line will contain n space separated integers, the ith integer is the severity ith prisoner's crime. The value of crime severities will be non-negative and will not exceed 109.

Output

Print a single integer — the number of ways you can choose the c prisoners.

Sample test(s)
Input
4 3 3
2 3 1 1
Output
2
Input
1 1 1
2
Output
0
Input
11 4 2
2 2 0 7 3 2 2 4 9 1 4
Output
6
【题目链接】
http://codeforces.com/contest/427/problem/B
【题目大意】
某市的监狱犯人关满了,需要从n个囚徒中选择c个转移至另外一个监狱,监狱长给出了选择的两个要求,让你计算有多少种方法然后输出方法数。
要求:
1.所选的囚徒必须都是连续的;
2.所选的囚徒中,每个囚徒的犯罪严重程度不超过t。
【题目分析】
首先看懂题后,心中大喜,这么简单的题,二话不说就写代码,当时都没多考虑,写完测试数据都没测就提交了,OK,初判过了,然后就做下一题,最后可想而知,超时了。
还是太粗心了,时间复杂度都没分析。附上比赛时提交的代码:
比赛代码(超时):
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
int main()
{
    int n,t,c;
    int i,j,k;
    int flag;
    int cnt=0;
    scanf("%d%d%d",&n,&t,&c);
    int a[200010];
    for(i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(i=c;i<=n;i++)
    {
        flag=0;
        for(j=0;j<c;j++)
        {
            if(a[i-j]>t)
            {
                flag=1;
                break;
            }
        }
        if(flag==0)
            cnt++;
    }
    printf("%d
",cnt);
    return 0;
}

好吧,比赛结束了,rating也掉了,心也上得差不多了,那就再来重新总结一下,再做一遍。

既然是超时,那就优化呗。

一开始我想到了上面的代码每次比较的时候基本上都要c次的回溯,时间浪费的太多了,这就好比字符串的模式匹配的BF算法一样,那何不将BF算法像KMP一样优化一下呢(扯远了),不需要每次都回溯,每次判断的时候如果遇到了一个大于t的数,那么接下来的c个都不能和他去新监狱里愉快的玩耍了,所以接下来的这c个根本就没有回溯的必要,所以又有了一下的SB代码:

代码(仍然超时):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
int main()
{
    int n,t,c;
    int i,j,k;
    int flag;
    int cnt=0;
    scanf("%d%d%d",&n,&t,&c);
    int a[200010];
    for(i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(i=c;i<=n;)
    {
//        k=i;
        flag=0;
        for(j=0;j<c;j++)
        {
            if(a[i-j]>t)
            {

                flag=1;
                i+=c;
                break;
            }
        }
        if(flag==0)
            {cnt++;i++;
//            cout<<k<<"  "<<a[k]<<endl;
            }
    }
    printf("%d
",cnt);
    return 0;
}

提交后一看,还超时,好吧,继续优化,我又想到其实如果判断了k个后,如果这k个都是小于t的(以下简称合法的),那么我就不用回溯了,直接判断当前这个数是否合法,合法那么方法就加1,遇到不和法的数就再次开始直到连续合法数达到c,那就可以继续以上的统计了,我就用一个变量full来统计连续合法数的个数。

代码(超时):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
int main()
{
    int n,t,c;
    int i,j,k;
    int flag;
    int cnt=0;
    int full=0;
    scanf("%d%d%d",&n,&t,&c);
    int a[200010];
    for(i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(i=c;i<=n;)
    {
//        k=i;
        flag=0;
    if(full>=c)
        {
            if(a[i]<=t)
            {
                cnt++;
                i++;
                continue;
            }
            else
            {
                flag=1;
                i+=c;
                full=0;
                continue;
            }
        }
    else
    {
        for(j=0;j<c;j++)
        {
            if(a[i-j]>t)
            {

                flag=1;
                i+=c;
                full=0;
                break;
            }
            else
                full++;
        }
    }
        if(flag==0)
            {cnt++;i++;
//            cout<<k<<"  "<<a[k]<<endl;
            }
    }
    printf("%d
",cnt);
    return 0;
}

提交一看,What?还超时,我不得不怀疑我的方法的正确性了,在吃饭的路上我独自想了一下,突然发现这就是一个简单的区间统计吗?只用扫描一遍统计出每一段数值都不超过t的区间有多少个不就可以了吗。

AC代码:

#include<iostream>
using namespace std;
int main()
{
    int n,t,c;
    int i,j,k;
    int ans=0;
    int cnt=0;
    cin>>n>>t>>c;
    for(i=0;i<n;++i)
    {
        cin>>k;
        if(k>t)
        {
            if(cnt>=c)
                ans+=cnt-c+1;
            cnt=0;
        }
        else
            cnt++;
    }
    if(cnt>=c)    //对最后一个cnt进行判断
        ans+=cnt-c+1;
    cout<<ans<<endl;
    return 0;
}

经过了一道水题的这般周折以后,我还是悟到了一些东西,codeforces div.2中的比赛一般是考理解英语题目的能力和解决一些小问题的能力,算法一般考的很少,但是这些方法都很巧妙,所以还是能锻炼编程能力的。

原文地址:https://www.cnblogs.com/crazyacking/p/3707989.html