BITACM2018第一轮积分赛(一)题解

A. Watermelon by miamiao

description: 给出一个西瓜的质量 w ,问能不能把这个西瓜分成两份,每一份的质量都是正偶数。

solution:如果满足 w4 w 是偶数,就可以。否则不行。

#include<bits/stdc++.h>
using namespace std;
int n;
int main()
{
    scanf("%d",&n);
    if (n>=4&&n%2==0)puts("YES");
    else puts("NO");
}

评价:低档题。签到。

B. Bayan Bus by zhber

description:给出车子的字符形状,上面有34个座位,用 # 表示。按照优先从右到左、再从上到下的顺序往里填 k 个人,有人坐的位置用O表示,输出现在的车子形状。

solution: 直接暴力模拟。

#include<bits/stdc++.h>
using namespace std;
char s[6][30]={"+------------------------+",
               "|#.#.#.#.#.#.#.#.#.#.#.|D|)",
               "|#.#.#.#.#.#.#.#.#.#.#.|.|",
               "|#.......................|",
               "|#.#.#.#.#.#.#.#.#.#.#.|.|)",
               "+------------------------+"};
int k,x,y;
int main()
{
    scanf("%d",&k);
    x=y=1;
    for (int i=1;i<=k;i++)
    {
        s[x][y]='O';
        if(x==4)x=1,y+=2;
        else if (x==2&&y!=1)x=4;
        else x=x+1;
    }
    for (int i=0;i<6;i++)puts(s[i]);
}

评价:低档题。签到。

C. Spyke Talks by sadyi98

description: n 个人,每个人可以在通话也可以不在通话,如果在通话就会得到一个通话id,一对正在通话的人将共享一个 id 。不允许超过两人共同进行通话。现在给出每个人通话情况,问这个状态是否合法,如果合法有几对正在通话。

solution: 排序后去掉 0 ,统计多少个数字恰好出现了2次即可。如果有数字出现超过 3 次则不合法。

void solve() {
    int n, ans;
    scanf("%d", &n);
    map<int, int> m;
    for (int i = 1; i <= n; i++) {
        int x;
        scanf("%d", &x);
        m[x]++;
    }
    for (auto i = m.begin(); i != m.end(); i++) {
        if (i->first == 0) continue;
        int t = i->second;
        if (t > 2) {
            puts("-1");
            return;
        }
        if (t == 2) ans++;
    }
    printf("%d\n", ans);
}

评价:低档题。

D. Opposites Attract by sadyi98

description: n 个数a1...an,保证 |ai|10 ,询问有多少有序对 (i,j),1i<jn 满足 ai+aj=0

solution: 统计数字 k 出现的次数tk。对于 0 0对应的情况,答案是 C2t0=t0(t01)2 。对于 k k对应的情况,答案是 tktk 。全部求和即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n;
ll zero,p[11],q[11];
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        int x;scanf("%d",&x);
        if (!x)zero++;
        else if (x>0)p[x]++;
        else q[-x]++;
    }
    ll ans=zero*(zero-1)/2;
    for (int i=1;i<=10;i++)ans+=p[i]*q[i];
    printf("%lld\n",ans);
}

评价:低档题。

E. Chtholly’s request by BITtian

description: 询问前 k 小的、长度是偶数的回文数之和,答案对p取模。保证 1k105,1p109

solution: 显然长度是偶数的回文数是通过一个数字后面接上它的翻转数字来构造。枚举 1 k的所有数字,并且用它来构造回文数相加即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int k,p;
ll ans;
ll calc(ll x)
{
    int d[10],len=0,temp=x;
    while (temp)
    {
        d[++len]=temp%10;
        temp/=10;
    }
    for (int i=1;i<=len;i++)x=x*10+d[i];
    return x;
}
int main()
{
    scanf("%d%d",&k,&p);
    for (int i=1;i<=k;i++)ans+=calc(i)%p;
    printf("%lld\n",ans%p);
}

