2019年湘潭大学程序设计竞赛(重现赛) 个人笔记 题解

原题链接

 

先来一波官方题解

A. Who’s better?

直接做即可

 


B. Number
直接模拟即可。

 


C. Math Problem
证明a % 192 = 1

后面就是等差数列和

 


D. Stone

贪心

题目乍一看像是NIM的模板题或者DP的模板题

但其实。。。

石子数累加和-最大一堆的石子数

因为每次合并代价都是小堆石子的数量,不妨设三堆石子a<= b <= c

最优方案,必然是a合并到c,再b合并到a+c;
如果先合并a和b,那么代价必然大于a+b

推而广之,最优方案为每次都往最大的那个堆上合并。
时间复杂度O(n)

 


E. Watermelon

两点

写法简单一点是O(m),复杂一点是O(n),两种都可以过

O(m)的方法:循环维护到每个人吃西瓜时的西瓜数量的区间[L,R]

初始L=R=m

对于非肚量最大的人L每次减一(至少吃一个),R每次减去能a[i](至多吃a[i]个)

对于肚量最大的人L和R都减去a[i]

直到某个非肚量最大的人吃西瓜的时候如果L<0,得到答案为No

或者肚量最大的人吃西瓜时R<0,那么说明可以成功,得到答案为Yes

O(n)方法类似,求出肚量最大的人之前和之后的人的肚量之和即可优化。

 


F. Black & White

二分+ 前缀和

首先预处理出前缀和,pre0[i]表示[1..i]这个区间有多少0;

然后二分答案,再对答案进行O(n)的验证;

只需判断区间内0或1的个数加上m是否不小于当前二分的答案便可;

时间复杂度:O(nlogn)

 


G. Truthman& Fakeman
此问题即是把N个人分为两个集合,且满足以下条件。

i认为j是一个Truthman,那么i和j属于一个集合(一真都真一假都假)

i认为j是一个Fakeman,那么i和j属于不同集合(你真我假你假我真)

要求出符合条件的Truthman最多的一种合理解。

dfs即可,任意找一个没有被分配身份的人开始dfs,假设他的身份为任意一 种,由此可以推出一些人的身份(或者矛盾),因为要使Truthman最多,所以选择人数多的集合作为Truthman(1)即可。

注意预设一个人的身份可能无法推出所有人的身份(图不连通),循环做上面 的步骤即可。

复杂度O(n+m)
 


H. Chat
可以转化为分组背包:

每一天可以选择造成1.2.3...m点生气度,在造成生气度的同时可以省去一些 上线时间。对于每一天m*m即可算出造成i点生气度时最多能省去多少时间。

预处理每一天,复杂度O(n*m*m)

把每一天的每一种生气度当做一个物品,那么生气度就是这个物品的空间, 省去的时间就是这个物品的价值,n个组,每个组m个物品,而背包的大小 就是可以造成生气度的最大值k

做分组背包即可求得最多能省去多少时间,用原来所需要的总时间减去最大省去的时间即是答案,背包复杂度O(n*m*k),总复杂度O(n*m*m+n*m*k)

 

std

A:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40644665

B:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40644670

C:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40644703

D:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40644693

E:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40644542

F:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40644656

G:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40638095

H:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40643710

 

 

 

A.Who's better?

链接:https://ac.nowcoder.com/acm/contest/893/A
来源:牛客网
 

题目描述

ICPC比赛中,谁通过的题数多,谁排名靠前;在通过题数相同的情况下,谁的罚时少,谁排名靠前;如果前两者都相同,就看最后正确提交的时间,谁早最排名靠前。 现在给你两个队伍的正确通过的题数、罚时和最后正确提交时间,请判断一下,谁的排名更靠前?

输入描述:

 

只有一组测试样例,两行,每行三个整数n(0≤n≤13),p(1≤p≤1000),s(1≤s≤300)n(0≤n≤13),p(1≤p≤1000),s(1≤s≤300),依次表示两个队的正确通过的题数、罚时和最后正确提交时间。

输出描述:

输出一行(末尾要换行符)。
如果是第1个队排名靠前,输出1;如果是2队,输出2;如果无法分辨,输出"God"。

