笔试强训题(8)

1. Day43

1.1 kotori和抽卡(二)(概率 - 数学期望)

  1. 题目链接kotori和抽卡(二)
  2. 题目描述:

  1. 解法:
    • 算法思路:直接代入高中求概率的公式~
  2. C++ 算法代码:
#include <iostream>
using namespace std;

int main()
{
    int n, m;
    cin >> n >> m;
    
    double ret = 1.0;
    for(int i = 0; i < m; i++)
    {
        ret *= 0.8;
    }
    
    for(int i = 0; i < n - m; i++)
    {
        ret *= 0.2;
    }
    
    for(int i = n; i >= n - m + 1; i--)
    {
        ret *= i;
    }
    
    for(int i = m; i >= 2; i--)
    {
        ret /= i;
    }
    
    printf("%.4lf", ret);
    
    return 0;
}

1.2 ruby和薯条(排序 + 二分 / 双指针)

  1. 题目链接ruby和薯条
  2. 题目描述:

  1. 解法:
    • 算法思路:
      • 解法⼀:排序 + 二分。 先排序,然后枚举较大值,在 [1, i - 1] 区间找差值的左右端点即可。
      • 解法二:排序 + 前缀和 + 双指针。 先排序;求差值在 [L, R] 区间内数对的个数,可以转化成求 [0, R] 区间内的个数 - [0, L] 区间内的个数。其中求 [0, X] 区间内数对的个数,可以用双指针快速统计出以 arr[right] 为结尾的数对有多少个。
  2. C++ 算法代码:
// 解法⼀:排序 + ⼆分
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 2e5 + 10;

int main()
{
    int n, l, r;
    cin >> n >> l >> r;
    int arr[N];
    for(int i = 1; i <= n; i++)
    {
        cin >> arr[i];
    }
    
    sort(arr + 1, arr + n + 1);
    
    long long ret = 0;
    for(int i = 2; i <= n; i++)
    {
        int L, R;
        
        // 找左端点
        int left = 1;
        int right = i - 1;
        while(left < right)
        {
            int mid = (right + left) / 2;
            if(arr[mid] >= arr[i] - r)
            {
                right = mid;
            }
            else
            {
                left = mid + 1;
            }
        }
        
        if(arr[left] >= arr[i] - r)
        {
            L = left;
        }
        else
        {
            L = left + 1;
        }
        
        // 找右端点
        left = 1;
        right = i - 1;
        while(left < right)
        {
            int mid = (right + left + 1) / 2;
            if(arr[mid] <= arr[i] - l)
            {
                left = mid;
            }
            else
            {
                right = mid - 1;
            }
        }
        
        if(arr[left] <= arr[i] - l)
        {
            R = left;
        }
        else
        {
            R = left - 1;
        }
        
        if(R >= L)
        {
            ret += R - L + 1;
        }
    }
    
    cout << ret << endl;
    
    return 0;
}

// 解法⼆:排序 + 前缀和 + 滑动窗⼝
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 2e5 + 10;
int n, l, r;
int arr[N];

// 找出差值在 [0, x] 之间⼀共有多少对
long long find(int x)
{
	int left = 0, right = 0;
	long long ret = 0;
	while(right < n)
	{
		while(arr[right] - arr[left] > x)
		{
			left++;
		}
		
		ret += right - left;
		right++;
	}
	
	return ret;
}
int main()
{
	cin >> n >> l >> r;
	for(int i = 0; i < n; i++) cin >> arr[i];
	sort(arr, arr + n);
	cout << find(r) - find(l - 1) << endl;
	return 0;
}

1.3 循环汉诺塔(动态规划)

  1. 题目链接AB27 循环汉诺塔
  2. 题目描述:

  1. 解法:
    • 算法思路:动态规划 + 空间优化。
    • 规律如下:AB(1)= 1,AC(1)= 2,AB(N)= 2 * AC(N-1)+ 1,AC(N)= 2 * AC(N-1)+ AB(N-1)+ 2
  2. C++ 算法代码:
#include <iostream>
using namespace std;

const int MOD = 1e9 + 7;

int main() 
{
    int n = 0;
    cin >> n;
    int x = 1;
    int y = 2;
    for(int i = 2; i <= n; i++)
    {
        int xx = x, yy = y;
        x = (2 * yy + 1) % MOD;
        y = ((2 * yy) % MOD + 2 + xx) % MOD;
    }

    cout << x << " " << y << endl;

    return 0;
}

2. Day44

2.1 差值(排序)

  1. 题目链接最小差值
  2. 题目描述:

  1. 解法:
    • 算法思路:排序,然后计算相邻两个数之差的最小值即可。
  2. C++ 算法代码:
class Solution {
public:

    int minDifference(vector<int>& a) 
    {
        sort(a.begin(), a.end());
        
        long long ret = 1e16 + 10;;
        for(int i = 1; i < a.size(); i++)
        {
            ret = min(ret, (long long)a[i] - a[i - 1]);
        }
        
        return ret;
    }
};

2.2 kotori和素因子(DFS)

  1. 题目链接: kotori和素因子
  2. 题目描述:

  1. 解法:
    • 算法思路:递归型枚举所有的情况。
  2. C++ 算法代码:
#include <iostream>
#include <cmath>
using namespace std;

const int N = 15, M = 1010;
int n = 0;
int arr[N] = {0};
bool use[M]; // 记录路径中⽤了哪些值
int path; // 记录当前路径中所有元素的和
int ret = 0x3f3f3f3f; // 统计最终结果

bool isPrim(int x)
{
    if(x <= 1)
    {
        return false;
    }
    
    for(int i = 2; i <= sqrt(x); i++)
    {
        if(x % i == 0)
        {
            return false;
        }
    }
    
    return true;
}

void dfs(int pos)
{
    if(pos == n)
    {
        ret = min(ret, path);
    }
    
    for(int i = 2; i <= arr[pos]; i++)
    {
        if(arr[pos] % i == 0 && isPrim(i) && !use[i])
        {
            path += i;
            use[i] = true;
            dfs(pos + 1);
            
            // 回溯 - 恢复现场
            path -= i;
            use[i] = false;
        }
    }
}

int main()
{
    cin >> n;
    for(int i = 0; i < n; i++)
    {
        cin >> arr[i];
    }
    
    dfs(0);
    
    if(ret == 0x3f3f3f3f)
    {
        cout << -1 << endl;
    }
    else
    {
        cout << ret << endl;
    }
    
    return 0;
}

2.3 dd爱科学1.0(最长上升子序列 - 贪心 + 二分)

  1. 题目链接dd爱科学1.0
  2. 题目描述:

  1. 解法:
    • 算法思路:
      • 要想改动最小,就应该在最长非下降子序列的基础上,对不是最长的部分进行更换。
      • 因为这道题的数据范围比较大,所以应该用贪心 + 二分求出最长非下降子序列的长度。
  2. C++ 算法代码:
#include <iostream>
using namespace std;

const int N = 1e6 + 10;

int main()
{
    int n = 0;
    string s;
    cin >> n >> s;
    
    char dp[N]; // dp[i] 表⽰:⻓度为 i 的所有的⼦序列中,最⼩的末尾是多少
    int ret = 0;
    for(int i = 0; i < n; i++)
    {
        char ch = s[i];
        if(ret == 0 || dp[ret] <= ch)
        {
            dp[++ret] = ch;
        }
        else
        {
            // ⼆分出 ch 应该放的位置
            int left = 1;
            int right = ret;
            while(left < right)
            {
                int mid = (left + right) / 2;
                if(dp[mid] > ch)
                {
                    right = mid;
                }
                else
                {
                    left = mid + 1;
                }
            }
            
            dp[left] = ch;
        }
    }
    
    cout << n - ret << endl;
    
    return 0;
}

3. Day45