评价:低档题。

F. Tourist’s Notes by miamiao

description: 给出一个长度为 n 的数列,限制数列中相邻的两项之差绝对值不超过1。现在确定了其中m个位置的值,询问这样的数列是否存在。如果不存在,输出 IMPOSSIBLE 。如果存在,输出这个数列中可能存在的最大的值。数据范围 1n109,1m105

solution: 相邻位置数字之差不超过1,如果有两个相邻的确定大小的数字,距离之差小于大小之差,即 |posiposi+1|<|aposiaposi+1| ,那么一定无解。接下来从第一个确定位置往前、最后一个确定位置往后的地方没有限制,答案初值为 max{apos1+pos11,aposm+nposm} 。接着考虑从 aposi 走到 aposi+1 多出来的步数往上走来获得更大的数字,考虑到要折返,用 max{aposi,aposi+1}+rest2 更新答案即可。

评价:中低档题。思路不是很难想到。定位是选手在比赛一小时内能写出来的题。

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
const int maxn = 1e5 + 10;
/* head */
int a[maxn], b[maxn];
inline void solve() {
    int n, m;
    scanf("%d%d", &n, &m);
    int ans = 0;
    rep(i, 1, m + 1) {
        scanf("%d%d", &a[i], &b[i]);
        ans = max(ans, b[i]);
    }
    bool ok = 1;
    rep(i, 2, m + 1) {
        if (a[i] - a[i - 1] < abs(b[i] - b[i - 1])) {
            ok = 0;
            break;
        } else {
            int d = a[i] - a[i - 1] - abs(b[i] - b[i - 1]);
            ans = max(ans, max(b[i], b[i - 1]) + d / 2);
        }
    }
    if (!ok) { puts("IMPOSSIBLE"); return; }
    ans = max(ans, max(b[1] + a[1] - 1, b[m] - a[m] + n));
    printf("%d\n", ans);
}
int main() {
    solve();
    return 0;
}
G. Number of Ways by zhber

description: 给一个长度为 n 的数列,满足|ai|109。要把这个数列分成不相交的 3 个非空子集,使得每个区间内数字之和相等。问有多少种分法。

solution:算出数列中所有数之和 sum ,每一个区间的和只能是 sum3 ,且其中一个区间与头部连接,一个区间与尾部连接,一个区间被夹在中间。显然只要前后两区间和是 sum3 ,那么中间的和也肯定是 sum3 。因此只要使得头尾前缀、后缀和满足条件,且在中间留出第三个区间的空位即可。预处理前后缀和,正向枚举k,如果当前以k为左端点的后缀和满足条件,统计一下它对应的前缀和有多少个,累加到答案里即可。只要前缀和的右端点不超过k-1就算是可行的,因为中间至少留出一个第三区间的位置。

ll a[5000005];
int main () {
    int n;
    cin>>n;
    if(n<3){
        cout<<0;
        return 0;
    }
    ll s = 0;
    for(int i = 0; i<n; i++){
        cin>>a[i];
        a[i]+=a[i-1];
    }
    ll t = 0;
    ll ans = 0;
    for(int i = 0; i<n-1; i++){
        if(a[i]*3 == a[n-1]*2)
            ans+=t;
        if(a[i]*3 == a[n-1])t++;
    }
    cout<<ans;  
    return 0;
}

评价:中档题。枚举前缀和与后缀和不难想到,但是统计的时候前后缀和不能重叠而且要留出中间部分的处理方法不容易想到。定位是选手在比赛两个小时左右可以做出来。

H. Design Tutorial: Learn from Life by zhber

n 个人坐电梯,第i个人要去第 ai 层,电梯只能同时载 m 个人,一开始所有人都在第1层,最后要求电梯回到第1层。每次电梯可以往上或者往下移动一层。问电梯最少移动多少次。