示例1

输入

复制

1 10 10
1 22 2

输出

复制

1

示例2

输入

复制

1 10 10
2 42 20

输出

复制

2

示例3

输入

复制

1 10 10
1 10 10

输出

复制

God

疯狂if...else模拟 

#include<iostream>
#include<cstdio>
using namespace std;

int main(){
    int a1,b1,c1,a2,b2,c2;
    cin>>a1>>b1>>c1>>a2>>b2>>c2;

    if(a1>a2)
        cout<<1<<endl;
    else if(a1<a2)
        cout<<2<<endl;
    else if(a1==a2){
        if(b1<b2)
            cout<<1<<endl;
        else if(b1>b2)
            cout<<2<<endl;
        else if(b1==b2){
            if(c1<c2)
                cout<<1<<endl;
            else if(c1>c2)
                cout<<2<<endl;
            else
                cout<<"God"<<endl;
        }

    }


	return 0;
}

 

 

 

B.Number

链接:https://ac.nowcoder.com/acm/contest/893/B
来源:牛客网
 

题目描述

Bonnie得到了一个数字n。
现在她想对这个数字不断的做一种操作:

  • 如果n的最后一位数码是0,那么她就把n除以10;
  • 否则她把这个数加上1;
  • 直到n变为一个不大于1的数。

给定n,请问Bonnie需要做多少次操作?

输入描述:

第一行一个数字T(1≤T≤300000)T(1≤T≤300000)--样例个数。

每个样例仅一行一个数字n(1≤n≤109)n(1≤n≤109)。

输出描述:

每个样例输出一行一个数字—Bonnie需要做的操作次数。

示例1

输入

复制

6  
9  
99  
2  
11032  
1000000000  
62

输出

复制

2
3
9
44
9
13

说明

第一个样例: 9→10→19→10→1
第二个样例: 99→100→10→199→100→10→1

和开心数一样,每次操作要O(1)就好,只是这个常熟会比较大,先尝试暴力一波 

#include<iostream>
#include<cstdio>
using namespace std;

int main(){
    int T,n;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        int ans = 0;
        while(n>1){
            if(n%10==0)
                n/=10;
            else
                n++;
            ans++;
        }
        printf("%d\n",ans);
    }

	return 0;
}

 

 

C.Math Problem

链接:https://ac.nowcoder.com/acm/contest/893/C
来源:牛客网
 

题目描述

已知整数a,a3a,a3除192的余数是1。求区间[L,R]之间满足条件的a的累加和是多少?

输入描述:

第一行是一个整数T(1≤T≤10000)T(1≤T≤10000),表示样例的个数。
每个样例包含两个整数L,R,1≤L≤R≤109L,R,1≤L≤R≤109。

输出描述:

每行输出一个样例的结果。

示例1

输入

复制

1
1 10

输出

复制

1

上面这个推导有点复杂,像我这种小白还是猜出mod192的规律比较靠谱

等差数列求和,边界处理

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
ll solve(int x) {
    if(x==0) return 0;
    ll k=(x-1)/192;
    return 192/2*k*(k+1)+(k+1);
}
int main() {
    int T;
    scanf("%d",&T);
    while(T--) {
        ll l,r;
        scanf("%lld%lld",&l,&r);
        printf("%lld\n",solve(r)-solve(l-1));
    }
    return 0;
}

学习下面的码风,用循环处理边界,不容易乱 

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
 
typedef long long ll;
 
ll l, r;
 
void solve() {
    while (r % 192 != 1) r--;
    while (l % 192 != 1) l++;
    if (l > r) return void(puts("0"));
    printf("%lld\n", (r + l) * ((r - l) / 192 + 1) / 2);
}
 
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%lld%lld", &l, &r);
        solve();
    }
    return 0;
}

 

 

D.Stone

链接:https://ac.nowcoder.com/acm/contest/893/D
来源:牛客网
 

题目描述