3.1 kanan和高音(模拟 + 双指针)

  1. 题目链接kanan和高音
  2. 题目描述:

  1. 解法:
    • 算法思路:从前往后遍历,用双指针找出⼀段能唱完的区域,然后更新指针继续找下⼀段。
  2. C++ 算法代码:
#include <iostream>
using namespace std;

const int N = 2e5 + 10;
int n;
int arr[N];

int main()
{
    cin >> n;
    for(int i = 0; i < n; i++)
    {
        cin >> arr[i];
    }
    
    int ret = 1;
    int left = 0;
    while(left < n)
    {
        int right = left;
        while(right + 1 < n && arr[right + 1] - arr[right] <= 8)
        {
            right++;
        }
        
        ret = max(ret, right - left + 1);
        left = right + 1;
    }
    
    cout << ret << endl;
    
    return 0;
}

3.2 拜访(BFS)

  1. 题目链接MT3 拜访
  2. 题目描述:

  1. 解法:
    • 算法思路:在层序遍历的过程中,维护额外的信息。
  2. C++ 算法代码:
class Solution {
public:
    int x1, y1, x2, y2;
    int dist[15][15] = { 0 };   // 判断是否经过
    int cnt[15][15] = { 0 };    // 到改位置的最短路径的数量
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};

    int bfs(vector<vector<int> >& CityMap, int n, int m)
    {
        memset(dist, -1, sizeof dist);
        queue<pair<int, int>> q;
        q.push({x1, y1});
        dist[x1][y1] = 0;
        cnt[x1][y1] = 1;

        while(q.size())
        {
            auto [a, b] = q.front();
            q.pop();
            for(int i = 0; i < 4; i++)
            {
                int x = a + dx[i];
                int y = b + dy[i];

                if(x >= 0 && x < n && y >= 0 && y < m && CityMap[x][y] != -1)
                {
                    if(dist[x][y] == -1)    // 第⼀次到这个位置
                    {
                        dist[x][y] = dist[a][b] + 1;
                        cnt[x][y] += cnt[a][b];
                        q.push({x, y});
                    }
                    else 
                    {
                        if(dist[a][b] + 1 == dist[x][y]) // 是不是最短路
                        {
                            cnt[x][y] += cnt[a][b];
                        }
                    }
                }
            }
        }

        return cnt[x2][y2];
    }

    int countPath(vector<vector<int> >& CityMap, int n, int m) 
    {
        for(int i = 0; i < n; i++)
        {
            for(int j = 0; j < m; j++)
            {
                if(CityMap[i][j] == 1)
                {
                    x1 = i;
                    y1 = j;
                }
                else if(CityMap[i][j] == 2)
                {
                    x2 = i;
                    y2 = j;
                }
            }
        }

        return bfs(CityMap, n, m);
    }
};

