2021-BNUZ-IT节程序设计竞赛网络赛题解

文章目录

  • 前言
  • 我的徒弟都是实习生
  • 快穿之我只想测身高
  • 凡人追妹传之大学篇
  • 北宋大烟篓
  • 游戏败家子
  • 超神排位赛
  • 这个大师太过正经
  • 糖果的四十六亿种分法
  • 我有一座研究院
  • 特拉福珠宝俱乐部
  • 吃糖直播间
  • 我师傅实在太稳健了
  • 大奉卖笔人

前言

网络赛有效提交者143人(不包括打星选手),题目类型包括数据结构,模拟,动态规划,贪心等算法

让我们来看看排行前十的选手(没错,打星选手就是大佬,学长闲着做了一下直接AK了…)

2021-BNUZ-IT节程序设计竞赛网络赛题解_第1张图片

本次IT节网络赛由某点倾情赞助…就怪

去年是二次元,今年是网文系列

题目名字改编自以下小说

  • 我的徒弟都是大反派

  • 快穿之xxx系列(这个太多了)

  • 凡人修仙传之仙界篇

  • 北宋大丈夫

  • 明朝败家子

  • 超神机械师

  • 这个人仙太过正经

  • 进化的四十六亿重奏

  • 我有一座冒险屋

  • 手术直播间

  • 特拉福买家俱乐部

  • 我师兄实在太稳健了

  • 大奉打更人

听说有人觉得插图好看,那就找出题人要照片吧,其实有些图片和题目都没啥关系~

下面给出C++代码

没有学习C++的同学,可以简单把cin看成C语言里的scanf,把cout看成C语言里的printf

我们会根据一定比例挑选进入现场赛的选手,届时将会将名单公布,欢迎关注公众号北师珠ACMICPC,敬请期待

我的徒弟都是实习生

简单组合数学问题

有一点点小坑,如果按照正常算的话会溢出(边界数据阶乘unsigned long long都存不下)

坑就在这里,我们需要的是解决溢出问题,不是时间

看着很少有一发过的人,基本都被出题人坑了一手

#include

using namespace std;
typedef long long ll;
ll cb[100001];

//ll C(ll k,ll n){//溢出的做法 
//	ll a = n-k,b = k;
//	if(a>b){//保证a最小
//		a^=b^=a^=b;
//	}
//	if(a==0)return 1;
//	ll res = 1;
//	for(int i=n ; i>b ; i--){
     
//		res*=i;
//	}
//	for(int i=2 ; i<=a ; i++){
     
//		res/=i;
//	}
//	return res;
//}

ll C(ll k, ll n) {
     //优化
    ll a = n - k, b = k;
    if (a > b) {
     //保证a最小
        a ^= b ^= a ^= b;
    }
    if (a == 0)return 1;
    ll res = 1;
    for (int i = n, j = 1; i > b; i--, j++) {
     
        res *= i;
        if (j <= a) {
     
            res /= j;
        }
    }
    return res;
}

int main() {
     
    ll t, n, m;
    while (cin >> t >> m >> n) {
     
        ll res = 0;
        for (int i = 4; i <= t - 1; i++) {
     
            int man = i;
            int girl = t - i;
            if (man <= n && girl <= m) {
     
                res += C(man, n) * C(girl, m);
            }

        }
        cout << res << "\n";
    }
}

快穿之我只想测身高

思考:

