ACM新手周赛 3

题解

A - 群里又来新人了(补题)

题目:执行n组命令,对一个队伍进行加入新人,去掉进去的时间最长的人,查询实力处于中间的人。

  • 注意点:其中,中间是这样定义的:floor(人数/2)+1。即,3个人时,中间就是2。4个人时,中间就是3。
  • 解题思路:队列+二分。具体:用队列解决删除操作中“去掉进群时间最长的人”的问题,然后用数组存储队列,每次插入和删除都要调整数组中的顺序,二分搜索用来查找插入数应在的位置
    对应代码:
#include <cstdio>
#include <cstring>
#include <queue>
#include <iostream>
using namespace std;
queue<int> Q;
const int maxn = 1e4 + 10;
int num[maxn];
int sum;
int binarySearch(int x)//二分查找x在num中的下界
{
    int L = 0, R = sum ;
    while (L < R)
    {
        int M = (L + R) / 2;
        if (num[M] >= x)
            R = M;
        else
            L = M + 1;
    }
    return L;
}
void Insert(int x)//插入x
{
    int k = binarySearch(x);//二分查找插入位置
    for (int i = sum + 1; i > k ; i--)
        num[i] = num[i - 1];//将大于x的后移
    num[k] = x;
    sum++;
}
void Delete(int x)//删除x
{
    int k = binarySearch(x);
    for (int i = k; i < sum; i++)
        num[i] = num[i + 1];//将大于x的前移
    sum--;
}
int main()
{
    int n, cnt = 1;
    while (~scanf("%d", &n))
    {
        while (!Q.empty())
            Q.pop();
        sum = 0;
        printf("Case #%d:\n", cnt++);
        for (int i = 0; i < n; i++)//读入以及执行操作
        {
            char ch[10];
            int x;
            scanf("%s", ch);
            if (ch[0] == 'i')
            {
                scanf("%d", &x);
                Q.push(x);
                Insert(x);
            }
            else if (ch[0] == 'o')
            {
                int tnum = Q.front();
                Q.pop();
                Delete(tnum);
            }
            else
            {
                printf("%d\n", num[sum / 2]);
            }
        }
    }
    return 0;
}

B - 49子序列(补题)

题目:给一个数字N,求1~N有多少个数字的序列包含“49”子序列。

  • 数位数组(类似题目如“不要62”)
  • 思路:本来直接套“不要62”的模板,求出不含49的,然后减去······但是C++的输入输出不支持我的想法,然后找到了这个强行实现这种想法的输入输出方法
#include <iostream>
#include <cstdio>
#define LL long long
using namespace std;
LL dp[23][2];
int num[23];
LL dfs(int pos,bool state,bool limit)
{
    if(!pos) return 1;
    if(!limit && dp[pos][state]) return dp[pos][state];
    int up=limit?num[pos]:9;
    LL ans=0;
    for(int i=0; i<=up; ++i)
        if(!(state&&i==9))
            ans+=dfs(pos-1,i==4,limit&&i==num[pos]);

    if(!limit) dp[pos][state]=ans;
    return ans;
}
LL solve(LL n)
{
    int s=0;
    while(n!=0)
    {
        num[++s]=n%10;
        n/=10;
    }
    return dfs(s,false,true);
}
int main()
{
    LL n;
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld",&n);//这里“%lld”很重要!!
        printf("%lld\n",n-solve(n)+1);
    }
    return 0;
}

C - 约翰的奶牛

题目:给出一组数,求出每个指定区间里的最大的数和最小的数的差。

  • 线段树
  • 求区间最大,区间最小
  • 对应代码
#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
const int maxn = 50000;
int mx[maxn*4], mm[maxn*4];
void buildtree(int l, int r, int k)
{
    if(l == r)
    {
        scanf("%d", &mx[k]);
        mm[k] = mx[k];
        return;
    }
    int mid = (l+r)/2;
    buildtree(l, mid, 2*k);
    buildtree(mid+1, r, 2*k+1);
    mx[k] = max(mx[2*k], mx[2*k+1]);
    mm[k] = min(mm[2*k], mm[2*k+1]);
}
int query1(int l, int r, int ql, int qr, int k)
{
    int mid = (l + r)/2;
    if(l == ql && r == qr)
        return mx[k];
    if(ql > mid)
        return query1(mid+1, r, ql, qr, 2*k+1);
    else if(qr <= mid)
        return query1(l, mid, ql, qr, 2*k);
    else
        return max(query1(l, mid, ql, mid, 2*k), query1(mid+1, r, mid+1, qr, 2*k+1));
}
int query2(int l, int r, int ql, int qr, int k)
{
    int mid = (l + r)/2;
    if(l == ql && r == qr)
        return mm[k];
    if(ql > mid)
        return query2(mid+1, r, ql, qr, 2*k+1);
    else if(qr <= mid)
        return query2(l, mid, ql, qr, 2*k);
    else
        return min(query2(l, mid, ql, mid, 2*k), query2(mid+1, r, mid+1, qr, 2*k+1));
}
int main()
{
    int n, m, a, b;
    scanf("%d%d", &n, &m);
    buildtree(1, n, 1);
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d", &a, &b);
        int c1 = query1(1, n, a,b, 1);
        int c2 = query2(1, n, a,b, 1);
        printf("%d\n", c1 - c2);
    }
    return 0;
}

D - 继承人(补题)

题目:有n种秘籍,每种秘籍都能提升某个人一定的能力,对a,b两个人培养,在尽量公平的基础上来重点培养a,求如何分配秘籍。

  • 注意点:尽量公平的基础上来重点培养a是指在不可能平均分配的情况下,尽量使两人获得的能力差别最小,而a使占据较多的那一个。
  • 背包问题
  • 思路:符合多重背包的特点,不过直接转换成0-1背包可能会好做一些
  • 对应代码
//这个题解的办法是把题目转换成0-1背包
//使B继承人的能力在不超过总体一半的情况下最大
#include<iostream>
#include<string.h>
#include<cstdio>
using namespace std;
int v[5005];
int dp[250005];
int a, b;
int n;
int main()
{
    while (~scanf("%d", &n) && n>0)
    {
        memset(dp, 0, sizeof(dp));

        int sum = 0;
        int num = 1;
        for (int i = 1; i <= n; i++)
        {
            scanf("%d%d", &a, &b);
            //这里把每种秘籍有多本的问题处理了
            while (b--)
            {
                v[num++] = a;
                sum += a;
            }
        }
        for (int i = 1; i < num; i++)
            for (int j = sum / 2; j >= v[i]; j--)
                dp[j] = max(dp[j - v[i]] + v[i], dp[j]);

        printf("%d %d\n", sum - dp[sum / 2], dp[sum / 2]);
    }
    return 0;
}
发布了47 篇原创文章 · 获赞 4 · 访问量 1301

猜你喜欢

转载自blog.csdn.net/listenhhh/article/details/98473581