有n堆石子排成一排,第i堆石子有aiai个石子。
每次,你可以选择任意相邻的两堆石子进行合并,合并后的石子数量为两堆石子的和,消耗的体力等价于两堆石子中石子数少的那个。
请问,将所有的石子合并成一堆,你所消耗的体力最小是多少?

输入描述:

第一行是一个整数T(1≤T≤20)T(1≤T≤20),表示样例的个数。
每个样例的第一行是一个整数n(1≤n≤10000)n(1≤n≤10000),表示石子堆的数量。
第二行是n个整数ai(1≤ai≤109)ai(1≤ai≤109)

输出描述:

每行输出一个样例的结果。

示例1

输入

复制

2
2
1 2
1
1

输出

复制

1
0

说明

巨大的输入,请使用C风格的输入。

贪心,开始的思路就是每次操作,用最小与最大继续合并,这样的费用应该会是最低的,但后面你会想到,合并之后的大小一定是比最大的还大,毕竟每次都是与最大的合并嘛,然后有n个石堆,无论怎么合并都只会是n-1次操作,所以花的费用永远也不用考虑上新合并出的石堆~~

复杂度最快可到O(n),直接遍历减去最大的石堆体积

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;

int main(){
    int T,n,a;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        //priority_queue<int> pmax;
        priority_queue<int, vector<int>, greater<int> > pmin;
        for(int i=1;i<=n;i++){
            scanf("%d",&a);
            pmin.push(a);
        }
        long long ans=0;
        while(pmin.size()>1){
            int u = pmin.top();
            ans+=u;
            pmin.pop();
        }

        printf("%lld\n",ans);
    }

	return 0;
}

 

E.Watermelon

链接:https://ac.nowcoder.com/acm/contest/893/E
来源:牛客网
 

题目描述

在ACM队暑假集训的某一天,实验室里有 n n个人。因为天气很热,大家都很想吃西瓜。
于是Eric买了 m m个西瓜拿到了实验室。
Eric把这 n n个人分别编号为 1,2,3...n 1,2,3...n,他希望这 n n个人循环轮流来吃西瓜。
也就是说从 1 1号开始,然后 2 2号, 3 3号...  n n号依次吃西瓜, n n号吃完后又轮到 1 1号来吃,直到这 m m个西瓜被吃完。
而这 n n个人每个人有一个肚量值,第i个人的肚量值为 ai ai。
lililalala是这 n n个人肚量值最大的人,不仅如此,他还非常贪吃,每次轮到他吃西瓜时,都会直接吃掉等同于他的度量值数量的西瓜。如果剩余的西瓜已经不够吃了,那么他会把所有西瓜直接吃完。(是的他很能吃)
除了lililalala以外的其他人,对于第 i i号每次吃西瓜可以选择吃掉 [1,ai] [1,ai]中任意整数数量个西瓜。当然,不能超过当前剩余的西瓜数量。
为了使吃西瓜更有意思一些,Eric规定如果在轮到某个人吃西瓜时没有西瓜了,那么由他来打扫一次实验室。(是的大家都很能吃)
其他人都觉得lililalala吃的太多了应该由他来打扫卫生。请问在其他人串通好的情况下能否合理安排每个人的行动使得吃完西瓜后由lililalala来打扫卫生?

输入描述:

第一行一个数字T(1≤T≤50)T(1≤T≤50)--样例个数。 
每个样例包含两行。 
第一行两个数字n,m(1≤n≤105,0≤m≤106)n,m(1≤n≤105,0≤m≤106)。
第二行 n n个数字a1,a2...an(1≤ai≤107)a1,a2...an(1≤ai≤107),以空格分隔。 
保证所有样例中肚量最大的人只有一个。这个人是lililalala。 
保证所有样例中 ∑n ∑n不超过 3×106 3×106。 
保证所有样例中 ∑m ∑m不超过 107 107。

输出描述:

每个样例输出一行,输出”YES”如果合理安排可以使得最后由lililalala来打扫卫生,否则输出“NO”。

示例1

输入

复制

2  
4 3  
1 2 3 2  
5 8  
1 2 3 2 1

输出

复制

YES
NO

说明