题目中的M对关系实际上是人之间身高的相对大小关系。具体来说,我们建立一个数组C,数组中起初全为0。若一条关系指明Ai和Bi可以互相看见(不妨设Ai

上述优化后的算法把对一个区间的操作转化为左、右两个端点上的操作,再通过前缀和得到原问题的解。这种思想很常用,我们在后面还会多次遇到。该算法的时间复杂度为O(N+M)。
另外,在本题的数据中,一条关系(A,B)可能会输入多次,要注意检查,对于重复的关系,只在第一次出现时执行相关操作即可。这一点需注意

#include

using namespace std;
map<pair<int, int>, bool> existed;
int c[10010], d[10010];

int main() {
     
    int n, p, h, m;
    cin >> n >> p >> h >> m;
    for (int i = 1; i <= m; i++) {
     
        int a, b;
        scanf("%d%d", &a, &b);
        if (a > b) swap(a, b);
        if (existed[make_pair(a, b)]) continue;
        d[a + 1]--;
        d[b]++;
        existed[make_pair(a, b)] = true;
    }
    for (int i = 1; i <= n; i++) {
     
        c[i] = c[i - 1] + d[i];
        printf("%d\n", h + c[i]);
    }
    cout << endl;
}

凡人追妹传之大学篇

可以插入到中间,左边和右边,也有可能无法插入

分别进行判断即可

我就想着不算难吧,但是就只有几个人写了

#include

using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int cb[100000];
int cc[100000][2];

int main() {
     
    int n;
    while (cin >> n) {
     
        for (int i = 0; i < n; i++) {
     
            cin >> cb[i];
        }
        sort(cb, cb + n);
        if (n == 1) {
     
            cout << "-1\n";
        } else if (n == 2) {
     //左边 右边 中间  
            int c = cb[1] - cb[0];
            if (c & 1) {
     
                cout << "2\n";
                cout << cb[0] - c << " " << cb[1] + c << "\n";
            } else if (cb[0] == cb[1]) {
     
                cout << "1\n";
                cout << cb[0] << "\n";
            } else {
     
                cout << "3\n";
                cout << cb[0] - c << " " << cb[0] + c / 2 << " " << cb[1] + c << "\n";
            }
        } else if (n >= 3) {
     //两个中间,左边右边,没有 
            //判断所有只有一个不等--中间
            int pd = 0;
            int min = INF;
            for (int i = 1; i < n; i++) {
     //找最小的 
                if (cb[i] - cb[i - 1] < min) {
     
                    min = cb[i] - cb[i - 1];
                }
            }

            for (int i = 1; i < n; i++) {
     
                if ((cb[i] - cb[i - 1]) != min && (cb[i] - cb[i - 1]) == min * 2) {
     
                    //可以插入 
                    cc[pd][0] = cb[i];
                    cc[pd++][1] = cb[i - 1];
                } else if ((cb[i] - cb[i - 1]) != min) {
     
                    pd = -1;//直接失败 
                    break;
                }
            }


            //全部相同
            int q = 1;
            for (int i = 1; i < n; i++) {
     
                if (cb[i] != cb[i - 1]) {
     
                    q = 0;
                }
            }
            if (q) {
     
                cout << "1\n";
                cout << cb[0] << "\n";
            } else {
     
                if (pd > 1 || pd == -1) {
     //>=一个不等--没有 
                    cout << "0\n";
                } else if (pd == 1) {
     
                    cout << "1\n";
                    cout << (cc[0][0] + cc[0][1]) / 2 << "\n";
                } else if (pd == 0) {
     //所有都等,左右 
                    int c = cb[1] - cb[0];
                    cout << "2\n";
                    cout << cb[0] - c << " " << cb[n - 1] + c << "\n";
                }
            }

        }
    }
}

北宋大烟篓

经典约瑟夫环问题

如果看不懂代码的可以看这里

https://blog.csdn.net/u011500062/article/details/72855826

#include 
#include 

using namespace std;

int cir(int n, int m) {
     
    int p = 0;
    for (int i = 2; i <= n; i++) {
     
        p = (p + m) % i;
    }
    return p + 1;
}

int main() {
     
    int t;
    int n, m;
    cin >> t;
    for (int i = 0; i < t; i++) {
     
        cin >> n >> m;
        cout << cir(n, m) << "\n";
    }
}

游戏败家子

动态规划
假设数组 cost 的长度为 n,则 n个阶梯分别对应下标 0 到 n-1,楼层顶部对应下标 n,问题等价于计算达到下标 n 的最小花费。可以通过动态规划求解。

创建长度为 n+1 的数组dp,其中dp[i] 表示达到下标 i的最小花费。

由于可以选择下标 0或 1 作为初始阶梯,因此有 dp[0]=dp[1]=0。

当 2≤i≤n 时,可以从下标 i−1 使用 cost[i−1] 的花费达到下标 i,或者从下标 i-2使用 cost[i−2] 的花费达到下标 i。为了使总花费最小,dp[i] 应取上述两项的最小值,因此状态转移方程如下:

dp[i]=min(dp[i−1]+cost[i−1],dp[i−2]+cost[i−2])

依次计算 dp 中的每一项的值,最终得到的dp[n]即为达到楼层顶部的最小花费。

#include
#include
#include
#include

int minCostClimbingStairs(int *cost, int costSize) {
     
    int dp[costSize];
    dp[0] = cost[0];
    dp[1] = cost[1];
    for (int i = 2; i < costSize; i++) {
     
        dp[i] = fmin(dp[i - 1], dp[i - 2]) + cost[i];
    }
    return fmin(dp[costSize - 1], dp[costSize - 2]);
}

int main() {
     
    int cost[1000];
    int costSize = 0;
    int n;
    while (~scanf("%d", &n)) {
     
        while (n--) {
     
            scanf("%d", &costSize);
            for (int i = 0; i < costSize; i++) {
     
                scanf("%d", &cost[i]);
            }
            printf("%d", minCostClimbingStairs(cost, costSize));
        }
    }


}

超神排位赛

模拟题,灵感来自某年山东省省赛,有一道炉石的排位机制模拟

按照题意模拟即可,只是细节要注意,例如升段的时候,降段位的时候,王者50星,王者不会掉到星耀等几种情况

#include

using namespace std;

char r[10][20] = {
     "", "Bronze", "Silver", "Gold", "Platinum", "Diamond", "Starry", "Master", "Challenger"};
int limit[] = {
     0, 3, 3, 4, 5, 5, 5, 999, 999};//星数限制
int limit2[] = {
     0, 3, 4, 4, 5, 5, 5, 999, 999};//分段数量限制
int rule[] = {
     0, 3, 3, 3, 4, 5, 6, 999, 999};//连胜规则数量
char br[6][10] = {
     "", "one", "two", "three", "four", "five"};
//cnt 段位下标   counter 小段位下标 
int cnt, counter;
char c1[20], c2[20];
int c, n;

void ascertain() {
     
    //cnt 段位下标   counter 小段位下标 
    for (int i = 0; i < 9; i++) {
     
        if (strcmp(c1, r[i]) == 0) {
     //相同
            cnt = i;
            break;
        }
    }

    for (int i = 0; i < 6; i++) {
     
        if (strcmp(c2, br[i]) == 0) {
     
            counter = i;
            break;
        }
    }
}

void verify() {
     
    //王者判定 
    if (c > limit[cnt] && cnt != 7) {
     
        c -= limit[cnt];
        counter--;
        if (counter == 0) {
     
            cnt++;
            counter = limit2[cnt];
        }
    }
}

int main() {
     
    int cb[1000];
    while (cin >> c1 >> c2 >> c) {
     
        ascertain();//使cnt,counter,c转化为数字
        cin >> n;
        int plus = 0;
        for (int i = 0; i < n; i++) {
     
            cin >> cb[i];
            //判断连胜的过程
            //判断加星升段的过程 
            if (cb[i] == 1) {
     //连胜场数 
                plus++;
            } else {
     
                plus = 0;
            }
            if (plus == rule[cnt] && cb[i] == 1) {
     //可以连胜 
                c += 2;
                verify();
                plus = 0;
            } else if (cb[i] == 1) {
     //普通胜利 
                c++;
                verify();
            } else if (cb[i] == -1) {
     //本身段位是0的可能,退小段位,大段位 
                if (cnt == 1 && counter == 3 && c == 0 || cnt == 7 && c == 0) {
     
                    continue;//青铜,王者的特殊情况 
                }
                c--;
                if (c == -1) {
     //掉小段位
                    counter++;
                    if (counter == limit2[cnt] + 1) {
     //掉大段位
                        counter = 1;
                        cnt--;
                    }
                    c = limit[cnt] - 1;
                }
            }
        }
        if (cnt == 7 && c < 50) {
     //王者 
            cout << r[cnt] << " " << c << "\n";
        } else if (cnt == 7 && c >= 50) {
     //荣耀王者 
            cout << r[8] << "\n";
        } else {
     
            cout << r[cnt] << " " << br[counter] << " " << c << "\n";
        }
    }
}

这个大师太过正经

简单快速幂

按照一般的暴力过不了

#include

typedef long long ll;

int main() {
     
    ll t;
    scanf("%lld", &t);
    while (t--) {
     
        ll n, a, b, p = 1000003;
        ll res = 0;
        scanf("%lld", &n);
        for (ll i = 0; i < n; i++) {
     
            ll q = 1;
            scanf("%lld %lld", &a, &b);
            while (b) {
     
                if (b & 1)q = q * a % p;
                b >>= 1;
                a = a * a % p;
            }
            res = (res + q) % p;
        }
        printf("%lld\n", res % p);
    }
    return 0;
}

糖果的四十六亿种分法

题意:有n个盒子组成一个圆,盒子里总共有不超过n个蛋糕,有的有好几个,有的为0。可以将一个盒子里的蛋糕往左右两个盒子里移动,一次只能移动一个,使最终每个盒子里有不超过一个蛋糕(可以没有),求最小的移动数

思路:把盒子分为两种,有蛋糕和没蛋糕的,然后建图,对于有蛋糕的盒子拆点,拆为蛋糕数-1个点,因为它本身留一个蛋糕不移动。需要注意的是因为是个圆,所以移动的路径肯定不会大于n/2,大于n/2时判断一下就好了。

#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 510;
const int INF = 0x3f3f3f3f;
int n, nx, ny;
int lx[N], ly[N], slack[N], match[N], s[N][N];
bool visx[N], visy[N];
int v[N], tmp[N];

bool hungary(int v) {
     
    visx[v] = true;
    for (int i = 0; i < ny; i++) {
     
        if (visy[i]) continue;
        if (lx[v] + ly[i] == s[v][i]) {
     
            visy[i] = true;
            if (match[i] == -1 || hungary(match[i])) {
     
                match[i] = v;
                return true;
            }
        } else slack[i] = min(slack[i], lx[v] + ly[i] - s[v][i]);
    }

    return false;
}

void km() {
     
    memset(match, -1, sizeof match);
    memset(ly, 0, sizeof ly);
    for (int i = 0; i < nx; i++)
        lx[i] = -INF;
    for (int i = 0; i < nx; i++)
        for (int j = 0; j < ny; j++)
            lx[i] = max(lx[i], s[i][j]);
    for (int i = 0; i < nx; i++) {
     
        memset(slack, 0x3f, sizeof slack);
        while (true) {
     
            memset(visx, 0, sizeof visx);
            memset(visy, 0, sizeof visy);
            if (hungary(i)) break;
            else {
     
                int d = INF;
                for (int j = 0; j < ny; j++)
                    if (!visy[j]) d = min(d, slack[j]);
                for (int j = 0; j < nx; j++)
                    if (visx[j]) lx[j] -= d;
                for (int j = 0; j < ny; j++)
                    if (visy[j]) ly[j] += d;
                    else slack[j] -= d;
            }
        }
    }
}

int main() {
     
    int n;
    int a;
    cin >> a;
    while (a--) {
     
        cin >> n;
        int t = n / 2;
        for (int i = 0; i < n; i++)
            scanf("%d", v + i);
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                s[i][j] = -INF;

        int cnt1 = 0, cnt2 = 0;
        for (int i = 0; i < n; i++)
            if (v[i] == 0)
                tmp[i] = cnt2++;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < v[i] - 1; j++) {
     
                for (int k = 0; k < n; k++) {
     
                    if (v[k] == 0) {
     
                        if (abs(k - i) >= t)
                            s[cnt1][tmp[k]] = -(n - abs(k - i));
                        else
                            s[cnt1][tmp[k]] = -abs(k - i);
                    }
                }
                cnt1++;
            }

        nx = cnt1, ny = cnt2;
        km();

        int res = 0;
        for (int i = 0; i < ny; i++)
            if (match[i] != -1)
                res += s[match[i]][i];
        printf("%d\n", -res);
    }
    return 0;
}

