XATU第九届算法大赛题解

目录

A.小闫买蛋糕

题目描述

 小闫同学进入了一个蛋糕店,想带走尽可能多的蛋糕。
蛋糕店里有 m m m 个货柜,第 i i i 个货柜里有 a i a_i ai 个蛋糕盒,每个蛋糕盒内都有 b i b_i bi 个蛋糕。
 所有的蛋糕盒大小相同,但是蛋糕不可以被重新分装在不同的蛋糕盒里。
 小闫的背包恰好能装 n n n 个蛋糕盒。
 问:小闫能够带走的最大蛋糕数量是多少。

输入格式

 输入的第一行包含整数 n ( 1   ≤   n   ≤   2 × 1 0 8 ) n (1 ≤ n ≤ 2×10^8) n(1 n 2×108) 和整数 m ( 1   ≤   m   ≤   20 ) m (1 ≤ m ≤ 20) m(1 m 20). 第 i   +   1 i + 1 i+ 1 行包含一对数字 a i a_i ai b i ( 1   ≤   a i   ≤   1 0 8 ,   1   ≤   b i   ≤   10 ) b_i(1 ≤ a_i ≤ 10^8, 1 ≤ b_i ≤ 10) bi(1 ai 108, 1 bi 10). 所有输入数字都是整数。

输出格式

 输出问题的唯一数字-答案。

样例 #1

样例输入 #1

1 1
1 2

样例输出 #1

2

题解

 此题为简单题。根据题目可得,每个蛋糕盒的大小相同,背包恰好能装 n n n 个蛋糕盒,要求最大蛋糕数量,只需要每个蛋糕盒中蛋糕尽可能的多,所以我们只需要将所有蛋糕盒中的蛋糕数存入数组,然后从大到小排序,输出前 n n n 个即可,但是,如果暴力存储每一个蛋糕盒中的蛋糕数,会导致内存超限,因此,我们需要转换思路,去存储每个货柜中的蛋糕盒数和每个蛋糕盒内的蛋糕数,这样就避免了大量的重复工作。

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

int main(){
    int n, m;
    cin >> n >> m;
    vector<pair<int, int>> arr;
    for (int i = 0; i < m;i++){
        int a, b;
        cin >> a >> b;
        arr.push_back({b, a});
    }
    sort(arr.begin(), arr.end());
    int cnt = 0;
    int sum = 0;
    for (int i = m - 1; i >= 0;i--){
        if (cnt + arr[i].second <= n){
            cnt += arr[i].second;
            sum += arr[i].first * arr[i].second;
        }else{
            if (cnt < n){
                sum += arr[i].first * (n - cnt);
                cnt = n;
            }else{
                break;
            }
        }
    }
    cout << sum << endl;
    return 0;
}


B.I’m coming! Algorithm competition!

Description

 Math Planet will hold an algorithm competition, with abundant prizes prepared, which makes all participants eager to give it a try. In the competition, all questions will be divided into k k k difficulty levels, and every level contains n 1 , n 2 , … , n k n_ 1,n_ 2,…,n_ k n1,n2,,nk questions.

 Xiao Deng is one of the participants, and it is known that his probability of solving a problem at the i − t h i-th ith difficulty level is p i p_ i pi. Please help him calculate the probability of his successful AK. (AK usually refers to obtaining full marks in a competition)

Input

  The first line,a positive integer k k k —— the number of difficulty levels。 ( 1 ≤ k ≤ 10 ) (1 \leq k \leq 10) (1k10)
 The second line,there are k k k numbers n 1 , n 2 , … , n k n_1,n_2,…,n_k n1,n2,,nk,which represent the number of questions at every difficulty level。 ( 1 ≤ n i ≤ 200 ) (1 \leq n_i \leq 200) (1ni200)
 The third line, there are k k k numbers p 1 , p 2 , … , p k p_1,p_2,…,p_k p1,p2,,pk,which represent his probability of solving every problem。 ( 0 ≤ p i ≤ 1 ) (0 \leq p_i \leq 1) (0pi1)

Output

 In one row, output the probability of Xiao Deng’s successful AK.

Requirements

 结果四舍五入,保留四位小数。

Example #1

Input #1

3
1 2 3
0.3 0.5 0.7

Output #1

0.0257

Solution

 此题为签到题。将每道题做出的概率进行累乘,即可得到最终的答案。


#include <iostream>
using namespace std;
const int maxn = 1e5 + 50;

int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}

int cnt[maxn];
double p[maxn];

int main()
{
    int k = read();
    for (int i = 1; i <= k; i++){
        cnt[i] = read();
    }
    for (int i = 1; i <= k; i++){
        p[i] = read();
    }
    double ans = 1;
    for (int i = 1; i <= k; i++){
        for (int j = 1; j <= cnt[i]; j++){
            ans *= p[i];
        }
    }
    printf("%.4lf", ans);
    return 0;
}


C.急速招聘会