第一个样例中lililalala是3号:
一种可行的方案:
1号吃掉1个西瓜
2号吃掉2个西瓜
第一次到3号吃西瓜时就没有西瓜可以吃了,所以3号需要打扫卫生。

第二个样例无论怎样安排各个人在每一次吃西瓜的数量,也不能达成目标。


 

两点

写法简单一点是O(m),复杂一点是O(n),两种都可以过

O(m)的方法:循环维护到每个人吃西瓜时的西瓜数量的区间[L,R]

初始L=R=m

对于非肚量最大的人L每次减一(至少吃一个),R每次减去能a[i](至多吃a[i]个)

对于肚量最大的人L和R都减去a[i]

直到某个非肚量最大的人吃西瓜的时候如果L<0,得到答案为No

或者肚量最大的人吃西瓜时R<0,那么说明可以成功,得到答案为Yes

O(n)方法类似,求出肚量最大的人之前和之后的人的肚量之和即可优化。

 

#include<iostream>
#include<cstdio>
using namespace std;

const int MAX = 5*1e6+5;
int a[MAX];
int sum[MAX];
int n,m;
void getAns(int p){
    if(n==1){
        printf("YES\n");
        return;
    }

    int L=p-1,R=sum[p-1];
    while(R<m){
        L+=a[p];
        R+=a[p];
        L+=n-1;
        R+=sum[n]-a[p];
    }
    if(m>=L&&m<=R)
        printf("YES\n");
    else
        printf("NO\n");

}


int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        int p=1;
        sum[0]=0;

        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            sum[i] = sum[i-1] + a[i];
            if(a[p]<a[i])
                p = i;
        }

        getAns(p);
    }

	return 0;
}

 

F.Black & White

链接:https://ac.nowcoder.com/acm/contest/893/F
来源:牛客网
 

题目描述

你有一个长度为 n 的 01 串S,你可以执行最多 m 次操作。
对于每次操作,你可以选择一个位置 i 满足 1≤i≤n1≤i≤n,翻转这一位的值,0变成1,1变成0。
定义一个 01 串的价值为其中最长连续0的个数和最长连续1的个数的较大值,求S在经过最多m次操作后的最大价值。

输入描述:

* 第一行一个整数 T ,表示接下来有 T 个样例。
* 首先输入n,m,表示S串的长度n和操作次数m,其中1≤n≤1000001≤n≤100000,0≤m≤10000≤m≤1000;
* 接下来输入一个长度为n的字符串S。

输出描述:

一个整数,表示题面上描述的最大价值。

示例1

输入

复制

2
5 1
00101
2 1
01

输出

复制

4
2

说明

第一个串翻转第三个位置,00001的价值为4;第二个串翻转第一个位置,11的价值为2。

双指针,复杂度O(n)

官方题解用的是前缀+二分O(nlogn),都很快都可以

#include<iostream>
#include<cstdio>
using namespace std;

int main(){
    ios::sync_with_stdio(false);
    int T,n,m,cnt1,cnt2;
    string s;
    cin>>T;
    while(T--){
        cin>>n>>m>>s;

        int j=0,ans=0;
        cnt1=0,cnt2=0;
        for(int i=0;i<n;i++){
            if(s[i]=='1'){
                cnt1++;
                if(cnt1>m){
                    while(s[j++]!='1');
                    ans = max(ans,i-j+1);
                }
            }
            ans = max(ans,i-j+1);
        }

        j=0;
        for(int i=0;i<n;i++){
            if(s[i]=='0'){
                cnt2++;
                if(cnt2>m){
                    while(s[j++]!='0');
                    ans = max(ans,i-j+1);
                }
            }
            ans = max(ans,i-j+1);
        }

        cout<<ans<<endl;
    }

	return 0;
}

 

 

G.Truthman or Fakeman

链接:https://ac.nowcoder.com/acm/contest/893/G
来源:牛客网
 

题目描述