solution: a 从小到大排序,显然电梯必须要到最大的那个ai去一次,还要回到第一层。这一次必去不可,但是可以顺便载着其他 m1 个人,显然这些人要去的层数越大越好。因此载层数最大的 m 个人一轮,移动次数是2(an1) 。接下来继续载剩下层数最大的 m 个人……直到没有人等候了为止。

评价:中档题。由于样例解释有把人送上去再放在中间楼层中转的过程,有一定误导性,因此不容易直接反应过来送上去中转和直接到目的地没有区别。但最优策略实际上不难发现,而且一旦想到最优策略立刻能意识到中转没有意义。因此定位是选手在比赛一两个小时的时候可以做出来的题目。

// by H题一血选手Natsumatsu
#include <cstdlib>  
#include <iostream>
#include <cmath> 
#include<algorithm>
#include <cstdio>
#include <cstring>
#include<stack>
#include <queue>
#include <functional>
using namespace std;
#define lim 1e-5
#define ll long long
//const double e = 2.718281828459;
//long long fpow(int a, int b);
#include <iostream>
#include <cmath> 
#include<algorithm>
#include <cstdio>
#include <cstring>
#include<stack>
#include <queue>
#include <functional>
using namespace std;
#define lim 1e-5
#define ll long long
int m,n; char in[200], out[200];
int main()
{
        priority_queue<int, vector<int>, less<int>> bg;
        int a, b; int x, y;
        cin >> a >> b;
        int i, j;
        ll sum = 0;
        for(i=1;i<=a;i++)
        {
            cin >> x;
            bg.push(x);
        }
        while (bg.size() >= b)
        {
            sum += 2 * (bg.top() - 1);
            for (i = 1; i <= b; i++) bg.pop();
        }
        if(!bg.empty())sum += 2 * (bg.top() - 1);
        cout << sum << endl;
    return 0;
}

I. They Are Everywhere

description:给出一个长度是n的字符串,只包含英文字母且区分大小写。求长度最短的子串长度,使得这个子串包含所有出现在原串的字母。

solution: 如果存在一个长度为 k 的满足条件的子串,那么显然也存在长度为k+1的满足条件的子串。因此可以二分子串长度。判定是否有长度为 mid 的合法子串,只要记录一下每个字母在当前子串出现的次数。当子串后移,即从 at...at+mid1 移动到 at+1...at+mid 时,其实只修改了 atat+1at+mid1at+mid 四个点的信息。

评价:中高档题。

#include<bits/stdc++.h>
#define F first
#define S second
using namespace std;
typedef vector<int> vi;
typedef vector<vi> vvi;

map<char, int> toint;
map<int, char> tochar;
vvi frec;
int ind = 1;

bool check(int l, int r) {
    for (int i=0;i<ind-1;++i) {
        int aux = frec[i][r];
        if (l > 0) aux -= frec[i][l-1];
        if (aux == 0) return false;
    }
    return true;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    int n;
    string s;
    cin>>n>>s;
    for (int i=0;i<n;++i) {
        if (toint[s[i]] == 0) {
            toint[s[i]] = ind;
            tochar[ind] = s[i];
            ind++;
        }
    }
    frec = vvi(ind, vi(n, 0));
    frec[toint[s[0]]-1][0] = 1;
    for (int i=1;i<n;++i) {
        for (int j=1;j<ind;++j) {
            frec[j-1][i] = frec[j-1][i-1] + (s[i]==tochar[j]);
        }
    }
    int mini = 1e8;
    for (int i=0;i<n;++i) {
        int left = i, right = n-1, mid, cnt = 50;
        if (!check(i, n-1)) break;
        while (cnt--) {
            mid = (left+right)/2;
            if (check(i, mid)) {
                right = mid;
            } else {
                left = mid+1;
            }
        }
        while(!check(i, mid))
            mid--;
        if (mini > mid-i+1) {
            mini = mid-i+1;
        }
    }
    cout<<mini<<"\n";
    return 0;
}
J. Main Sequence by zhber

