题解
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;
}