3.3 买卖股票的最好时机(四)(动态规划)

  1. 题目链接DP33 买卖股票的最好时机(四)
  2. 题目描述:

  1. 解法以及算法思路:
    1. 状态表示:为了更加清晰的区分「买入」和「卖出」,我们换成「有股票」和「无股票」两个状态。
      • f[i][j] 表示:第 i 天结束后,完成了 j 笔交易,此时处于「有股票」状态的最大收益;
      • g[i][j] 表示:第 i 天结束后,完成了 j 笔交易,此时处于「无股票」状态的最大收益。
    2. 状态转移方程:
      • 对于 f [i][j] ,我们也有两种情况能在第 i 天结束之后,完成 j 笔交易,此时手里「有股票」的状态:
        • 在 i - 1 天的时候,手里「有股票」,并且交易了 j 次。在第 i 天的时候,啥也不干。此时的收益为 f[i - 1][j] ;
        • 在 i - 1 天的时候,手里「没有股票」,并且交易了 j 次。在第 i 天的时候,买了股票。那么 i 天结束之后,我们就有股票了。此时的收益为 g[i - 1][j] - prices[i] ;
        • 上述两种情况,我们需要的是「最大值」,因此 f 的状态转移方程为: f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i])
      • 对于 g [i][j] ,我们有下面两种情况能在第 i 天结束之后,完成 j 笔交易,此时手里「没有股票」的状态:
        • 在 i - 1 天的时候,手里「没有股票」,并且交易了 j 次。在第 i 天的时候,啥也不干。此时的收益为 g[i - 1][j] ;
        • 在 i - 1 天的时候,手里「有股票」,并且交易了 j - 1 次。在第 i 天的时候,把股票卖了。那么 i 天结束之后,我们就交易了 j 次。此时的收益为 f[i - 1][j - 1] + prices[i] ;
        • 上述两种情况,我们需要的是「最大值」,因此 g 的状态转移方程为: g[i][j] = max(g[i - 1][j], f[i - 1][j - 1] + prices[i])
    3. 初始化:
      • 由于需要用到 i = 0 时的状态,因此我们初始化第一行即可。
      • 当处于第 0 天的时候,只能处于「买入过⼀次」的状态,此时的收益为 -prices[0] ,因此 f[0][0] = - prices[0] 。
      • 为了取 max 的时候,⼀些不存在的状态「起不到干扰」的作用,我们统统将它们初始化为 - INF (用 INT_MIN 在计算过程中会有「溢出」的风险,这里 INF 折半取 0x3f3f3f3f ,足够小即可)。
    4. 填表顺序:
      • 从上往下填每⼀行,每⼀行从左往右,两个表⼀起填。
    5. 返回值:
      • 返回处于卖出状态的最大值,但是我们也不知道是交易了几次,因此返回 g 表最后⼀行的最大值。
    6. 优化点:
      • 我们的交易次数是不会超过整个天数的⼀半的,因此我们可以先把 k 处理⼀下,优化⼀下问题的规模:k = min(k, n / 2)。
    7. 如果画一个图的话,它们之间交易关系如下:

  1. C++ 算法代码:
#include <iostream>
using namespace std;

const int N = 1010, M = 110;
int n, k, p[N];
int f[N][M], g[N][M];

int main() 
{
    cin >> n >> k;
    for(int i = 0; i < n; i++)
    {
        cin >> p[i];
    }

    k = min(k, n / 2);
    for(int j = 0; j <= k; j++)
    {
        f[0][j] = g[0][j] = -0x3f3f3f3f;
    }

    f[0][0] = -p[0];
    g[0][0] = 0;
    for(int i = 1; i < n; i++)
    {
        for(int j = 0; j <= k; j++)
        {
            f[i][j] = max(f[i - 1][j], g[i - 1][j] - p[i]);
            g[i][j] = g[i - 1][j];
            if(j >= 1)
            {
                g[i][j] = max(g[i][j], f[i - 1][j - 1] + p[i]);
            }
        }
    }

    int ret = 0;
    for(int j = 0; j <= k; j++)
    {
        ret = max(ret, g[n - 1][j]);
    }

    cout << ret << endl;

    return 0;
}

4. Day46

4.1 AOE还是单体?(贪心)

  1. 题目链接AOE还是单体?
  2. 题目描述:

  1. 解法:
    • 算法思路。小贪心:
      • 如果使用一次 AOE 造成的伤害比消耗的蓝量多,那就使用;
      • 否则就一直使用单体伤害。
  2. C++ 算法代码:
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 2e5 + 10;

int main()
{
    int n, x;
    cin >> n >> x;
    int arr[N];
    for(int i = 1; i <= n; i++)
    {
        cin >> arr[i];
    }
    
    sort(arr + 1, arr + n + 1);
    long long ret = 0;
    int index = max(0, n - x);
    ret += arr[index] * x;
    for(int i = index + 1; i <= n; i++)
    {
        ret += arr[i] - arr[index];
    }
    
    cout << ret << endl;
    
    return 0;
}

4.2 kotori和n皇后(哈希表)

  1. 题目链接kotori和n皇后
  2. 题目描述:

  1. 解法:
    • 算法思路:使用哈希表标记行列以及两个对角线。
  2. C++ 算法代码:
#include <iostream>
#include <unordered_set>
using namespace std;

int k, t;
int ret = 1e5 + 10; // 第⼀次出现互相攻击的皇后的个数
unordered_set<long long> row; // 标记⾏ y
unordered_set<long long> col; // 标记列 x
unordered_set<long long> dig1; // 标记主对⻆线 y - x
unordered_set<long long> dig2; // 标记副对⻆线 y + x

int main()
{
    cin >> k;
    for(int i = 1; i <= k; i++)
    {
        int x, y;
        cin >> x >> y;
        if(ret != 1e5 + 10)
        {
            continue;
        }
        
        if(row.count(y) || col.count(x) || dig1.count(y - x) || dig2.count(y + x))
        {
            ret = i;
        }

        row.insert(y); 
        col.insert(x);
        dig1.insert(y - x);
        dig2.insert(y + x);
    }
    
    cin >> t;
    while(t--)
    {
        int i = 0;
        cin >> i;
        if(i >= ret)
        {
            cout << "Yes" << endl;
        }
        else
        {
            cout << "No" << endl;
        }
    }
    
    return 0;
}

4.3 取金币(动态规划 - 区间dp)

  1. 题目链接NC393 取金币
  2. 题目描述:

  1. 解法:
    • 算法思路。区间 dp:
      • 为了方便能处理边界情况,将原数组前后添加⼀个 1,并不影响最后的结果。
      • 状态表示:
        • dp[i][j] 表示: [i, j] 区间⼀共能获得多少金币。
  2. C++ 算法代码:
class Solution {
public:
    int arr[110] = { 0 };
    int dp[110][110] = { 0 };

    int getCoins(vector<int>& coins) 
    {
        int n = coins.size();
        arr[0] = arr[n + 1] = 1;
        for(int i = 1; i <= n; i++)
        {
            arr[i] = coins[i - 1];
        }

        for(int i = n; i >= 1; i--)
        {
            for(int j = i; j <= n; j++)
            {
                for(int k = i; k <= j; k++)
                {
                    dp[i][j] = max(dp[i][j], dp[i][k - 1] + dp[k + 1][j] + arr[i - 1] * arr[k] * arr[j + 1]);
                }
            }
        }

        return dp[1][n];
    }
};

5. Day47

5.1 矩阵转置(数学)

  1. 题目链接BC138 矩阵转置
  2. 题目描述:

  1. 解法:
    • 算法思路:观察转置前和转置后下标的关系即可。
  2. C++ 算法代码:
#include <iostream>
using namespace std;

const int N = 15;
int n, m;
int arr[N][N];

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

    for(int j = 0; j < m; j++)
    {
        for(int i = 0; i < n; i++)
        {
            cout << arr[i][j] << " ";
        }

        cout << endl;
    }

    return 0;
}

5.2 四个选项(DFS + 剪枝 + 哈希表)

  1. 题目链接四个选项
  2. 题目描述:

  1. 解法:
    • 算法思路:用递归枚举出所有的情况,注意剪枝。
  2. C++ 算法代码:
#include <iostream>
#include <vector>
using namespace std;

int cnt[5]; // ⽤数组存每⼀个选项出现多少次
int m, x, y;
bool same[13][13]; // 存哪些题的答案是相同的
int ret;
vector<int> path; // 记录路径⾥⾯选了哪些选项

bool isSame(int pos, int cur)
{
    for(int i = 1; i < pos; i++)
    {
        if(same[pos][i] && path[i] != cur)
        {
            return false;
        }
    }
    
    return true;
}

void dfs(int pos)
{
    if(pos > 12)
    {
        ret++;
        return;
    }
    
    for(int i = 1; i <= 4; i++)
    {
        if(cnt[i] == 0)  // 没有使⽤次数
        {
            continue;
        }
        
        if(!isSame(pos, i))  // 需要相同的位置,没有相同
        {
            continue;
        }
        
        cnt[i]--;
        path.push_back(i);
        dfs(pos + 1);
        
        path.pop_back();
        cnt[i]++;
    }
}