我有一座研究院

过题人数最多的一道题

三种方法:暴力,贪心,公式

暴力

如果哪边的轻就加哪边,直到一边数量已经到达一半

#include
#include

using namespace std;

int main() {
     
    int t;
    cin >> t;
    while (t--) {
     
        int a = 0, b = 0, n, ta = 0, tb = 0;
        cin >> n;
        for (int i = n; i > 0; i--) {
     
            if (ta == n / 2) {
     
                b += pow(2, i);
                tb++;
            } else if (tb == n / 2) {
     
                a += pow(2, i);
                ta++;
            } else if (a <= b) {
     
                a += pow(2, i);
                ta++;
            } else if (a > b) {
     
                b += pow(2, i);
                tb++;
            }
        }
        cout << abs(a - b) << "\n";
    }
    return 0;
}

贪心

最后一个和前n-1个是一组,其他是一组,因为要保持两边的绝对值最小

#include
#include

using namespace std;

int main() {
     
    int t;
    cin >> t;
    while (t--) {
     
        int a = 0, b = 0, n;
        cin >> n;
        a += pow(2, n);
        for (int i = 1; i < n / 2; i++) {
     
            a += pow(2, i);
        }
        for (int i = n / 2; i < n; i++) {
     
            b += pow(2, i);
        }
        cout << abs(a - b) << "\n";
    }
    return 0;
}