题目描述

 为了鼓励大学生就业,学校决定召开前所未有的“急速招聘会”。所谓“急速招聘会”,是指每场招聘会只持续两分钟,并且每隔两分钟就会召开下一场招聘会,且每场招聘会只会在经纬广场或是小广场召开,不会同时在两个广场召开招聘会。智子的好朋友茄子想要模仿演员张颂文的求职经历,投出尽可能多的简历。在求助智子无果后,茄子找到了精通算法的你希望你能帮他解决疑惑。

 茄子投递简历时有几个特点:

  1. 茄子只会在一场招聘会中投下一份简历,当然,他也可以选择不投;
  2. 两分钟只够他从一个广场前往另一个广场,并投下简历,他的体力只支持他换 m m m次会场,当然,他也可以选择不换会场,一开始他在经纬广场;
  3. 为了保证万无一失,茄子在有足够普通简历(假设他有无穷多的普通简历)的情况下,还准备了 v v v份彩印简历。

 每份彩印简历在经纬广场投递相当于7份普通简历,在小广场投递相当于5份普通简历。问:茄子最终投递的简历,最多相当于多少份普通简历?

输入格式

 输入为 n + 1 n + 1 n+1行。

 第一行包含三个整数 n n n, m m m, v v v。分别代表有 n n n场招聘会,茄子能够辗转会场 m m m次,茄子准备的彩印简历的数目( 1 ≤ n ≤ 1 × 1 0 3 1\le n\le 1×10^3 1n1×103, 1 ≤ m ≤ 30 1\le m\le 30 1m30, 1 ≤ v ≤ 30 1\le v\le 30 1v30)。

 随后n行,每行一个数字 1 1 1 2 2 2 1 1 1代表招聘会在经纬广场召开, 2 2 2代表招聘会在小广场召开。

输出格式

 输出为一行。一个整数,茄子最终投递的简历,最多相当于多少份普通简历。

样例 #1

样例输入 #1

7 2 3
2
1
1
2
2
1
1

样例输出 #1

24

样例解释 #1

 茄子在跳过第一场招聘会,即选择呆在经纬广场。之后,他选择在随后经纬的两次招聘会中投下两份彩印简历,相当于14份普通简历;在第4场招聘会时,茄子前往小广场投下一份普通简历,并等待第5场招聘会投下一份普通简历;在第6场招聘会时,茄子又前往经纬广场投下一份彩印简历,相当于7份普通简历;等到第7场招聘会到来时他已经没有彩印简历了,投下了一份普通简历。总共,14 + 2 + 7 + 1 = 24份普通简历。

题解

 此题为中等题。经典的询问最优(最多)类型的动态规划问题,其实我更偏向于是记忆化搜索。
