#include<iostream>usingnamespace std;intmain(){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);return0;}
1.2 ruby和薯条(排序 + 二分 / 双指针)
题目链接: ruby和薯条
题目描述:
解法:
算法思路:
解法⼀:排序 + 二分。 先排序,然后枚举较大值,在 [1, i - 1] 区间找差值的左右端点即可。
#include<iostream>usingnamespace std;constint MOD =1e9+7;intmain(){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;return0;}
2. Day44
2.1 差值(排序)
题目链接: 最小差值
题目描述:
解法:
算法思路:排序,然后计算相邻两个数之差的最小值即可。
C++ 算法代码:
classSolution{public:intminDifference(vector<int>& a){sort(a.begin(), a.end());longlong ret =1e16+10;;for(int i =1; i < a.size(); i++){ret =min(ret,(longlong)a[i]- a[i -1]);}return ret;}};
2.2 kotori和素因子(DFS)
题目链接: kotori和素因子
题目描述:
解法:
算法思路:递归型枚举所有的情况。
C++ 算法代码:
#include<iostream>#include<cmath>usingnamespace std;constint N =15, M =1010;int n =0;int arr[N]={0};bool use[M];// 记录路径中⽤了哪些值int path;// 记录当前路径中所有元素的和int ret =0x3f3f3f3f;// 统计最终结果boolisPrim(int x){if(x <=1){returnfalse;}for(int i =2; i <=sqrt(x); i++){if(x % i ==0){returnfalse;}}returntrue;}voiddfs(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;}}}intmain(){cin >> n;for(int i =0; i < n; i++){cin >> arr[i];}dfs(0);if(ret ==0x3f3f3f3f){cout <<-1<< endl;}else{cout << ret << endl;}return0;}
2.3 dd爱科学1.0(最长上升子序列 - 贪心 + 二分)
题目链接: dd爱科学1.0
题目描述:
解法:
算法思路:
要想改动最小,就应该在最长非下降子序列的基础上,对不是最长的部分进行更换。
因为这道题的数据范围比较大,所以应该用贪心 + 二分求出最长非下降子序列的长度。
C++ 算法代码:
#include<iostream>usingnamespace std;constint N =1e6+10;intmain(){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;return0;}
3. Day45
3.1 kanan和高音(模拟 + 双指针)
题目链接: kanan和高音
题目描述:
解法:
算法思路:从前往后遍历,用双指针找出⼀段能唱完的区域,然后更新指针继续找下⼀段。
C++ 算法代码:
#include<iostream>usingnamespace std;constint N =2e5+10;int n;int arr[N];intmain(){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;return0;}
3.2 拜访(BFS)
题目链接: MT3 拜访
题目描述:
解法:
算法思路:在层序遍历的过程中,维护额外的信息。
C++ 算法代码:
classSolution{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};intbfs(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];}intcountPath(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;}elseif(CityMap[i][j]==2){x2 = i;y2 = j;}}}returnbfs(CityMap, n, m);}};
3.3 买卖股票的最好时机(四)(动态规划)
题目链接: DP33 买卖股票的最好时机(四)
题目描述:
解法以及算法思路:
状态表示:为了更加清晰的区分「买入」和「卖出」,我们换成「有股票」和「无股票」两个状态。
f[i][j] 表示:第 i 天结束后,完成了 j 笔交易,此时处于「有股票」状态的最大收益;
g[i][j] 表示:第 i 天结束后,完成了 j 笔交易,此时处于「无股票」状态的最大收益。
状态转移方程:
对于 f [i][j] ,我们也有两种情况能在第 i 天结束之后,完成 j 笔交易,此时手里「有股票」的状态:
在 i - 1 天的时候,手里「有股票」,并且交易了 j 次。在第 i 天的时候,啥也不干。此时的收益为 f[i - 1][j] ;
在 i - 1 天的时候,手里「没有股票」,并且交易了 j 次。在第 i 天的时候,买了股票。那么 i 天结束之后,我们就有股票了。此时的收益为 g[i - 1][j] - prices[i] ;
我们的交易次数是不会超过整个天数的⼀半的,因此我们可以先把 k 处理⼀下,优化⼀下问题的规模:k = min(k, n / 2)。
如果画一个图的话,它们之间交易关系如下:
C++ 算法代码:
#include<iostream>usingnamespace std;constint N =1010, M =110;int n, k, p[N];int f[N][M], g[N][M];intmain(){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;return0;}
4. Day46
4.1 AOE还是单体?(贪心)
题目链接: AOE还是单体?
题目描述:
解法:
算法思路。小贪心:
如果使用一次 AOE 造成的伤害比消耗的蓝量多,那就使用;
否则就一直使用单体伤害。
C++ 算法代码:
#include<iostream>#include<algorithm>usingnamespace std;constint N =2e5+10;intmain(){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);longlong 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;return0;}
4.2 kotori和n皇后(哈希表)
题目链接: kotori和n皇后
题目描述:
解法:
算法思路:使用哈希表标记行列以及两个对角线。
C++ 算法代码:
#include<iostream>#include<unordered_set>usingnamespace std;int k, t;int ret =1e5+10;// 第⼀次出现互相攻击的皇后的个数
unordered_set<longlong> row;// 标记⾏ y
unordered_set<longlong> col;// 标记列 x
unordered_set<longlong> dig1;// 标记主对⻆线 y - x
unordered_set<longlong> dig2;// 标记副对⻆线 y + xintmain(){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;}}return0;}
4.3 取金币(动态规划 - 区间dp)
题目链接: NC393 取金币
题目描述:
解法:
算法思路。区间 dp:
为了方便能处理边界情况,将原数组前后添加⼀个 1,并不影响最后的结果。
状态表示:
dp[i][j] 表示: [i, j] 区间⼀共能获得多少金币。
C++ 算法代码:
classSolution{public:int arr[110]={0};int dp[110][110]={0};intgetCoins(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 矩阵转置(数学)
题目链接: BC138 矩阵转置
题目描述:
解法:
算法思路:观察转置前和转置后下标的关系即可。
C++ 算法代码:
#include<iostream>usingnamespace std;constint N =15;int n, m;int arr[N][N];intmain(){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;}return0;}
5.2 四个选项(DFS + 剪枝 + 哈希表)
题目链接: 四个选项
题目描述:
解法:
算法思路:用递归枚举出所有的情况,注意剪枝。
C++ 算法代码:
#include<iostream>#include<vector>usingnamespace std;int cnt[5];// ⽤数组存每⼀个选项出现多少次int m, x, y;bool same[13][13];// 存哪些题的答案是相同的int ret;
vector<int> path;// 记录路径⾥⾯选了哪些选项boolisSame(int pos,int cur){for(int i =1; i < pos; i++){if(same[pos][i]&& path[i]!= cur){returnfalse;}}returntrue;}voiddfs(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]++;}}intmain(){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;return0;}
5.3 接雨水问题(双指针)
题目链接: 接雨水
题目描述:
解法:
算法思路:考虑每一根柱子上方雨水的高度。
C++ 算法代码:
classSolution{public:inttrap(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 疯狂的自我检索者(贪心)
题目链接: 疯狂的自我检索者
题目描述:
解法:
算法思路:小贪心~
C++ 算法代码:
#include<iostream>usingnamespace std;intmain(){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);return0;}
6.2 栈和排序(栈 + 贪心)
题目链接: NC115 栈和排序
题目描述:
解法:
算法思路:每次尽可能的先让当前需要的最大值弹出去。
C++ 算法代码:
#include<stack>classSolution{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 加减(枚举 + 前缀和 + 滑动窗口 + 贪心)
题目链接: 加减
题目描述:
解法:
算法思路:转化问题。将原数组排序之后,选择⼀段区间,让他们在不超过 k 次的前提下,全部变的相等。
C++ 算法代码:
#include<iostream>#include<algorithm>usingnamespace std;constint N =1e5+10;longlong n, k;longlong arr[N];longlong sum[N]={0};// 前缀和数组longlongcal(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]);}intmain(){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){longlong cost =cal(left, right);while(cost > k){left++;cost =cal(left, right);}ret =max(ret, right - left +1);right++;}cout << ret << endl;return0;}