公式

我们可以得出规律,按照规律推导结果

#include
#include

using namespace std;

int main() {
     
    int t, n;
    cin >> t;
    while (t--) {
     
        cin >> n;
        cout << pow(2, n / 2 + 1) - 2 << "\n";
    }
    return 0;
}

特拉福珠宝俱乐部

题意:
求树上每个点能走出的最远距离

思路:
树形DP
程序中的数据结构:
A数组记录V结点沿子树方向最长路所经过V的孩子编号。
B数组记录沿子树方向的次长路长度。
dp1数组 记录子树方向的最长路。
dp2数组记录来自父节点的最长路。
vectorcnt:记录每条边的信息。
我们假设一个结点V的孩子为{v1,v2,v3……vn};

  1. 如果vi在V沿子树方向的最长路上
    那么dis【vi】 = max(dp1【vi】,max(dp2【V】,B【V】) + dis(V,vi))
    说一下内嵌的max,以图中2(V)结点2结点来自父节点的最长路和2结点的子树方向次长路的较大值加上V 和vi的距离

  2. 如果vi不在V沿子树方向的最长路上
    那么dis【vi】= max(dp1【V】,dp2【V】) + dis(V, vi);
    最长路要么来自父节点子树方向的最长路加上dis(V,vi),要不来自父节点V的从V的父节点来的最长路 + dis(V, vi);