有n个人在玩一个身份扮演的游戏。
把这n个人编号为1,2,3...n。
其中每个人会扮演下面两种身份中的一种:
Truthman:当某个人扮演Truthman时,这个人只会说真话。
Fakeman:当某个人扮演Fakeman时,这个人只会说假话。
这n个人是互相知道身份的,但是Casya作为一个旁观者不知道任何一个人的身份。
为了让Casya有可能推断这些人的身份,这n个人说了m句话。
每句话的内容只包含某人对某人身份的一条描述,且被Casya记录为以下形式:
u,v,0 -- u认为v是一个Fakeman;
u,v,1 -- u认为v是一个Truthman;
当然这些话不一定都是真话,这取决于说话的人的身份。
但是可以肯定的是身份只有两种,也就是说某个人不是Truthman就是Fakeman。

Casya想知道不违反上面的条件和记录最少有多少个Fakeman,除此之外他还想得到一组在此情况下的一组合理的解—即所有人的身份。或者确定记录本来就是矛盾的所以没有任何符合条件的解。

输入描述:

第一行一个数字T(1≤T≤30)T(1≤T≤30)--样例个数。 
其中每个样例:
第一行两个数字n,m(1≤n≤105,0≤m≤105)n,m(1≤n≤105,0≤m≤105)。
然后m行,每一行包含三个整数u,v,w(1≤u,v≤n,w={0,1})u,v,w(1≤u,v≤n,w={0,1})
保证所有样例中∑n∑n不超过2×1062×106。 
保证所有样例中∑m∑m不超过2×1062×106。

输出描述:

每个样例输出一行。
如果存在合理的解,输出一个长度为n的字符串,且只包含’0’或’1’。
其中第i个字符为’0’表示$i$是一个Fakeman,为’1’表示i是一个Truthman。并使得Fakeman的数量最少。如果有多个解符合要求,输出任意一个即可。

如果不存在任何合理的解,输出-1。

示例1

输入

复制

2  
5 4  
1 2 1  
2 3 0  
2 4 0  
1 5 0  
3 3  
1 2 1  
1 3 0  
2 3 1

输出

复制

00111
-1

 

H.Chat

链接:https://ac.nowcoder.com/acm/contest/893/H
来源:牛客网
 

题目描述

在Casya生活的世界里,一天由m个小时组成。
最近Casya的女神终于答应在接下来的n天中与Casya聊天,Casya非常激动。

在这n天中的每一天的每一个小时中女神可能会在线或者不在线,
某个小时如果女神如果在线且Casya在线的话他们就会开心的聊一个小时;
反之如果女神在线Casya没有在线的话女神就会认为Casya放了她的鸽子而积累一点生气度。

而Casya是个很懒惰的人,他每天只愿意上线一次,当他某天下线后就不愿意再上线了。
换句话说,他每天在线的时间必须是连续的。

现在Casya已经知道每一天的每个小时女神是否会在线

Casya希望在这n天中女神的总生气度不超过k,在此前提下Casya希望他的总上线时间最小。
假设每个小时Casya和女神要么完整在线要么完整不在线,请问Casya在这n天中最小的总上线时间是多少小时?

输入描述:

第一行一个数字T(1≤T≤30)T(1≤T≤30)--样例个数。

每个样例第一行三个数字n,m,k(1≤n,m≤200,0≤k≤200)n,m,k(1≤n,m≤200,0≤k≤200)。
然后这n行,每行一个长度为m的只含'0'和'1'的字符串,
第i个字符串的第j个字符为'0'代表女神第i天的第j个小时不在线,为'1'表示女神第i天的第j个小时在线。

保证所有样例中∑n×m∑n×m不超过5×1055×105。

输出描述:

每个样例输出一行一个数字--Casya在这n天中最小的总上线时间

示例1

输入

复制

2  
2 5 1  
01001  
10110  
2 5 0  
01001  
10110  

输出

复制

5
8

说明

第一个样例:
一种可行的方案:
Casya第一天只在第2个小时上线,第五个小时女生在线而Casya不在线,生气度积累1;
Casya第一天在第1、2、3、4个小时上线。
Casya的总上线时间是1+4=5。
第二个样例:
只能老老实实上线。
Casya第一天在第2、3、4、5个小时上线;
Casya第一天在第1、2、3、4个小时上线。
Casya的总上线时间是4+4=8。

 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值