每场每场招聘会召开时茄子可以做三个动作:

  1. 投一份普通简历。前提是必须在招聘会召开的广场
  2. 换广场。前提是他的体力足够( m ≠ 0 m \ne 0 m=0
  3. 投一份彩印简历。前提是必须在招聘会召开的广场,且( s ≠ 0 s \ne 0 s=0

 在搜索的过程中讨论茄子可以做的动作,并使用 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]描述和记忆状态。其中, i i i表示第 i i i场招聘会, j j j:还可以跳几次, k k k:彩印简历的个数。
 注意,显然此问题满足最优子结构,即第 i i i场招聘会所能投下的简历依赖于第 i − 1 i-1 i1场招聘会所能投下的简历,对于参数 j j j k k k同样如此。
参考状态转移方程:
小广场
d p [ i ] [ j ] [ k ] = m a x { d p [ i − 1 ] [ j ] [ k ] + 1 , d p [ i − 1 ] [ j − 1 ] [ k ] + 1 , d p [ i − 1 ] [ j ] [ k − 1 ] + 5 , d p [ i − 1 ] [ j − 1 ] [ k ] + 5 } dp[i][j][k] = max\left \{ dp[i - 1][j][k] + 1, dp[i - 1][j - 1][k] + 1, dp[i - 1][j][k - 1] + 5, dp[i - 1][j - 1][k] + 5 \right \} dp[i][j][k]=max{dp[i1][j][k]+1,dp[i1][j1][k]+1,dp[i1][j][k1]+5,dp[i1][j1][k]+5}
经纬广场
d p [ i ] [ j ] [ k ] = m a x { d p [ i − 1 ] [ j ] [ k ] + 1 , d p [ i − 1 ] [ j − 1 ] [ k ] + 1 , d p [ i − 1 ] [ j ] [ k − 1 ] + 7 , d p [ i − 1 ] [ j − 1 ] [ k ] + 7 } dp[i][j][k] = max\left \{ dp[i - 1][j][k] + 1, dp[i - 1][j - 1][k] + 1, dp[i - 1][j][k - 1] + 7, dp[i - 1][j - 1][k] + 7 \right \} dp[i][j][k]=max{dp[i1][j][k]+1,dp[i1][j1][k]+1,dp[i1][j][k1]+7,dp[i1][j1][k]+7}

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e3 + 5;
const int M = 2e5 + 5;
const int inf = 0x3f3f3f3f;
typedef pair<int, int> pii;
#define debug(x) cout<<#x<<" = "<<x<<endl
#define all(a) a.begin(), a.end()
#define print(fg) puts(fg ? "YES" : "NO")

int n, w, s;
int arr[N], dp[N][35][35];

// k:第k次 m:还可以跳几次 pos:当前在那个会场 v:彩印简历的个数
int dfs(int k, int m, int v, int pos) {

    if(dp[k][m][v]) {
        return dp[k][m][v];
    }

    if(k > n - 1) {
        return 0;
    }

    int ans1{0}, ans2{0};
    if(pos == arr[k]) { // 会场相同
        ans1 = dfs(k + 1, m, v, pos) + 1; // 投普通简历
        if(v) { // 投彩印简历
            ans1 = max(ans1, dfs(k + 1, m, v - 1, pos) + (arr[k] == 1 ? 7 : 5));
        }
    } else { // 会场不同 参数m-1表示换会场了
        if(m) {
            ans2 = dfs(k + 1, m - 1, v, arr[k]) + 1;// 投普通简历
            if(v) { // 投彩印简历
                ans2 = max(ans2, dfs(k + 1, m - 1, v - 1, pos) + (arr[k] == 1 ? 7 : 5));
            }
        }
        ans2 = max(ans2, dfs(k + 1, m, v, pos));
    }
    return dp[k][m][v] = max(ans1, ans2);
}

int main()
{
    cin >> n >> w >> s;
    for (int i = 0; i < n; ++i) {
        cin >> arr[i];
    }

    cout << dfs(0, w, s, 1) << endl;
    return 0;
}

D.Beautiful Number

Description

 Zhizi is sensitive to numbers.He would be happy if an integer is a “Beautiful Number”,and an integer x x x is called a beautiful number if it is divisible by ⌊ x ⌋ \left \lfloor \sqrt{x} \right \rfloor x .

 Here ⌊ x ⌋ \left \lfloor x \right \rfloor x
denotes the “floor” of a real number x x x. In other words, it’s the largest integer not greater than x x x.For example: 8 is a beautiful number, since 8 is divisible by ⌊ 8 ⌋ \left \lfloor \sqrt{8} \right \rfloor 8 = ⌊ 2.8284 ⌋ \left \lfloor 2.8284 \right \rfloor 2.8284 = 2.

 Being a friend of Zhizi, could you help he find how many beautiful numbers between the range [L, R].

Input

 The first line contains an integer T T T( 1 ≤ T ≤ 1 × 1 0 4 1\le T\le 1×10^4 1T1×104). The description of the test cases follows.

 In the following each line, there are two numbers L and R in each line, which is the closed interval [ L , R ] [L, R] [L,R]( 1 ≤ L ≤ R ≤ 1 × 1 0 18 1\le L \le R \le 1×10^{18} 1LR1×1018).

Output

 T integers. How many beautiful numbers are there in each inquiry interval.

Example #1

Input #1

5
8 19
8 20
119 121
1 100000000000000000
1234567891011 1000000000000000000

Output #1

5
6
2
948683296
2996666667

Solution

 此题为中等题。题目大意是:对于给定区间 [ L , R ] [L, R] [L,R] ( 1 ≤ L ≤ R ≤ 1 × 1 0 18 ) (1\le L \le R \le 1×10^{18}) (1LR1×1018),问有多少个数能被自己的开根向下取整整除。显然,最直观的方式就是遍历区间并逐个验证,但是注意区间大小这种方式一定会超时的。
 那么,考虑更数学一点的方式。考虑俩个完全平方数之间会有多少个"beautiful number"?这恐怕也不容易,进一步考虑两个相邻的完全平方数之间 [ x 2 , ( x + 1 ) 2 ) [x^2,(x + 1)^2) [x2,(x+1)2)会有多少个"beautiful number"?将其展开 [ x 2 , x 2 + 2 x + 1 ) [x^2,x^2 + 2x + 1) [x2,x2+2x+1),显然此时会有三个满足要求的数字 x 2 , x 2 + x , x 2 + 2 x x^2,x^2 + x,x^2 + 2x x2,x2+x,x2+2x,因为相邻两个完全平方数开根号并向下取整是 x x x
 此时,你应该得出每两个完全平方数之间均有3个"beautiful number",并且只要稍微特判一下边界就可以 O ( 1 ) O(1) O(1)的时间耗费解决这道题目了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;
typedef pair<int, int> pii;
#define debug(x) cout<<#x<<" = "<<x<<endl
#define all(a) a.begin(), a.end()
#define print(fg) puts(fg ? "YES" : "NO")

ll cacul(ll x){
    if(x == 0) return 0;
    // 开根并向下取整
    ll t = sqrt(x);
 
    ll res = 3 * (t - 1);
    // 特判边界
    if(x >= t*t)
        ++res;
    if(x >= t*t + t)
        ++res;
    if(x >= t*t + 2*t)
        ++res;
    return res;
}

int main()
{
    int t{1};
    cin>>t;
    while (t--) {
        ll l, r;
        cin >> l >> r;
        cout << cacul(r) - cacul(l - 1) << endl;
    }
    return 0;
}

E.小x序列

题目描述

 此题为简单题。最近小x喜欢上了一种数字序列:连续数字之间的差严格地在正数和负数之间交替,第一个数字可正可负,我们称其为小x序列。小x认为仅有一个数或者含两个不同数字的也是这种序列。
 子序列可以通过原始序列中删除一些元素来获得(也可以不删除),剩下的元素保持其原始顺序。
 现在小x将给你一个序列 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an,请你输出该序列中满足小x序列的最长子序列的长度。

输入格式

 输入共两行,第一行一个数字,代表数列的长度。

 第二行共个数字,代表序列,数字间以空格分隔。

输出格式

 输出共一个数字,代表满足小x要求的最长子序列长度。

样例 #1

样例输入 #1

5
1 3 2 15 7

输出 #1

5

样例 #2

样例输入 #2

10
1 17 5 10 13 15 10 5 16 8

样例解释 #2

 其中包含长度为7的最长子序列,其中一个为 [ 1 , 17 , 10 , 13 , 10 , 16 , 8 ] [1, 17, 10, 13, 10, 16, 8] [1,17,10,13,10,16,8]

样例输出 #2

7

样例 #3

样例输入 #3

4
1 2 3 4

样例输出 #3

2

数据规模和约定

1 ≤ n ≤ 1 0 3 1\leq n \leq10^3 1n103
0 ≤ a i ≤ 1 0 3 0 \leq a_i \leq 10^3 0ai103

题解

 观察这个序列可以发现,我们不断地交错选择差值正数和负数,可以使得该序列尽可能长(可以自己找规律推一下),如果我们选择了一个元素,那么在原序列中,这个元素的两侧有一个正数和一个负数,我们假设在原序列中的出现顺序为「+1」「元素」「-1」。如果元素在选择的序列中小于其两侧的元素,那么「-1」一定没有在选择的序列中出现,我们可以将「元素」替换成「-1」,另一种可能同理。所以在实际代码中,我们记录当前序列的取值,每次向后面遍历一个元素时,用新的差值与之前相乘,如果结果为负,答案加一,并更新当前序列的差值。

#include<iostream>
#include<vector>
using namespace std;
int main() {
    vector<int> nums;
    int n;
    cin>>n;
    while(n--){
        int c;
        cin>>c;
        nums.push_back(c);
    }
    vector<int> arr;
    int maxx=0;
    if(nums.size()==1){
        cout<<1;
        return 0;
    }
    for(int i=1;i<nums.size();i++){
        arr.push_back(nums[i]-nums[i-1]);
    }
    int len=1;
    for(int i=0;i<arr.size();i++){
        int j=i+1;
        int pre=arr[i];
        if(pre==0){
            len=0;
        }
        while(j<arr.size()){
            if(pre*arr[j]<0){
                len++;
                pre=arr[j];
            }
            j++;
        }
        maxx=max(maxx,len);
        len=1;
    }
    cout<<maxx+1;
    return 0;
}

F.Habitable Zone

Description

 Mr.Space travels to a new planet, where there is M × N M×N M×N areas. Mr.Space wants to find a habitable zone by measuring the temperature index of every area.

 Mr.Space holds the belief that a habitable zone consists of k × k k×k k×k adjacent areas, and the sum of temperature indices is between [ a , b ] [a, b] [a,b].

 However, Mr.Space doesn’t even know how to count. Therefore, in order to find a habitable zone, he decides to ask you q q q queries. For every query, you’re given a different k k k —— the side length of a habitable zone. You just need to tell him how many habitable zones there are on the planet when k k k is certain.

Input

 The first line contains three integers m m m, n n n and q q q ( 1 ≤ m , n , q ≤ 1 × 1 0 3 ) (1\leq m,n,q \leq 1×10^3) (1m,n,q1×103) m m m, n n n represent the number of rows and columns of areas. q q q represents the number of queries.
 Each of the next n n n lines contains m m m integers — the temperature index of every area.
 The next line contains two integers a a a, b b b ( − 1 0 5 ≤ a , b ≤ 1 0 5 ) (-10^5≤a,b≤10^5) (105a,b105) — the range of the temperature index of a habitable zone.
 The next q q q lines contains only one integer k k k ( 1 ≤ k ≤ m i n { m , n } ) (1≤k≤min\{m, n\}) (1kmin{m,n}) — the side length of a habitable zone.

Output

 For each test case, output q q q lines. Every line contains an integer representing the answer to every query.

** Requirements**

 Please choose a better algorithm to reduce time complexity.

Example #1

Input #1

3 3 2
1 -1 1
2 -2 0
2 1 -5
-2 2
2
1

Output #1

2
8

Solution

 此题为简单题。题目要求在 M × N M×N M×N的矩阵中,计数 k × k k×k k×k的矩阵,其中所有元素和介于 [ a , b ] [a,b] [a,b]之间。暴力模拟会超时,本题需要采用二维前缀和来进行优化,通过求出各位置的前缀和,来将查询的时间复杂度优化至 O ( 1 ) O(1) O(1),即可解决该问题。


#include <iostream>
using namespace std;
const int maxn = 1e4 + 50;

int arr[maxn][maxn], prefix[maxn][maxn];

int main()
{
    int m, n, q;
    cin >> m >> n >> q;
    for (int i = 1; i <= m; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> arr[i][j];
            prefix[i][j] = prefix[i - 1][j] + prefix[i][j - 1] - prefix[i - 1][j - 1] + arr[i][j];
        }
    }
    int a, b, cnt;
    cin >> a >> b;
    while (q--)
    {
        int k;
        cin >> k;
        cnt = 0;
        for (int i = 1; i + k - 1 <= m; i++)
        {
            for (int j = 1; j + k - 1 <= n; j++)
            {
                int sum = prefix[i + k - 1][j + k - 1] - prefix[i - 1][j + k - 1] - prefix[i + k - 1][j - 1] + prefix[i - 1][j - 1];
                if (sum >= a && sum <= b)
                    cnt++;
            }
        }
        cout << cnt << endl;
    }
    return 0;
}