对于根节点特殊处理因为没有父节点只有次最长路,并且单孩子的结点没有次长路。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int INF = 510;
int n, A[INF], B[INF], dp1[INF],dp2[INF];
typedef pair<int, int> PLL;
vector<PLL>cnt[INF];
 
int dfs1(int root){
     
    int len = cnt[root].size();
    if(!len){
     //搜索到叶子
        return 0;
    }
    int Max = -1, loc1 = -1, loc2 = -1;
    for(int i = 0; i < len; i++){
     //寻找最长路
        if(dfs1(cnt[root][i].first) + cnt[root][i].second > Max){
     
            Max = dfs1(cnt[root][i].first) + cnt[root][i].second;
            loc1 = i;
        }
    }
    dp1[root] = Max;
    Max = -1;
    A[root] = loc1;
    for(int i = 0; i < len; i++){
     //寻找次长路
        if(dp1[cnt[root][i].first] + cnt[root][i].second > Max && i != loc1){
     
            Max = dp1[cnt[root][i].first] +cnt[root][i].second;
            loc2 = i;
        }
        if(loc2 != -1)B[root] = Max;
    }
    return dp1[root];
}
 
void dfs2(int root){
     
    int len = cnt[root].size();
    for(int i = 0; i < len; i++){
     
        if(i == A[root]){
     //这个孩子在沿子树方向的最长路径上
            dp2[cnt[root][i].first] = max(dp2[root], B[root]) + cnt[root][i].second;
        }
        else{
     
            dp2[cnt[root][i].first] = max(dp2[root], dp1[root]) + cnt[root][i].second;
        }
        dfs2(cnt[root][i].first);
    }
}
int main(){
     
    while(scanf("%d",&n) != EOF){
     
      memset(dp1, 0, sizeof(dp1));
      memset(B, 0, sizeof(B));
      memset(A, 0, sizeof(A));
      memset(dp2, 0, sizeof(dp2));
      for(int i = 1; i <= INF; i++){
     
          cnt[i].clear();
      }
      for(int i = 2; i <= n; i++){
     
          int x1, x2;
           scanf("%d%d", &x1, &x2);
           cnt[x1].push_back(make_pair(i, x2));
      }
      dfs1(1);
      dfs2(1);
      for(int i = 1; i <= n; i++){
     
        cout<<max(dp1[i], dp2[i])<<endl;
      }
 
    }
    return 0;
}

