2020中南大学研究生招生夏令营机试题题解

2020中南大学研究生招生夏令营机试题

第一题:缺失的彩虹

题意

颜色共有七种,给定 n ( n ≤ 100 ) n(n≤100) n(n100) 个颜色,问七种颜色中哪些没有出现。

思路

开一个大小为 7 7 7 的数组,分别统计七种颜色出现次数,最后看看哪些出现次数为 0 0 0 即可。

代码

#include "bits/stdc++.h"
using namespace std;

int main() {
    int n;
    string s;
    while(cin>>n) {
        vector<int> cnt(7,0);
        for(int i=1; i<=n; ++i) {
            cin>>s;
            if(s[0]=='r') cnt[0]++;
            else if(s[0]=='o') cnt[1]++;
            else if(s[0]=='y') cnt[2]++;
            else if(s[0]=='g') cnt[3]++;
            else if(s[0]=='c') cnt[4]++;
            else if(s[0]=='b') cnt[5]++;
            else if(s[0]=='p') cnt[6]++;
        }
        vector<char> ans;
        for(int i=0; i<7; ++i) if(cnt[i]==0) {
            ans.push_back(char('A'+i));
        }
        cout<<int(ans.size())<<'\n';
        for(char p: ans) cout<<p<<'\n';
    }
}

第二题:最小价值和

题意

给定 n ( n ≤ 1 e 5 ) n(n≤1e5) n(n1e5) 个整数数对 ( a , b ) ( 0 ≤ a , b ≤ 1 e 9 ) (a,b)(0≤a,b≤1e9) (a,b)(0a,b1e9),有一个长度也为 n n n 的数组。

若将某个数对放在数组的第 i i i 个位置上,则其价值为: a ∗ ( i − 1 ) + b ∗ ( n − i ) a*(i-1)+b*(n-i) a(i1)+b(ni)

求将这些数对分配在数组上后所有价值之和的最小值。

思路

这是一种常见的考察排序、贪心的题目。

我们需要的就是给这些数对排个序,而排序就得知道两个数对的“大小关系”,因此我们来考察一下两个数对:

a 1 , b 1 a_1,b_1 a1,b1 i i i 位置, a 2 , b 2 a_2,b_2 a2,b2 j j j 位置,不妨假设 i < j ii<j,现在我们仅仅考虑这两个数对交换前后分别产生的价值,并且他们是否应该交换位置不会影响其他数对的价值。

若: a 1 ∗ ( i − 1 ) + b 1 ∗ ( n − i ) + a 2 ∗ ( j − 1 ) + b 2 ∗ ( n − j ) > a 1 ∗ ( j − 1 ) + b 1 ∗ ( n − j ) + a 2 ∗ ( i − 1 ) + b 2 ∗ ( n − i ) a_1*(i-1)+b_1*(n-i)+a_2*(j-1)+b_2*(n-j)>a_1*(j-1)+b_1*(n-j)+a_2*(i-1)+b_2*(n-i) a1(i1)+b1(ni)+a2(j1)+b2(nj)>a1(j1)+b1(nj)+a2(i1)+b2(ni)

则: ( a 1 − a 2 ) ∗ ( i − 1 ) + ( b 1 − b 2 ) ∗ ( n − i ) > ( a 1 − a 2 ) ∗ ( j − 1 ) + ( b 1 − b 2 ) ∗ ( n − j ) (a_1-a_2)*(i-1)+(b_1-b_2)*(n-i)>(a_1-a_2)*(j-1)+(b_1-b2)*(n-j) (a1a2)(i1)+(b1b2)(ni)>(a1a2)(j1)+(b1b2)(nj)

则: ( a 1 − a 2 ) ∗ ( i − j ) > ( b 1 − b 2 ) ∗ ( i − j ) (a_1-a_2)*(i-j)>(b_1-b_2)*(i-j) (a1a2)(ij)>(b1b2)(ij)

则: a 1 − b 1 < a 2 − b 2 a_1-b_1a1b1<a2b2(由于 i < j ii<j,因此不等号方向改变)

上述推导表明若处于靠前的 i i i 位置的数对需要和靠后的 j j j 位置的数对发生交换,则应该满足前者的 a − b a-b ab 较小。

也就是说: a − b a-b ab 较大的数对应该放得更靠前!

这样,我们就可以设计排序函数了,通过 a − b a-b ab 的大小来决定两个数对的优先关系。

代码

#include "bits/stdc++.h"
using namespace std;
typedef long long ll;

const int maxn = 1e5+7;