G.[NOI1999]生日蛋糕

题目描述

 XXY和WYX一起去西工大参加比赛,但是很不幸,XXY晕车了,WYX要为此制作一个体积为 N π N\pi Nπ M M M 层晕车纪念碑,每层都是一个圆柱体。

 设从下往上数第 i i i 1 ≤ i ≤ M 1 \leq i \leq M 1iM)层纪念碑的半径是 R i R_i Ri,高度为 H i H_i Hi 的圆柱。当 i < M i \lt M i<M 时,要求 R i > R i + 1 R_i \gt R_{i+1} Ri>Ri+1 H i > H i + 1 H_i \gt H_{i+1} Hi>Hi+1

 由于要在纪念碑上涂颜料,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积 Q Q Q 最小。

 请编程对给出的 N N N M M M,找出纪念碑的制作方案(适当的 R i R_i Ri H i H_i Hi 的值),使 S = Q π S=\dfrac{Q}{\pi} S=πQ 最小。

 (除 Q Q Q 外,以上所有数据皆为正整数)

输入格式

 第一行为一个整数 N N N N ≤ 2 × 1 0 4 N \leq 2 \times 10^4 N2×104),表示待制作的纪念碑的体积为 N π N\pi Nπ

 第二行为 M M M M ≤ 15 M \leq 15 M15),表示蛋糕的层数为 M M M