吃糖直播间

首先考虑最简单的情况。如果只有一个箱子,则选择该箱子,可以选择到最高总糖果数。如果只有两个箱子,则由于两个箱子相邻,不能同时选择,只能选择其中的一个箱子,因此选择其中糖果数较高的箱子进行选择,可以选择到最高总糖果数。

如果箱子数量大于两个,应该如何计算能够选择到的最高总糖果数呢?对于第 k(k>2) 个箱子,有两个选项:
1.选择第 k 个箱子,那么就不能选择第 k-1 个箱子,选择总糖果数为前 k-2 个箱子的最高总糖果数与第 k 个箱子的糖果数之和。
2.不选择第 k 个箱子,选择总糖果数为前 k-1 个箱子的最高总糖果数。

在两个选项中选择选择总糖果数较大的选项,该选项对应的选择总糖果数即为前 k 个箱子能选择到的最高总糖果数。

用 dp[i] 表示前 i 个箱子能选择到的最高总糖果数,那么就有如下的状态转移方程:
dp[i]=max(dp[i−2]+nums[i],dp[i−1])

边界条件为:
{
dp[0]=nums[0] 只有一个箱子,则吃该箱子中的糖果
dp[1]=max(nums[0],nums[1]) 有两个箱子,选择其中糖果数多的箱子。
}
最终的答案即为dp[n−1],其中 n 是数组的长度。

#include
#include

using namespace std;
const int maxn = 110;

int arr[maxn], dp[maxn][5];

int main() {
     
    int t, cas = 0;
    cin >> t;
    while (t--) {
     
        int n;
        cin >> n;
        for (int i = 1; i <= n; i++) {
     
            cin >> arr[i];
        }
        dp[1][0] = 0;
        dp[1][1] = arr[1];
        for (int i = 2; i <= n; i++) {
     
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]);
            dp[i][1] = dp[i - 1][0] + arr[i];
        }
        printf("Case #%d:\n", ++cas);
        cout << max(dp[n][0], dp[n][1]) << endl;
    }
}

我师傅实在太稳健了

我觉得这个看了代码秒懂,这个也是签到题吧…

#include

using namespace std;

int main() {
     
    int t;
    cin >> t;
    while (t--) {
     
        int a, b, c, d, e;
        cin >> a >> b >> c >> d >> e;
        if (a - b - c - d - e > 1 ||
            b - a - c - d - e > 1 ||
            c - d - e - a - b > 1 ||
            d - e - a - b - c > 1 ||
            e - a - b - c - d > 1) {
     
            cout << "no\n";
        } else {
     
            cout << "yes\n";
        }
    }
    return 0;
}

你看,是不是秒懂…

大奉卖笔人

大致思路如下:
1、先对输入的数组进行快速排序,从小到大排列
2、先进行第一步优化,将可以去掉的球先去掉,使后面的连续几类球都保持一样的数量,
在此过程中去掉的球的总价格计算进total里面,
这一步优化目的在于找到一个阈值threshold,使得去掉的球的数量threshold <= orders
一旦threshold > orders,结束此步。
这一步结束后,color_count表示优化了多少个类的球的数量,base表示下一步优化时球的基准,
optimize表示没有超过orders的最大threshold,即这一步去掉的球的总数。

例如:[2 4 6 8 10] orders = 20
-> [2 4 6 8 8] threshold = 10-(8×1) = 2 < orders
-> [2 4 6 6 6] threshold = (10+8)-(6×2) = 6 < orders
-> [2 4 4 4 4] threshold = (10+8+6)-(4×3) = 12 < orders
-> [2 2 2 2 2] threshold = (10+8+6+4)-(2×4) = 20 = orders
所以从[2 4 6 8 10]优化后是[2 2 2 2 2]
color_count = 4, base = 2,threshold = 20,optimize = 20

[2 4 6 8 10] orders = 23
-> [2 2 2 2 2] threshold = (10+8+6+4)-(2×4) = 20 < orders
-> [0 0 0 0 0] threshold = 10+8+6+4+2 = 30 > orders
所以从[2 4 6 8 10]优化后是[2 2 2 2 2]
color_count = 4, base = 2,threshold = 30,optimize = 20

