[Offer收割]编程练习赛10简略题解

题目1 : 出勤记录I(水题)

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

小Hi的算法课老师每次上课都会统计小Hi的出勤记录。迟到会被记录一个L,缺席会被记录一个A,按时上课会被记录一个O。

一学期结束,小Hi的出勤记录可以看成是一个只包含LAO的字符串,例如"OOOOLOOOLALLO……"。

如果小Hi整学期缺席不超过1次,并且没有连续3次迟到,小Hi的出勤记录就算合格。  

现在给出小Hi的出勤记录,你能判断他是否合格么?

输入

输入第一行包含一个整数T(1 <= T <= 10),代表测试数据的组数。  

以下T行每行一个程度不超过100的字符串S,代表小Hi的出勤记录。

输出

对于每一份出勤记录,输出YES或者NO表示该份记录是否合格。

样例输入
3
LLOLLALL  
OLLLOOOO  
OOAAOOOO
样例输出
YES  
NO  
NO
判断是否有“LLL”出现或者"A"出现的个数超过两次 输出NO,其他为YES

题目2 : 出勤记录II(dp)

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

小Hi的算法课老师每次上课都会统计小Hi的出勤记录。迟到会被记录一个L,缺席会被记录一个A,按时上课会被记录一个O。

一学期结束,小Hi的出勤记录可以看成是一个只包含LAO的字符串,例如"OOOOLOOOLALLO……"。

如果小Hi整学期缺席不超过1次,并且没有连续3次迟到,小Hi的出勤记录就算合格。  

现在给出字符串的长度N,小Hi想知道长度为N的出勤记录中,合格的记录总共有多少种。  

例如长度为3的合格出勤记录有19种:OOO OOL OOA OLO OAO LOO AOO OLL OLA OAL LOL LOA AOL LLO LAO ALO LLA LAL ALL。

输入

一个整数N(1 <= N <= 100000)。

输出

长度为N的合格记录总数。由于结果可能很大,你只需输出结果模109+7的余数。

样例输入
3

样例输出
19
可以用dp[i][A][L]表示长度为N序列的串,“A”出现的个数为A, 末尾连续“L”的个数为L个
if(s[i+1] == 'L' && L <= 2 && L >= 1) dp[i+1][A][L] += dp[i][A][L-1]
if( s[i+1] == 'A') dp[i+1][1][0] += dp[i][0][L]
if( s[i+1] == 'O') dp[i+1][A][0] += dp[i][A][L]
#include <iostream>
#include <cstdio>
#include <string>
#include <map>

using namespace std;
const int MAXN = 2e5+10;
int A[MAXN], cnt[MAXN];
int n;
typedef long long int LL;
LL k;
map<int, int>Map;
LL slove(LL ans){
    LL ret = 0, sum = 0;
    for(int i = 0, j = 0; i < n; i++){
        while(j < n && sum+cnt[A[j]] <= ans)    {
            sum += cnt[A[j]];
            cnt[A[j]]++;
            j++;
        }
        ret += j-i;
        cnt[A[i]]--;
        sum -= cnt[A[i]];
    }
    return ret;
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &k);
        for(int i = 0; i < n; i++){
            scanf("%d", &A[i]);
            Map[A[i]] = i;
        }
        for(int i = 0; i < n; i++) A[i] = Map[A[i]];
        LL L = -1, R = 1LL*n*(n+1);
        while(L + 1 < R){
            LL mid = (L+R)>>1;
            if(slove(mid) < k) L = mid;
            else R = mid;
        }
        printf("%lld\n", R);
    }
    return 0;
}

题目3 : 区间价值(二分+双指针)

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

给定n个数A1...An,小Ho想了解AL..AR中有多少对元素值相同。小Ho把这个数目定义为区间[L,R]的价值,用v[L,R]表示。

例如1 1 1 2 2这五个数所组成的区间的价值为4。

现在小Ho想知道在所有的的v[L,R](1 <= L <= R <= n)中,第k小的值是多少。

输入

第一行一个数T(T<=10),表示数据组数。

对于每一组数据:

第一行两个数n,k(1<=n<=200,000,1<=k<=n*(n+1)/2)

第二行n个数A1…An(1<=Ai<=1,000,000,000)

输出

一个数表示答案。

样例输入
2
4 7
1 1 2 3
3 6
100 100 100
样例输出
0
3


二分答案,用双指针求出[1~n]区间 <= ans的个数

#include <iostream>
#include <cstdio>
#include <string>
#include <map>

using namespace std;
const int MAXN = 2e5+10;
int A[MAXN], cnt[MAXN];
int n;
typedef long long int LL;
LL k;
map<int, int>Map;
LL slove(LL ans){
    LL ret = 0, sum = 0;
    //ret 小于等于ans区间的个数
    for(int i = 0, j = 0; i < n; i++){
        while(j < n && sum+cnt[A[j]] <= ans)    {
            sum += cnt[A[j]];
            cnt[A[j]]++;
            j++;//指针j向右移动
        }
        ret += j-i;//求出满足条件区间的 个数  
        cnt[A[i]]--;//指针i向右移动
        sum -= cnt[A[i]];
    }
    return ret;
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &k);
        for(int i = 0; i < n; i++){
            scanf("%d", &A[i]);
            Map[A[i]] = i;
        }
        for(int i = 0; i < n; i++) A[i] = Map[A[i]];//使A[i]处在[0~n-1]
        LL L = -1, R = 1LL*n*(n+1);
        while(L + 1 < R){
            LL mid = (L+R)>>1;
            if(slove(mid) < k) L = mid;
            else R = mid;
        }
        printf("%lld\n", R);
    }
    return 0;
}

 

 


posted on 2017-03-19 21:46  disppr  阅读(445)  评论(0编辑  收藏  举报