北京信息科技大学第十三届程序设计竞赛暨ACM选拔赛题解
A lzh的蹦床
sky被lzh抓到了一间密室,密室里放了一排紧密相邻的蹦床 ,lzh声称自己最喜欢欣赏sky跳跃在空中的姿态,并示意他可以开始跳了。sky的跳跃方向是单向向前的,当然如果他落到一个蹦床上则会继续向前弹跳,直到sky落到了坚硬可靠的地面上,他才可以结束这一趟弹跳,lzh当然允许sky自己选择每一趟跳跃开始的蹦床。
每一个蹦床都有它的弹力值 bi,这决定了从该蹦床起跳后的落点–通俗来说,从i 蹦床起跳将会落到 i+bi蹦床上(假如这个位置有蹦床)。但由于sky心中有怨气,他便不会爱惜的使用lzh珍藏已久的蹦床,可惜的是,他每一次的起跳都会造成当前蹦床的弹力值 -1,但幸好lzh的蹦床是为了sky的安全设计的,也就是说,这个蹦床的弹力值不会低于 1 。sky饿的不行了,他想尽早回宿舍,他观察到lzh对他进行距离为 1 的跳跃(也就是从 ai 跳到 ai+1)不怎么感兴趣,他打算使所有蹦床弹力值都变成 11 ,这样他就可以回宿舍吃饭了,请你帮帮他,计算出他最少需要跳多少趟。
输入描述:
第一行输入一个正整数 T(T≤500),表示测试用例的个数,接下来 T 个用例,
每个用例第一行一个正整数 n(1≤n≤5000),表示蹦床的数量;
第二行有 n 个正整数 bi(1≤bi≤1e9),表示每个蹦床的弹力值。
输出描述:
对于每组用例,输出一个整数,表示sky最少需要跳多少趟
示例:
输入:
3
7
1 4 2 2 2 2 2
2
2 3
5
1 1 1 1 1
输出:
4
3
0
代码:
#include <bits/stdc++.h>
#define mem(a) memset(a,0,sizeof(a))
int a[10050], b[10050], c[10050];
int main()
{
int t; si(t);
while (t--)
{
int n; si(n); ll ans = 0; mem(b); mem(c);
for (int i = 1; i <= n; i++) si(a[i]);
for (int i = 1; i <= n; i++)
{
c[i] += c[i - 1]; b[i] += c[i];
ans += max(0, a[i] - b[i] - 1); b[i] = max(b[i], a[i] - 1);
b[i + 1] += b[i] - a[i] + 1;
if (a[i] > 1)
c[i + 2]++, c[min(i + a[i]+1, n + 1)]--;
}
printf("%lld\n", ans);
}
return 0;
}
B 所谓过河
sky一到饭点便冲向了食堂,刚学会魔法的lzh见状,在他和食堂之间变出了一条无限长的大河( 宽当然是有限的 )。
幸运的是,lzh的魔法还不够精妙,长河中还留有 NN 个残余的圆形石头(重叠部分厚度不计),
sky可以踩在上面过河,当然他不能在石头之间跳远,只有两个圆形石头相交(有重叠部分或紧贴)他才能在这两块石头上来回移动,
同样他只能通过与岸边相交的石头才能上岸或者进入河中。 sky想知道他能不能到达河对面,请你帮帮他。
输入描述:
第一行包括两个整数 N ,H ,表示石头的个数和河的宽度(1≤N≤10000,1≤H≤1e9)。
sky所在的河岸边可以看作 的直线,河对面的岸可看作 的直线。
接下来 NN 行每一行包括三个整数 xi,yi,ri;分别表示每块石头的圆心坐标以及半径,保证所有石头的圆心都在河中。(0≤∣xi∣,∣yi∣≤1e9,1≤ri≤1e9)
输出描述:
如果sky能到河对面,输出”Yes“,反之输出”No“
示例:
输入:
2 3
1 1 1
1 3 1
输出:
Yes
代码:
#include <bits/stdc++.h>
#define si(a) scanf("%d",&a)
#define sf(a) scanf("%lf",&a)
int b[10010]; int fa[10010];
struct dian
{
double x, y, z;
bool friend operator <(dian a, dian b)
{
if(a.y!=b.y)
return a.y-a.z < b.y-b.z;
}
}a[10010];
bool check(dian a, dian b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)) <= a.z + b.z;
}
int main()
{
int n, h; si(n), si(h); bool f = 0;
for (int i = 1; i <= n; i++)
{
sf(a[i].x), sf(a[i].y), sf(a[i].z); fa[i] = i;
}
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++)
{
if (a[i].z >= a[i].y)
b[i] = 1;
if (a[i].y + a[i].z >= h)
if (b[i])
f = 1;
else
b[i] = 2;
}
for (int i = 1; i < n; i++)
for (int j = i + 1; j <= n; j++)
if (check(a[i], a[j]))
fa[j] = i;
for (int i = 1; i <= n; i++)
{
int now = i;
while (fa[now] != now)
now = fa[now];
if (b[i]*b[now] == 2)
f = 1;
}
f ? puts("Yes") : puts("No");
return 0;
}
C 旅行家问题1
lzh最近在学习旅行家问题,为了照顾太菜的他,学长给他出了一个一维的问题,好让他对生活抱有希望。在一个数轴上面,散落着 n 个城市,每个城市的坐标由 xi表示。
给定旅行家的坐标,他每走一个单位长度需要的时间为1s,求他访问所有城市需要的最小时间。
输入描述:
第一行包含两个正整数 n ,x (1≤n≤2e5,1≤x≤1e9)表示需要访问的城市数量以及lzh最初所在城市的坐标。
第二行包含 n 个正整数 xi (1≤xi≤1e9),表示要访问的城市的位置坐标。
输出描述:
输出一个正整数,代表旅行家访问所有城市需要的最短时间。
示例:
输入:
2 3
2 4
输出:
3
代码:
#include <bits/stdc++.h>
#define si(a) scanf("%d",&a)
int main()
{
int n, x, num, maxn=0, minn=1e9;
si(n), si(x);
while (n--)
{
si(num);
maxn = max(maxn, num), minn = min(minn, num);
}
if (x < minn)
printf("%d\n", maxn - x);
else if (x > maxn)
printf("%d\n", x - minn);
else
printf("%d\n", maxn - minn + min(x-minn,maxn-x));
return 0;
}
D 旅行家问题2
显然,身为旅行家,爬山是他不能回避的问题。地图上存在 N 座高度 hi,由于这些山实在是太高惹,他们的宽度和彼此之间的距离都可以忽略不计。每座山的山顶存在一个高为 xi 的梯子,
他可以通过这个梯子到达高度可及的山顶,通俗的来说从高山i的山顶,可以转移到 hj ≤hi+xi 的高山j的山顶上去,同时交付 xi的梯子使用费。如果他想达到当前山顶的梯子高度所不能及的高山,他便需要搭乘飞机并交纳 hj−hi的运输费。
旅行家从一号山出发,他需要访问所有高山并返回一号山,请你帮他设计一个路线,使他花费的费用最小。
输入描述:
第一行包含一个整数 N。(2≤N≤1e5) 接下来 N 行,第 i 行包括两个正整数 hi,xi (1 ≤ i ≤ N,0 ≤ hi,xi ≤ 1e9),表示第 i 个山的高度,山顶梯子的长度。
输出描述:
输出一个正整数,表示最小的花费。
示例:
输入:
3
1 9
2 1
4 1
输出:
11
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
pair <int, int> a[100010]; int n; ll ans = 0; cin >> n;
for (int i = 0; i < n; i++) cin>>a[i].first>>a[i].second, ans += a[i].second;
sort(a, a + n); int h = a[0].first + a[0].second;
for (int i = 1; i < n; i++) { if (a[i].first > h) ans += a[i].first - h; h = max(h, a[i].first + a[i].second); }
printf("%lld\n", ans);
return 0;
}
E 小菲和Fib数列
小菲喜欢上了Fibonacci数列,该数列的通项公式为f(1)= 1, f(2) = 1,f(i) = f(i-1)+f(i-2) (i≥3),他试图对该数列进行她独特的求和方式,即对数列中所有本质不同的二元组进行如下运算(%为取模运算),并对所有本质不同的二元组的运算结果进行加和。
注:本质不同的二元组为组内两个元素的下标是该数列元素下标的唯一组合。
输入描述:
输入一个正整数(1≤N≤5e4),表示数列元素的个数。
输出描述:
输出一个正整数,表示用小菲的运算方式得到的答案。
示例:
输入:
4
输出:
3
代码:
#include <bits/stdc++.h>
typedef long long ll;
#define si(a) scanf("%d",&a)
int a[50010], b[50010];
int main()
{
a[1] = a[2] = 1;
for (int i = 3; i <= 5e4; i++)
{
a[i] = (a[i - 2] + a[i - 1]) % 2;
b[i] = b[i - 1] + !a[i];
}
int n; si(n); ll ans = 0;
for (int i = 1; i <= n; i++)
if (!a[i]) ans += i - 1;
else ans += b[i - 1];
printf("%lld\n", ans);
return 0;
}
F 好玩的音乐游戏
小红最近迷上了一个好玩的音乐游戏——邦多利。她玩了一段时间之后,决定自己制作音游的谱面。于是,她打开了一个谱面制作器:
已知音符一共有2种类型:
1.点击(tap),用大写字母O表示。
2.滑动(也称粉键,flick),用大写字母X表示。
在同一时间如果有两个音符同时出现,需要用连接线把它们连接起来(除非他们是相邻的两个音符就不用连)。
小红输入了一些时间和该时间应出现的音符,请你将谱面打印出来。
ps:谱面一共有7个轨道可以用来放置音符!
输入描述:
第一行为两个正整数 n 和 m ,代表音符的数量以及歌曲的长度。(1≤n,m≤2000)接下来的 n 行,每行由一个字符串 si和两个正整数ti
,hi组成,分别代表音符的类型、音符出现的时间以及音符所在的音轨位置。(1≤ti≤m,1≤hi≤7,且 si一定是tap或flick中的一个单词)
保证输入合法,即不会在同一时间的同一个音轨出现两个以上音符,也不会同一时间出现超过2个音符。
输出描述:
一共 m+1 行字符串,用来表示谱面。谱面最底部有一行固定的字符串 ±------+ 表示谱面开始,这一行不计入歌曲总时间。
谱面开始以后,每一行表示一个时刻,一行中有 7 个字符(只可能是空格、大写字母O、大写字母X、短横线中的一种)被包含在两个竖线(|)之间表示当前时刻 7 个音轨的状态。
如果在同一时刻,相邻的两个音轨出现音符,则不需要再绘制短横线进行连接。
示例:
输入:
3 10
tap 2 1
tap 5 6
flick 2 4
输出:
| |
| |
| |
| |
| |
| O |
| |
| |
|O--X |
| |
+-------+
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
string s[N];
int n, m;
int main() {
cin >> n >> m;
s[0] = "+-------+";
for(int i=1; i<=m; i++) s[i] = "|-------|";
for(int i=0; i<n; i++) {
string tmp;
int t, h;
cin >> tmp >> t >> h;
if(tmp == "tap") s[t][h] = 'O';
else s[t][h] = 'X';
}
for(int i=1; i<=m; i++) {
for(int j=1; j<=7; j++) {
if(s[i][j] != '-') break;
s[i][j] = ' ';
}
for(int j=7; j>=0; j--) {
if(s[i][j] != '-') break;
s[i][j] = ' ';
}
}
for(int i=m; i>=0; i--) cout << s[i] << endl;
return 0;
}
G ranko的手表
ranko 的手表坏了,正常应该显示 xx:xx 的形式(4 个数字),比如下午 1 点半应该显示 13:30 ,但现在经常会有一些数字有概率无法显示。
ranko 在 t1时刻看了下时间,过了一段时间在 t2时刻看了下时间。她想知道, t1和 t2这两个时刻之间相距的时间的最大值和最小值是多少?
保证 t1在 t2之前(且 t1和 t2不等)。t1和 t2在同一天的 00:00 到 23:59 之间。
输入描述:
两行输入两个时间,为 xx:xx 的形式。其中 xx 为数字或者字符 ‘?’ ,问号代表这个数字没有显示。
保证输入是合法的。
输出描述:
一行输出两个整数,分别代表 t1和 t2相距时间的最小值和最大值(单位分钟)。
示例:
输入:
18:0?
2?:1?
输出:
121 319
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int j,i;
string s1,s2;
cin>>s1>>s2;
vector<int>v1,v2;
for(i=0;i<24*60;i++){
int h=i/60,m=i%60;
if((h/10==s1[0]-'0'||s1[0]=='?')&&(h%10==s1[1]-'0'||s1[1]=='?')&&(m/10==s1[3]-'0'||s1[3]=='?')&&(m%10==s1[4]-'0'||s1[4]=='?'))v1.push_back(i);
if((h/10==s2[0]-'0'||s2[0]=='?')&&(h%10==s2[1]-'0'||s2[1]=='?')&&(m/10==s2[3]-'0'||s2[3]=='?')&&(m%10==s2[4]-'0'||s2[4]=='?'))v2.push_back(i);
}
int mi=1e9,ma=0;
for(i=0;i<v1.size();i++){
for(j=0;j<v2.size();j++){
if(v1[i]<v2[j]){
ma=max(ma,v2[j]-v1[i]);
mi=min(mi,v2[j]-v1[i]);
}
}
}
cout<<mi<<" "<<ma<<endl;
}
H 字母收集
有一个 n∗m 的矩形方阵,每个格子上面写了一个小写字母。
小红站在矩形的左上角,她每次可以向右或者向下走,走到某个格子上就可以收集这个格子的字母。
小红非常喜欢 “love” 这四个字母。她拿到一个 l 字母可以得 4 分,拿到一个 o 字母可以得 3 分,拿到一个 v 字母可以得 2 分,拿到一个 e 字母可以得 1 分。
她想知道,在最优的选择一条路径的情况下,她最多能获取多少分?
输入描述:
1≤n,m≤500
接下来的 n 行 每行一个长度为 m 的、仅有小写字母构成的字符串,代表矩形方阵。
输出描述:
小红最大可能的得分。
示例:
输入:
3 2
ab
cd
ef
输出:
1
代码:
#include <bits/stdc++.h>
using namespace std;
#define si(a) scanf("%d",&a)
int a[510][510];
int main()
{
int n, m; si(n), si(m);
for(int i=1;i<=n;i++)
for (int j = 1; j <= m; j++)
{
char c; cin >> c;
a[i][j] = max(a[i - 1][j], a[i][j - 1]);
if (c == 'l')
a[i][j] += 4;
else if (c == 'o')
a[i][j] += 3;
else if (c == 'v')
a[i][j] += 2;
else if (c == 'e')
a[i][j]++;
}
printf("%d\n", a[n][m]);
return 0;
}
I 数字染色
小红拿到了一个由正整数组成的数组,她准备把这个数组上的某些数染成红色。
但是有一个限制,染成红色的所有的数的 gcd (最大公约数)必须大于 1 。
小红想知道,自己有多少种不同的染色方案?
我们定义,若两种方案染色的数的数量不同,或者有两个数的染色状态不同,那么则视为两种不同的方案。
这个方案数可能过大,请对 1e9+7 取模。
输入描述:
第一行一个正整数 n ,代表数组的大小。
第二行有 n 个正整数 ai,表示数组。
1≤n,ai≤1e5
输出描述:
染红色数字的方案数,对 1e9+7 取模。
示例:
输入:
4
2 3 6 2
输出:
9
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+5,mod=1e9+7;
ll a[N],in[N],T[N],num[N],prim[N],idx=0;
bool dis[N];
void add(ll &a,ll b) {a=(a+b)%mod;}
int main()
{
in[0]=1;
for(int i=1;i<N;i++) add(in[i],in[i-1]*2);
ll n,nu,ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&nu),a[nu]++;
for(int i=1e5;i>=2;i--)
{
ll x=a[i];
for(int j=i+i;j<=1e5;j+=i)
add(T[i],-T[j]),x+=a[j];
add(T[i],in[x]-1);
add(ans,T[i]);
}
add(ans,mod);
cout<<ans<<endl;
return 0;
}
J 小红的心愿
小红很喜欢红色。
有一天她来到了北京信息科技大学校赛的现场,她带来了个气球,其中有一些是红气球。她想把红色的气球送给这场比赛做出这道题的人。
这道题只需要需要你输出一个正整数,代表字符串"redballoon"的长度即可。
你可以帮她完成这个心愿吗?
输入描述:
无
输出描述:
一个正整数。
代码:
#include <bits/stdc++.h>
int main()
{
puts("10");
return 0;
}
K 小红的树
小红拿到了一棵有根树。根节点为1号节点。
所谓树,指没有回路的无向连通图。
现在小红想给一部分点染成红色。之后她有 次询问,每次询问某点的子树红色节点的个数。
输入描述:
第一行一个正整数 n ,代表树的节点个数。
第二行有 n−1 个正整数,分别表示第 2 个节点到第 n 个节点每个节点的父亲。
接下来一个长度为 n 的、由’W’或’R’字符组成的字符串。第 i 个字符为’W’代表该字符没被染色,若字符为’R’代表该字符被染成了红色。
接下来一个正整数 q ,代表小红的询问次数。
接下来的 q 行,每行有一个正整数 xx ,代表一次询问。
(1≤n,q≤1e5,1≤x≤n)
输出描述:
对于每次询问,输出一个整数,代表节点xx的子树的红色节点个数。
示例:
输入:
5
1 2 1 4
WRWRR
3
3
4
5
输出:
0
2
1
代码:
#include <bits/stdc++.h>
using namespace std;
#define si(a) scanf("%d",&a)
int f[100010], a[100010];
int main()
{
int n; si(n); f[1] = 1;
for (int i = 2; i <= n; i++)
si(f[i]);
for (int i = 1; i <= n; i++)
{
char c; cin >> c;
if (c == 'R')
{
int now = i;
while (f[now] != now)
{
a[now]++;
now = f[now];
}
a[now]++;
}
}
int q; si(q);
while (q--)
{
int n; si(n);
printf("%d\n", a[n]);
}
return 0;
}
L 重排字符串
小红拿到了一个只由小写字母组成的字符串。她准备把这个字符串重排(只改变字母的顺序,不改变数量)
重排后小红想让新字符串不包含任意两个相同的相邻字母。
你能帮帮她吗?
输入描述:
第一行一个正整数 n ,代表字符串的长度。 (1≤n≤1e5)
第二行为一个长度为 n 的、只由小写字母组成的字符串。
输出描述:
如果可以完成重排,请在第一行输出一个“yes”,第二行输出重排后的字符串。如果有多个正解,输出任意即可。
如果不能重排,则直接输出“no”
示例:
输入:
7
aabbccc
输出:
yes
cabcabc
代码:
#include <bits/stdc++.h>
using namespace std;
int n; string s; priority_queue <pair<int, char>, vector<pair<int, char> >, less<> >q; int a[30]; pair <int, char> p,o;
int main()
{
cin >> n >> s; bool f = 1;
for (int i = 0; i < n; i++)
a[s[i] - 'a']++;
s.clear();
for (int i = 0; i < 30; i++)
if (a[i]) q.push({ a[i], 'a' + i });
p = q.top(); q.pop();
s += p.second; p.first--;
while (q.size())
{
o = q.top(); q.pop();
s += o.second; o.first--;
if (p.first)
q.push(p);
p = o;
}
if (p.first)
puts("no");
else
{
puts("yes");
cout << s << endl;
}
return 0;
}