3、这一步结束之后,我们已经去掉的球的数量是optimize,剩下的球的数量是orders-optimize
如果orders = optimize,直接返回total,就是最终的价格
否则再进行进一步优化
在第2步中的优化是从球最多的开始,每次优化1个,2个…n个球的数量
而在这一步中,现在要一次优化color_count个球的数量
在优化的过程,去掉的球的总价格就是color_count个等差数列的和
先计算(orders-optimize)/(color_count+1),得到等差数列的n
然后计算(order-optimize)%(color_count+1),得到计算完等差数列后剩下几个单独的球需要取出
a1 = base - n + 1
an = base
然后根据a1,an,n计算出等差数列的和,color_count个等差数列的和就是这一次优化的球的总价格
最后还剩下几个球要单独取出,它们的价格是base-n,数量 < color_count + 1
将批量的和单独的球的总价格都加进total里
最后得到total就是结果

注意在计算过程中注意数字溢出的问题

#include

using namespace std;

void quickSort(int arr[], int low, int high, int size);

int maxProfit(int *inventory, int inventorySize, int orders);

int main() {
     
    int num;
    cin >> num;
    int size;
    int inventory[100000];
    int orders;
    while (num--) {
     
        cin >> size;
        for (int i = 0; i < size; i++) {
     
            cin >> inventory[i];
        }
        cin >> orders;
        cout << maxProfit(inventory, size, orders) << "\n";
    }
}

void quickSort(int arr[], int low, int high, int size) {
     
    if (size == 1)
        return;
    if (size == 2) {
     
        if (arr[0] >= arr[1]) {
     
            int temp = arr[0];
            arr[0] = arr[1];
            arr[1] = temp;
            return;
        } else
            return;
    }
    if (low >= high)
        return;
    int first = low;
    int last = high;
    int key = arr[first];
    while (first < last) {
     
        while (first < last && arr[last] >= key) {
     
            last--;
        }
        arr[first] = arr[last];

        while (first < last && arr[first] < key) {
     
            first++;
        }
        arr[last] = arr[first];
    }
    arr[first] = key;
    quickSort(arr, low, first - 1, size);
    quickSort(arr, first + 1, high, size);
}

int maxProfit(int *inventory, int inventorySize, int orders) {
     
    quickSort(inventory, 0, inventorySize - 1, inventorySize);
    long int total = 0;
    if (inventorySize == 1) {
     
        long int a1 = inventory[0] - orders + 1;
        total = ((long int) orders * (a1 + (long int) inventory[0]) / 2) % 1000000007;
        return total;
    }
    int color_count = 0;
    long int balls_count = 0;
    long int threshold = 0;
    int optimize = 0;
    for (int m = inventorySize - 1; m >= 0; m--) {
     
        balls_count += (long int) inventory[m];
        if (m > 0)
            threshold = balls_count - (long int) (inventorySize - m) * inventory[m - 1];
        else
            threshold = balls_count;
        if (threshold <= (long int) orders) {
     
            optimize = threshold;
            color_count += 1;
        } else
            break;
    }
    int base = 0;
    if (color_count == inventorySize)
        base = 0;
    else
        base = inventory[inventorySize - color_count - 1];
    for (int n = 0; n < color_count; n++) {
     
        total = (total +
                 (long int) (inventory[inventorySize - n - 1] - base) * (base + 1 + inventory[inventorySize - n - 1]) /
                 2) % 1000000007;
    }
    if (optimize == orders)
        return total;
    int left = orders - optimize;
    int times = left / (color_count + 1);
    int single = left % (color_count + 1);
    long int times_total = 0;
    int a1 = base - times + 1;
    times_total =
            ((long int) times * ((long int) a1 + (long int) base) / 2 * (long int) (color_count + 1)) % 1000000007;;
    total = (total + times_total) % 1000000007;
    total = (total + (long int) (a1 - 1) * (long int) single) % 1000000007;
    return total;
}

最后预祝各位大佬在现场赛都能取得好成绩

2021-BNUZ-IT节程序设计竞赛网络赛题解_第2张图片

你可能感兴趣的:(题解)