struct Pair{
    int a, b;
    friend bool operator < (const Pair &A, const Pair &B) {
        return A.a-A.b>B.a-B.b;
    }
}p[maxn];

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    int n;
    while(cin>>n) {
        for(int i=1; i<=n; ++i) {
            cin>>p[i].a>>p[i].b;
        }
        sort(p+1,p+1+n); //sort就完事
        ll ans=0;
        for(int i=1; i<=n; ++i) {
            ans+=ll(i-1)*p[i].a+ll(n-i)*p[i].b; //小心爆int啦
        }
        cout<<ans<<'\n';
    }
}

第三题:PIPI上学路

题意

有一个 n ∗ m ( n , m ≤ 5000 ) n*m(n,m≤5000) nm(n,m5000) 的矩形,PIPI每次只能从矩形的某一个点向右走或向下走。

问从 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 共有多少种走法?(答案对 1 e 9 + 7 1e9+7 1e9+7取模)

难点:多组数据 + 每组数据 q ( q ≤ 5000 ) q(q≤5000) q(q5000) 个询问,因此总询问次数可能非常多!

思路

由于询问次数非常多,那我们能提前处理好所有可能的询问就好了。

考虑 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 之间的所有方格构成了一个矩形,并且此矩形在原 n ∗ m n*m nm 矩形中位置不影响方案数,只有这个矩形的尺寸决定方案数。

因此,我们预处理 n m a x ∗ n m a x ( n m a x = 5000 ) n_{max}*n_{max}(n_{max}=5000) nmaxnmax(nmax=5000) 的矩形即可,预处理:

设: w a y s [ i ] [ j ] ways[i][j] ways[i][j]表示从 ( 1 , 1 ) (1,1) (1,1) 走到 ( i , j ) (i,j) (i,j)处的方案数。

则: w a y s [ i ] [ j ] = w a y s [ i − 1 ] [ j ] + w a y s [ i ] [ j − 1 ] ways[i][j]=ways[i-1][j]+ways[i][j-1] ways[i][j]=ways[i1][j]+ways[i][j1]

处理好后,对于每次的询问可以直接 O ( 1 ) O(1) O(1) 回答: w a y s [ x 2 − x 1 + 1 ] [ y 2 − y 1 + 1 ] ways[x2-x1+1][y2-y1+1] ways[x2x1+1][y2y1+1]

时间复杂度: O ( n ∗ n + T ∗ q ) O(n*n + T * q) O(nn+Tq),空间复杂度: O ( n ∗ n ) O(n*n) O(nn)

(PS:尽量不要使用cin和cout,否则输入输出速度太慢,自行优化)

代码

#include "bits/stdc++.h"
using namespace std;

const int maxn = 5e3+7;
const int mod = 1e9+7;

int ways[maxn][maxn];

int main() {
    ways[1][0]=1; //一个假想的入口(1,0),方便对ways[1][1]进行赋值
    for(int i=1; i<maxn; ++i) {
        for(int j=1; j<maxn; ++j) {
            ways[i][j]=(ways[i-1][j]+ways[i][j-1])%mod;
        }
    }
    int n, m, q;
    ios::sync_with_stdio(false); cin.tie(0); //这里通过采用取消输入输出同步的方式加快读入
    while(cin>>n>>m>>q) {
        while(q--) {
            int x1, y1, x2, y2;
            cin>>x1>>y1>>x2>>y2;
            cout<<ways[x2-x1+1][y2-y1+1]<<'\n'; //不要cout<
        }
    }
}

第四题:最大容量和

题意

m m m 根木棍, m = n ∗ k ( n , k ≤ 1 e 5 ) m=n*k(n,k≤1e5) m=nk(n,k1e5) n n n 个桶,每个桶由 k k k 根木棍构成,桶的容量由最短的木棍长度决定,桶的底面积为 1 1 1,木棍长度不大于 1 e 9 1e9 1e9

现要求最大的桶和最小的桶容量差小于等于 L L L,问 n n n 个桶的最大容量和。

如果无法满足组成 n n n 个桶,输出 0 0 0

思路

由于桶的容量由构成它的最短木棍决定,因此我们希望那些决定桶的容量的木棍就尽可能长。

在不考虑 L L L 的情况下,我们只需要将所有木棍拍个序,然后从较大的到较小的依次选择木棍,每有 k k k 根木棍,我们就拿他们构建一个桶。

显然,这样构建的 n n n 个桶容量都是尽可能大了。

然后再来考虑 L L L 这个限制条件,由于所有木棍最短的那根一定决定了最小容量桶的容量,因此,我们只要最大的桶不要用大于 a m i n + L a_{min}+L amin+L 的木棍决定它即可。

最终,我们仍然只需要从大到小选择木棍,但一定要从不大于 a m i n + L a_{min}+L amin+L 的地方开始构建桶;同理,若这样的方法最后导致有些木棍没有参与构建桶子,那么就输出 0 0 0 ,具体见代码。

代码