输出格式

 输出一个整数 S S S,若无解,输出 0 0 0

样例 #1

样例输入 #1

100
2

样例输出 #1

68

题解

 此题为中等题。可以采用DFS,找个数组存储半径和高,可是如单单使用DFS不加剪枝的话,10分——20分。

 所以,我们来想一想如何剪枝:

  1. 当前的奶油面积+之后的最小奶油面积>现在已求出的的最小奶油面积——果断return;

  2. 当前的体积大于 n n n,则return;

  3. 当前的体积+之后的最大体积<体积总数,果断return;

  4. 发现每次枚举半径和高时,是从上一个的半径和高,到还剩下的层数。这是因为每一层的半径和高都要比下一层的小1,所以每一层都要留一个1;

 OK,现在我们加上剪枝之后就可以通过了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
using namespace std;
int r[30],h[30],minn=2147483647,n,m;
void dfs(int x,int y,int k,int z)
{  if(y<0) return;
    if(x>m+1) return;
     if(k>=minn) return;
    if(y==0&&x==m+1)
    {  k+=r[1]*r[1];
         if(k<minn) minn=k;
         return;
    }
    if(k+z+r[1]*r[1]>minn) return;
   if(y-(r[x-1])*(r[x-1])*(h[x-1])*z>0) return;
    for(int i=r[x-1]-1;i>=z;i--)
      for(int j=h[x-1]-1;j>=z;j--)
      {
            if(y-i*i*j>=0&&x+1<=m+1)
             {     r[x]=i;
                   h[x]=j;
                    dfs(x+1,y-i*i*j,k+(i*2*j),z-1);
                   h[x]=0;
                   r[x]=0;
             }
      }
}
int main()
{
    scanf("%d%d",&n,&m);
    r[0]=(int)sqrt(n);
    h[0]=(int)sqrt(n);
    dfs(1,n,0,m);
    if(minn==2147483647) printf("%d",0);
      else printf("%d",minn);
    return 0;
}

H.Remove Numbers

Description

 You are given an array of non-decreasing integers a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an.
 Each time, you can select an index i i i and remove a i a_i ai from the array. You are allowed to perform such operation for k k k times.
 Your purpose is to make the minimum of the difference between two adjacent integers as bigger as possible. That is to say, there is an integer m m m satisfying that for every j ∈ [ 2 , n ] j∈[2,n] j[2,n], the value of a j  –  a j − 1 a_j\ –\ a_{j-1} aj  aj1 is no less than m m m. You need to find the maximum of m after no more than k k k operations.

Input

 The first line of the input contains a single integer t t t ( 1 ≤ t ≤ 1 0 4 ) (1≤t≤10^4) (1t104) — the number test cases.
 Then the descriptions of the input data sets follow.
 The first line of each test case contains two integers n n n and k k k ( 2 ≤ n ≤ 1 0 5 , 0 ≤ k ≤ 1 0 5 , k ≤ n − 2 ) (2≤n≤10^5, 0≤k≤10^5, k≤n-2) (2n105,0k105,kn2) — the length of the non-decreasing array and the times you can remove a number.
 The second line of each test case contains n n n integers — a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an. ( − 1 0 8 ≤ a i ≤ 1 0 8 ) (-10^8≤a_i≤10^8) (108ai108)

Output

 For each test case, output a line containing an integer representing the maximum of m m m.

Example #1

Input #1

2
6 3
-1 0 1 3 5 9
2 0
0 100000

Output #1

4
100000

Explanation #1

 In the first case, you can remove − 1 , 0 , 3 −1,0,3 1,0,3. After that, the smallest difference between two adjacent numbers is 4 4 4.
 In the second case, you can’t remove any number, so the answer is 100000 100000 100000.