description: k 种不同的括号,左括号用数字k表示,右括号用 k 表示。定义:空集是合法括号序列;若 A B都是合法括号序列,则 AB 是合法括号序列;若 A 是合法括号序列,v,v是左、右括号,则 v,A,v 是合法括号序列。给一个长度是 n 的序列,右括号有t个,其他都是左括号。可以任意把左括号改成右括号(但不能把右括号改成左括号),要通过这种修改操作得到任意一种合法括号序列并输出,无解输出 NO

solution: 左括号可以变右括号,但右括号不能变左括号。这说明左括号变化更多,而右括号没得变。因此考虑倒着用右括号匹配对应的左括号。如果倒着遇到一个左括号,它不能和后面的右括号匹配了,就改成右括号。从后往前维护一个栈,保证栈中只有右括号,找新括号与栈中右括号匹配。如果当前的括号与栈顶匹配就马上匹配并退栈,否则右括号直接入栈,左括号改右括号入栈。

#include<bits/stdc++.h>
using namespace std;
int n,t;
int a[1000010];
stack<int>st;
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)scanf("%d",a+i);
    scanf("%d",&t);
    for (int i=1;i<=t;i++)
    {
        int x;
        scanf("%d",&x);a[x]*=-1;
    }
    for (int i=n;i>=1;i--)
    {
        if (!st.empty()&&st.top()+a[i]==0)st.pop();//栈中只有右括号,如果匹配了这个一定是左括号
        else if (a[i]<0)st.push(a[i]);//右括号入栈
        else a[i]*=-1,st.push(a[i]);//左括号没有匹配成功就变号也入栈
    }
    if (!st.empty())puts("NO");
    else {puts("YES");for (int i=1;i<=n;i++)printf("%d ",a[i]);}
}

评价:中高档题。

K. Long Jumps by zhber

description: 一个尺子上已经有 n 个刻度,分别是a1,a2...an。已知 a1=0,an=l 。定义尺子能够量出长度 x ,是存在两个数1i,jn,满足 |aiaj|=x 。给两个数 x,y ,问尺子上最少添加多少个刻度,就能量出这两个数 x,y 。输出方案。

solution: a1=0 ,所以至多添加两个刻度 x,y 即可量出这两个数。这说明答案不超过 2 ,因此判断0 1 能否作为答案即可。对于答案是0的情况,判断 x y是否都能被量出。(判断是否能被量出,只要排序后枚举 1in ,二分查看数字 aix 或者 ai+x 是否存在。对于 y ,我们做同样的讨论)对于答案是1的情况,判断 x y是否至少一个能被量出。本题一个 trick 是可能存在这样的情况: xy 都无法被量出,但是存在数字 k ,刻度k±x k±y 都存在,这样只加一个刻度 k 也是可以的。对于此种情况,k只能是 ai±x ai±y ,枚举可能的 k 判定即可。

评价:由于这个trick,划分到高档题,这题的定位是选手偏后期才会选择完成的。