int main()
{
    for(int i = 1; i <= 4; i++)
    {
        cin >> cnt[i];
    }
    
    cin >> m;
    while(m--)
    {
        cin >> x >> y;
        same[x][y] = same[y][x] = true;
    }
    
    path.push_back(0); // 先放进去⼀个占位符
    dfs(1);
    
    cout << ret << endl;
    
    return 0;
}

5.3 接雨水问题(双指针)

  1. 题目链接接雨水
  2. 题目描述:

  1. 解法:
    • 算法思路:考虑每一根柱子上方雨水的高度。
  2. C++ 算法代码:
class Solution {
public:
    int trap(vector<int>& height) 
    {
        int n = height.size();
        vector<int> left(n);
        vector<int> right(n);

        left[0] = height[0];
        for(int i = 1; i < n; i++)
        {
            left[i] = max(left[i - 1], height[i]);
        }

        right[n - 1] = height[n - 1];
        for(int i = n - 2; i >= 0; i--)
        {
            right[i] = max(right[i + 1], height[i]);
        }

        int ret = 0;
        for(int i = 1; i < n - 1; i++)
        {
            ret += min(left[i], right[i]) - height[i];
        }

        return ret;
    }
};

6. Day48

6.1 疯狂的自我检索者(贪心)

  1. 题目链接疯狂的自我检索者
  2. 题目描述:

  1. 解法:
    • 算法思路:小贪心~
  2. C++ 算法代码:
#include <iostream>
using namespace std;

int main()
{
    int n, m, a;
    cin >> n >> m;
    
    int sum = 0;
    for(int i = 0; i < n - m; i++)
    {
        cin >> a;
        sum += a;
    }
    
    printf("%.5lf %.5lf\n", (sum + m) * 1.0 / n, (sum + m * 5) * 1.0 / n);

    return 0;
}

6.2 栈和排序(栈 + 贪心)

  1. 题目链接NC115 栈和排序
  2. 题目描述:

  1. 解法:
    • 算法思路:每次尽可能的先让当前需要的最大值弹出去。
  2. C++ 算法代码:
#include <stack>
class Solution {
public:

    vector<int> solve(vector<int>& a) 
    {
        int n = a.size();
        stack<int> st;

        bool hash[50010] = { 0 }; // 统计当前哪些元素已经进栈
        int aim = n;
        vector<int> ret;
        for(auto& x : a)
        {
            st.push(x);
            hash[x] = true;

            while(hash[aim])
            {
                aim--;
            }

            while(st.size() && st.top() >= aim)
            {
                ret.push_back(st.top());
                st.pop();
            }
        }

        return ret;
    }
};

6.3 加减(枚举 + 前缀和 + 滑动窗口 + 贪心)

  1. 题目链接加减
  2. 题目描述:

  1. 解法:
    • 算法思路:转化问题。将原数组排序之后,选择⼀段区间,让他们在不超过 k 次的前提下,全部变的相等。
  2. C++ 算法代码:
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10;
long long n, k;
long long arr[N];
long long sum[N] = {0}; // 前缀和数组

long long cal(int l, int r)
{
    int mid = (l + r) / 2;
    return (mid - l - r + mid) * arr[mid] - (sum[mid - 1] - sum[l - 1]) + (sum[r] - sum[mid]);
}

int main()
{
    cin >> n >> k;
    for(int i = 1; i <= n; i++)
    {
        cin >> arr[i];
    }
    
    sort(arr + 1, arr + n + 1);
    for(int i = 1; i <= n; i++)
    {
        sum[i] = sum[i - 1] + arr[i];
    }
    
    int left = 1;
    int right = 1;
    int ret = 1;
    while(right <= n)
    {
        long long cost = cal(left, right);
        while(cost > k)
        {
            left++;
            cost = cal(left, right);
        }
        
        ret = max(ret, right - left + 1);
        right++;
    }
    
    cout << ret << endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Smile丶凉轩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值