#include "bits/stdc++.h"
using namespace std;
typedef long long ll;

const int maxn = 1e5+7;

int a[maxn];

int main() {
    int n, k, L;
    scanf("%d%d%d", &n, &k, &L);
    int N=n*k;
    for(int i=1; i<=N; ++i) scanf("%d", &a[i]);
    sort(a+1,a+1+N);
    int max_position=upper_bound(a+1,a+1+N,a[1]+L)-a; //最大桶不能取到的第一个地方
    ll ans=0;
    int cur_num=0; //cur_num记录有多少木棍等待使用
    for(int i=N; i; --i) {
        cur_num++;
        if(i<max_position&&cur_num>=k) {
            ans+=a[i]; //用a[i]决定这个桶的容量
            cur_num-=k; //使用k根木棍
        }
    }
    if(cur_num>0) printf("0\n"); //说明还有木棍没用上,那么就不能组成n个桶了
    else printf("%lld\n", ans);
}

第五题:最小特征值之和

题意

给定一个常数 c c c,若一个数组长度为 l l l,则它的特征值为除去最小的 l / c l/c l/c (向下取整)外的所有元素之和。

给定一个长度为 n ( n ≤ 1 e 6 ) n(n≤1e6) n(n1e6) 的数组和常数 c ( c ≤ 1 e 6 ) c(c≤1e6) c(c1e6),数组的元素为不大于 1 e 9 1e9 1e9的自然数,求一个最优的划分,使得所有子数组特征值之和最小。

思路

首先,特征值之和最小意味着未参与贡献的元素之和最大。

本题数据非常大( 1 e 6 1e6 1e6级别),意味着只能使用 O ( n ) O(n) O(n) O ( l o g 2 n ) O(log_2n) O(log2n) 的方法,因此思考的方向就少了很多。

考虑某种划分中出现了长度为 l l l的子数组:

  • l < c ll<c,那么这个子数组所有元素都参与了贡献,等效于 l l l 个长度为 1 1 1 的子数组连在一起。
  • c < l < 2 c cc<l<2c,那么这个子数组中只有一个元素不参与贡献,此时若将这个子数组拆成 1 1 1 个长度为 c c c 的子数组和 l − c l-c lc 个长度为 1 1 1 的数组,则结果可能更优,因为长度为 c c c 的子数组中最小元素可能比长为 l l l 的子数组最小元素更大,即不参与贡献的元素更大。
  • l ≥ 2 c l≥2c l2c,那么将这个子数组最左边的长为 c c c 的元素分离出来,结果可能更优,因为这个长为 c c c 的数组中最小元素可能比之前 l l l 数组中不参与贡献的最大元素还要大,即这个操作使得不参与贡献的元素可能变大。剩下长为 l − c l-c lc 的子数组可同理处理。

观察上述分析可知,划分过程中所有子数组长度仅为 1 1 1 c c c 就可以得到最优解,大方向确定后只剩下如何得到最优解。

考虑 O ( n ) O(n) O(n) 的做一次动态规划,令 d p [ i ] dp[i] dp[i] 表示前 i i i 个元素中不参与贡献元素之和最大值,则转移方程为:

d p [ i ] = m a x ( d p [ i − 1 ] , d p [ i − c ] + m i n ( a [ i − c + 1 … i ] ) dp[i]=max(dp[i-1],dp[i-c]+min(a[i-c+1\dots i]) dp[i]=max(dp[i1],dp[ic]+min(a[ic+1i])

m i n ( a [ i − c … i ] ) min(a[i-c\dots i]) min(a[ici]) 可通过维护一个大小始终为 c c c 的可重集或滑动窗口实现。

这样,得到最大不参与贡献元素和后,最小参与贡献元素和也就出来了:

a n s = s u m − d p [ n ] ans=sum-dp[n] ans=sumdp[n]

时间复杂度: O ( n l o g 2 c ) O(nlog_2c) O(nlog2c)

代码

#include "bits/stdc++.h"
using namespace std;
typedef long long ll;

const int maxn = 1e6+7;

int n, c;
int a[maxn];
ll dp[maxn];

int main() {
    scanf("%d%d", &n, &c);
    multiset<int> s; //要使用可重集哦,不然有些元素可能会丢掉
    ll sum=0;
    for(int i=1; i<=n; ++i) {
        scanf("%d", &a[i]);
        dp[i]=dp[i-1];
        s.insert(a[i]);
        if(i>c) s.erase(s.find(a[i-c])); //总是保证s中只有c个元素
        if(i>=c) {
            dp[i]=max(dp[i],dp[i-c]+(*s.begin()));
        }
        sum+=a[i];
    }
    printf("%lld\n", sum-dp[n]);
}

你可能感兴趣的:(编程机试指南)