Solution

 此题为中等题。本题给出一个非降序序列,并从少删除不多于 k k k个数字,要求删除后的序列中,最小的相邻差值(即 a j − a j − 1 a_{j}-a_{j-1} ajaj1)尽可能大。要求我们求出这个差值。本题采用二分答案的算法,根据题意可以确定答案范围介于 [ 0 , 2 × 1 0 8 ] [0,2×10^8] [0,2×108]之间,通过对答案进行不断的二分,并将答案代入题意中进行检验,最终找到最大且满足条件的最小差值。

#include <iostream>
using namespace std;
const int maxn = 1e5 + 50;

int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}

int n, k, arr[maxn];

bool check(int mid)
{
    int pre = 1, cnt = k;
    for (int cur = 2; cur <= n; cur++)
    {
        if (arr[cur] - arr[pre] >= mid)
        {
            cnt -= cur - pre - 1;
            if (cnt < 0)
                return false;
            pre = cur;
        }
    }
    if (pre == n)
        return true;
    return false;
}

int main()
{
    int t = read();
    while (t--)
    {
        n = read(), k = read();
        for (int i = 1; i <= n; i++)
        {
            arr[i] = read();
        }
        int l = 0, r = 2e8;
        while (l < r)
        {
            int mid = l + r + 1 >> 1;
            if (check(mid))
                l = mid;
            else
                r = mid - 1;
        }
        cout << r << endl;
    }
    return 0;
}


I.队长与桌游

题目描述

 队长是一个热爱桌游的大学生,现在他被一个桌游难住了,快来帮帮他!

 这个桌游的地图可以被抽象成一个由 n n n个点, m m m条边组成的有向无环图(不保证连通),队长在这个地图上行走,队长能走到某个点当且仅当能够到达这个点的所有点都已经被队长走到。队长会走到每个点恰好 11 11 11次,并且他能走到哪些点与他当前所在的点没有关系(即可以走到与当前所在的点没有连边的点,只要满足之前的条件)。

 队长每走到一个标号比之前走到的点都大的点,他就会有 1 2 \frac{1}{2} 21的概率从对手那里拿到 1 1 1块筹码,有 1 2 \frac{1}{2} 21的概率给对手 1 1 1块筹码,双方初始各有 1919810 1919810 1919810个筹码 。

 队长的运气时好时坏,所以他希望你帮他计算出:

  • 在最优情况下,即他每次都能从对手那里拿到筹码时,他采取最优的行走方式能得到的筹码数。
  • 在最劣情况下,即对手每次都能从他那里拿到筹码时,他采取最优的行走方式会失去的筹码数。

输入格式

 第一行两个正整数 n n n, m m m

 接下来 m m m行,每行两个正整数 u u u, v v v,表示地图上有一条有向边,不保证无重边。

输出格式

 输出两行,每行一个正整数,第一行表示最优情况下队长能拿到的筹码数,第二行表示最劣情况下队长会失去的筹码数。

样例 #1

样例输入 #1

3 2
1 2
1 3

样例输出 #1

3
2

题解

 此题为较难题。

  • 本题算是一个贪心,如果是最优的情况,那么每一次都优先选择当前最小的且能够更新(也就是入度为0)的点更新,因为当前越小,后面能够选择的就越多,然后如果当前有比当前最大权值还小的点,那么可以选择直接更新,因为这样的点是不会对答案有影响的,而且还能够提供更多的选择来更新。所以最优的策略可以总结为,每一次选择当前可以选择的最小的点更新。
  • 如果是最劣的情况就有一些不一样,对于当前能够更新的点,如果当前能够选择的点的最小权值也比我们当前更新到的最大权值还要大的话。那最优的就是把当前权值最大的点更新。因为当前最大权值如果越大的话,那么后面产生贡献的可能性就越小。这时就可以将所有点权比当前的最大权值小的点都更新了。因为这些点留着也永远不会产生贡献,那么就选择更新。就这样一直到所有的点更新完。
  • 然后因为题目的更新法则符合拓扑排序,那么就跑两遍拓扑排序跑最优和最劣情况就行了。然后对于这些最大和最小的点,就用堆(优先队列)来优化。