//by K题一血选手minisam233
#include<cstdio>
#include<iostream>
using namespace std;
int n,l,x,y;
int a[100001];
bool check(int x){
    //printf("a%d ",x);
    int left=1,right=n,mid,use;
    while (left<=right){
        mid=(left+right)/2;
        if (a[mid]==x) return true;
        if (a[mid]<x){
            use=mid;
            left=mid+1;
        }
        else right=mid-1;
    }
    //printf("%d\n",use);
    if (x==a[use]) return true;
    else return false;
}
int main(){
    scanf("%d%d%d%d",&n,&l,&x,&y);
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    int flag1=0,flag2=0;
    for (int i=1;i<=n;i++){
        if (a[i]+x<=l){
            if (check(a[i]+x)){
                flag1=1;
                break;
            }
        }
        if (a[i]-x>=0){
            if (check(a[i]-x)){
                flag1=1;
                break;
            }
        }
    }
    for (int i=1;i<=n;i++){
        if (a[i]+y<=l){
            if (check(a[i]+y)){
                flag2=1;
                break;
            }
        }
        if (a[i]-y>=0){
            if (check(a[i]-y)){
                flag2=1;
                break;
            }
        }
    }
    //printf("\n%d %d\n",flag1,flag2);
    if (flag1==1 && flag2==1){
        printf("0");
        return 0;
    }
    if (flag1==1 && flag2==0){
        printf("1\n%d",y);
        return 0;
    }
    if (flag1==0 && flag2==1){
        printf("1\n%d",x);
        return 0;
    }
    for (int i=1;i<=n;i++){
        if (a[i]+x<=l){
            if (a[i]+x+y<=l && check(a[i]+x+y)){
                printf("1\n%d",a[i]+x);
                return 0;
            }
            if (a[i]+x-y>=0 && check(a[i]+x-y)){
                printf("1\n%d",a[i]+x);
                return 0;
            }
        }
        if (a[i]-x>=0){
            if (a[i]-x+y<=l && check(a[i]-x+y)){
                printf("1\n%d",a[i]-x);
                return 0;
            }
            if (a[i]-x-y>=0 && check(a[i]-x-y)){
                printf("1\n%d",a[i]-x);
                return 0;
            }
        }
    }
    printf("2\n%d %d",x,y);
    return 0;
}

L. Stadium and Games

description: n 个人进行比赛,如果剩下的人数是偶数就一直进行两两淘汰赛,每次淘汰赛打n2场,剩余人数减半。当剩余人数是奇数时,两两打一场比赛且不淘汰,这样进行 n(1)2 场,然后整个比赛终止。现在给定一个数 k ,询问有多少个n,使得 n 个人参与比赛到结束恰好打了k场。输出所有可行的 n 。保证1k1018

solution: n 个人比赛,n=t2D,其中 t 是奇数。那么比赛的场数可以表示成t(2D1)+t(t1)2。只需解方程 t(2D1)+t(t1)2=k ,所有的合法整数解带入 n=t2D 即可。考虑到D的大小不会很大,大约只有 60 多种,因此可以枚举 D ,判断对应的整数解t是否存在。此时 2D1 是定值,等式右边是一个t的函数 f(t)=t(2D1)+t(t1)2 。显然它是单调的,那么直接二分使得 f(t)=k t <script type="math/tex" id="MathJax-Element-152">t</script>值即可。

评价:难题。

//by L题一血选手sy742705903
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
#include <string>
#include <map>
#include <vector>
#include <set>
#include <queue>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long LL;
const LL maxxN = 1e18+1;
LL out[100];
int cnnt;
LL maxx;
LL c(LL t1,LL x)
{
    return x * (x - 1) / 2 + (t1 - 1) * x;
}
LL judge(LL n, LL t1)
{
    LL r,l;
    l = 0;
    r = maxx;
    while(r - l> 1) {
        LL mid = (r + l) / 2;
        if(c(t1,mid) == n) {
            return mid;
        } else if(c(t1, mid) > n)
            r = mid;
        else if(c(t1,mid) < n)
            l = mid;
    }
    maxx = r;
    if(c(t1,r) == n)
        return r;
    else if(c(t1,l) == n)
        return l;
    else if(r==1 && l == 0)
        return 0;
    return -1;
}

int main()
{
    LL n;
    scanf("%I64d",&n);
    cnnt = 0;
    maxx = sqrt(4 * n);
    LL t1 = 1;
    while(1) {
        LL temp = judge(n, t1);
        if(temp == 0)
            break;
        else if(temp != -1 && temp%2 != 0)
            out[cnnt++] = temp * t1;
        t1 *= 2;
    }
    sort(out, out+cnnt);
    if(cnnt > 0) {
        for(int i=0; i<cnnt; i++) {
            if(i>0 && out[i] == out[i-1])
                continue;
            printf("%I64d\n",out[i]);
        }
    } else
        printf("-1\n");
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值