#include<queue>
#include<cstdio>
#include<vector>
using namespace std;
queue<int>kz;
priority_queue<int,vector<int>,greater<int> >qgreater;
priority_queue<int,vector<int>,less<int> >qless;
vector<int>g[500001];
int in[500001],in2[500001];
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++){
        int u,v;
        scanf("%d %d",&u,&v);
        g[u].push_back(v);
        in[v]++;
        in2[v]++;
    }
    for(int i=1;i<=n;i++){
        if(in[i]==0){
            qgreater.push(i);
            qless.push(i);
        }
    }
    //"最优情况" 
    int maxn=0,ans=0;
    while(!qgreater.empty()){
        int x=qgreater.top();
        qgreater.pop();
        if(x>maxn){
            ans++;
        }
        maxn=max(maxn,x);
        for(int j=0;j<g[x].size();j++){
            int y=g[x][j];
            in[y]--;
            if(in[y]==0){
                qgreater.push(y);
            }
        }
    }
    printf("%d\n",ans);
    //"最劣情况" 
    maxn=0,ans=0;
    while(!qless.empty()){
        int x=qless.top();
        if(x>maxn){
            ans++;
        }
        while(!qless.empty()){
            kz.push(qless.top());
            qless.pop();
        }
        while(!kz.empty()){
            int nx=kz.front();
            kz.pop();
            maxn=max(maxn,nx);
            for(int j=0;j<g[nx].size();j++){
                int y=g[nx][j];
                in2[y]--;
                if(in2[y]==0){
                    if(y>maxn){
                        qless.push(y);
                    }else{
                        kz.push(y);
                    }
                }
            }
        }
    }
    printf("%d",ans);
    return 0;
}`

J.Turn on all the lights

Description

 XXY is playing a puzzle game with the goal of turning on all the nine lights, but it is too difficult for XXY!

 The mechanism of the lights is odd, where one click will change the switch status of itself and all four surrounding lights. Please help XXY calculate how many steps is required to turn on all the lights given.

 Here’s a puzzle for example:

0  1  1
1  0  0
1  0  1

 Just click on the middle light ( 2 , 2 ) (2,2) (2,2) and it will become:

0  0  1
0  1  1
1  1  1

 Just click on the light in the upper left corner again, and it will become:

1  1  1
1  1  1
1  1  1

 To achieve the goal,at least 2 steps are required.So 2 is the answer to the problem.

输入格式

 Nine numbers in a format of 3 × 3 3×3 3×3, with only one space between each two numbers, indicating the initial switch state of the light. ( 0 0 0 represents the light is off, and 1 1 1 represents the opposite)

输出格式

 1 integer representing the minimum number of steps required to turn on all lights.

样例 #1

样例输入 #1

0 1 1
1 0 0
1 0 1

样例输出 #1

2

题解

 此题为中等题。通过严谨的证明,我们知道,同一个开关不可能被使用两次,所以我们就可以放心大胆地直接深度优先搜索了。可以用G数组表示目前灯的开关状态,用 u s e use use数组表示开关是否用过(用过为 1 1 1,未用过为 0 0 0),再加上数据范围较小,总的运算次数不会超过 O ( 9 9 ) O(9^9) O(99),可以实现。

#include<iostream>
using namespace std;
int G[5][5],ans=10;///由题可知,答案不会超过10,所以ans的初始值为10
bool use[5][5];///判断是否用过
///小技巧:用a和b两个数组快速完成位置变换
int a[5]={+0,+0,+1,-1,0};
int b[5]={+1,-1,+0,-0,0};
bool check()///判断函数
{
    for(int i=1;i<=3;i++)
        for(int j=1;j<=3;j++)
            if(!G[i][j])
                return false;
    return true;
}
void change(int x,int y)///变化函数
{
    for(int i=0;i<5;i++) G[x+a[i]][y+b[i]]=1-G[x+a[i]][y+b[i]];
}
void dfs(int step)///主体部分
{
    if(step>=ans) return ;///最(mei)优(shen)剪(me)枝(yong)
    if(check()) ans=min(ans,step);///找到答案
    else
    for(int i=1;i<=3;i++)
        for(int j=1;j<=3;j++)
            if(!use[i][j])
            {
                use[i][j]=1;
                change(i,j);
                dfs(step+1);
                ///回溯
                use[i][j]=0;
                change(i,j);
            }
}
int main()
{
    for(int i=1;i<=3;i++)
        for(int j=1;j<=3;j++)
            cin>>G[i][j];
    dfs(0);
    cout<<ans;
    return 0;
}


K.小闫取蛋糕

题目描述

 小闫同学进入了一个蛋糕店,考过四级的她今天非常开心,于是她买下了店内所有的蛋糕,当然这些蛋糕里可能会有款式相同的蛋糕。

 小闫把这 N N N 个蛋糕从 1 1 1 N N N 编号,然后从编号 L L L R R R 中取两个蛋糕,她要和她的闺蜜小黄一起吃,因此她想要取两个款式相同的蛋糕,当然,从多个蛋糕中随机取到两个款式相同蛋糕是个概率问题,小闫希望这个概率尽量高,所以她可能会询问多个 [ L , R ] [L,R] [L,R] 以方便自己选择。

你的任务是告诉小闫,她有多大的概率抽到两个款式相同的蛋糕。当然,小闫希望这个概率尽量高,所以她可能会询问多个 [ L , R ] [L,R] [L,R] 以方便自己选择。

输入格式

 输入文件第一行包含两个正整数 N N N Q Q Q N N N 为所有蛋糕的数量, Q Q Q 为小闫所询问的数量。

 接下来一行包含 N N N 个正整数 C i C_i Ci,其中 C i C_i Ci 表示第 i i i 个蛋糕的款式,相同的款式用相同的数字表示。

 再接下来 Q Q Q 行,每行两个正整数 L L L R R R 表示一个询问的区间 [ L , R ] [L,R] [L,R]

输出格式

 包含 Q Q Q 行,对于每个询问在一行中输出分数 A / B A/B A/B 表示从该询问的区间 [ L , R ] [L,R] [L,R] 中随机抽出两个蛋糕款式相同的概率。

 若该概率为 0 0 0 则输出 0 / 1 0/1 0/1,否则输出的 A / B A/B A/B 必须为最简分数。(详见样例)

样例 #1

样例输入 #1

7 2
1 3 2 2 3 1 2
1 3
1 4

样例输出 #1

0/1
1/6

样例解释 #1

 询问1:共 C ( 3 , 2 ) = 3 C(3,2)=3 C(3,2)=3 种可能,无法抽到款式相同的蛋糕,概率为 0 / 3 = 0 / 1 0/3=0/1 0/3=0/1

 询问2:共 C ( 4 , 2 ) = 6 C(4,2)=6 C(4,2)=6 种可能,其中抽出两个2有1种可能,概率为 1 / 6 1/6 1/6

 注:上述 C ( a , b ) C(a,b) C(a,b) 表示组合数,组合数 C ( a , b ) C(a,b) C(a,b) 等价于在 a a a 个不同的物品中选取 b b b 个的选取方案数。

数据规模和约定

 30%的数据中 N , Q ≤ 5000 N,Q ≤ 5000 N,Q5000

 60%的数据中 N , Q ≤ 25000 N,Q ≤ 25000 N,Q25000

 100%的数据中 N , Q ≤ 50000 , 1 ≤ L < R ≤ N , C i ≤ N N,Q ≤ 50000,1 ≤ L < R ≤ N,C_i ≤ N N,Q500001L<RNCiN

题解

 此题为困难题。这是一道可以用莫队算法解决的经典问题。具体来说,我们可以将所有蛋糕按款式分成若干个块,并对每个块内的蛋糕按出现顺序进行排序。对于每个询问 [ l , r ] [l,r] [l,r] ,我们需要求出 [ l , r ] [l,r] [l,r] 区间中能够成对的蛋糕的个数。

 由于蛋糕的款式总共有 m m m 种,因此块的数量最多为 m m m 。对于每个块内的蛋糕,我们可以使用哈希表等数据结构来记录每种款式的蛋糕的数量。当 [ l , r ] [l,r] [l,r] 所在的块不完整时,我们可以直接暴力求解 [ l , r ] [l,r] [l,r] 区间内能够成对的蛋糕的个数。否则,我们只需要考虑 [ l , r ] [l,r] [l,r] 所在的块以及相邻的块即可。

 具体来说,我们可以维护一个类似于莫涛队列的数据结构,其中用双指针 [ l , r ] [l,r] [l,r] 来表示当前的滑动窗口,每次向左或向右移动一个元素时,只需要更新滑动窗口内的元素,并在滑动窗口进入或离开一个块的时候重新计算块内蛋糕的数量即可。在计算块内蛋糕的数量时,我们可以利用之前的哈希表信息,通过哈希表的查找操作实现 O ( 1 ) O(1) O(1) 的时间复杂度。

 总时间复杂度为 O ( n n + m ) O(n\sqrt{n}+m) O(nn +m),其中 n n n\sqrt{n} nn 是莫队算法的复杂度, m m m 是哈希表操作的复杂度。由于哈希表的复杂度为 O ( 1 ) O(1) O(1),因此总时间复杂度为 O ( n n ) O(n\sqrt{n}) O(nn )

#include<bits/stdc++.h>
using namespace std;
const int N = 5e4 + 5;
typedef long long ll;

ll n, k, sum;
ll arr[N], ans[N], cnt[N];

struct query{
    ll l, r, idx, block;
}q[N];

void add(int x) {
    sum -= cnt[arr[x]] * (cnt[arr[x]] - 1) / 2;
    cnt[arr[x]]++;
    sum += cnt[arr[x]] * (cnt[arr[x]] - 1) / 2;
}

void sub(int x) {
    sum -= cnt[arr[x]] * (cnt[arr[x]] - 1) / 2;
    cnt[arr[x]]--;
    sum += cnt[arr[x]] * (cnt[arr[x]] - 1) / 2;
}

int main(){
    cin >> n >> k;
    ll q_n = sqrt(n);
    for(int i = 1; i <= n; ++i) {
        cin >> arr[i];
    }
    for(int i = 1; i <= k; ++i) {
        cin >> q[i].l >> q[i].r;
        q[i].idx = i;
        q[i].block = (q[i].l - 1) / q_n + 1;
    }
    
    sort(q + 1, q + k + 1, [](const query& q1, const query& q2){
        if(q1.block != q2.block) {
            return q1.l < q2.l;
        }
        if(q1.l & 1) {
            return q1.r < q2.r;
        }
        return q1.r > q2.r;
    });
    
    int l = 1, r = 0;
    for(int i = 1; i <= k; ++i) {
        while(l > q[i].l) add(--l);
        while(r < q[i].r) add(++r);
        while(l < q[i].l) sub(l++);
        while(r > q[i].r) sub(r--);
        ans[q[i].idx] = sum;
    }
    sort(q + 1, q + k + 1, [](const query& q1, const query& q2){
        return q1.idx < q2.idx;
    });
    
    for(int i = 1; i <= k; ++i) {
        ll fm = (q[i].r - q[i].l + 1) * (q[i].r - q[i].l) / 2;
        ll x = __gcd(ans[i], fm);
        if(q[i].l == q[i].r) {
            cout << "0/1\n";
        } else {
            cout << (ans[i] / x) << "/" << (fm / x) << "\n";
        }
    }
    return 0;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值