笔试强训——第七周
目录
Day1
kotori和抽卡(二)
ruby和薯条
循环汉诺塔
Day2
最小差值
kotori和素因子
dd爱科学1.0
Day3
kanan和高音
拜访
买卖股票的最好时机(四)
Day4
AOE还是单体?
kotori和n皇后
取金币
Day5
矩阵转置
四个选项
接雨水问题
Day6
疯狂的自我检索者
栈和排序
加减
Day1
kotori和抽卡(二)
解法:数学
#include <iostream>
#include <cmath>
using namespace std;double n, m, a = 1, b = 1, c = 1, d = 1;int main()
{cin >> n >> m;for (int i = n; i >= n - m + 1; i--)a *= i;for (int i = m; i >= 1; i--)b *= i;// for(int i=0;i<m;i++) c*=0.8;// for(int i=0;i<n-m;i++) d*=0.2;printf("%0.4f", a / b * pow(0.8, m) * pow(0.2, n - m));// printf("%0.4f",n*0.8/m);return 0;
}
ruby和薯条
解法1:暴力
两层for循环一个一个比较枚举统计出队数(超时)
解法2:排序 + 二分
先排升序,再以数组的每个数(假设为a[i])为基准值统计出[a[i] + l ,a[i] +r]区间的个数,此时采取两次二分分别求出左端点与右端点;
解法3:排序 + 滑动窗口
与小红的子串的思路一样:先排序,再用滑动窗口统计出 [0,r]和 [0,l-1]区间的队数后相减就是 [l,r]中的队数(注意统计合法的队数时是:right - left 而不是 right -left + 1)
//二分
#include <iostream>
#include <algorithm>
using namespace std;typedef long long LL;
const int N = 2e5 + 10;
LL n, l, r, a[N], ret;int main()
{cin >> n >> l >> r;for (int i = 0; i < n; i++)cin >> a[i];sort(a, a + n);for (int i = 0; i < n; i++){if (a[i] + l > a[n - 1])break;//[a[i]+l,a[i]+r] 二分区间:找左右端点int left = 0, right = n - 1, begin = 0, end = 0;while (left < right){int mid = (left + right) / 2;if (a[mid] < a[i] + l)left = mid + 1;elseright = mid;}begin = left;left = 0, right = n - 1;while (left < right){int mid = (left + right + 1) / 2;if (a[mid] > a[i] + r)right = mid - 1;elseleft = mid;}end = left;ret += end - begin + 1;}cout << ret << endl;return 0;
}//滑动窗口
#include <iostream>
#include <algorithm>
using namespace std;typedef long long LL;
const int N = 2e5 + 10;
LL n, l, r, a[N];LL fun(int tmp)
{LL ret = 0;for (int left = 0, right = 0; right < n; right++){while (a[right] - a[left] > tmp)left++;ret += right - left;}return ret;
}int main()
{cin >> n >> l >> r;for (int i = 0; i < n; i++)cin >> a[i];sort(a, a + n);// 区间 [0,r] 和 [0,l-1]cout << fun(r) - fun(l - 1) << endl;return 0;
}
循环汉诺塔
解法:找规律
当n=1时,A->B的操作是1,A->C的操作是2;
当n=2时,A->B的操作是1+1+1=3,A->C的操作是2+1+1+1+2=7;
当n=3时:
A->B的操作是:先把n-1个小金片从A->C上,这个操作在n=2时已经计算过了,操作数为:7,再把A的大金片移到B上,操作数为1,再把C上全部金片移到B上,这个操作我们计算过了吗?当然!C->B上与A->C上本质都是要移动两格,所以操作数为7,计算结果为:7 + 1 + 7 =15;把n=2时 A->C的操作次数设为y,公式为:2y + 1
A->C的操作也同理,结果为:7+1+5+1+7=21,把n=2时 A->B的操作次数设为x,公式为:2y + x + 1;
#include <iostream>
using namespace std;typedef long long LL;
const int N = 1e9 + 7;
LL n, x = 1, y = 2, a, b;int main()
{cin >> n;n -= 1;while (n--){a = (2 * y + 1) % N;b = (2 * y + x + 2) % N;x = a;y = b;}cout << x << ' ' << y << endl;return 0;
}
Day2
最小差值
解法:排序
排序后依次找最小值
class Solution
{
public:int minDifference(vector<int> &a){sort(a.begin(), a.end());long long ret = 0x3f3f3f3f3f3f3f3f;for (int i = 0; i < a.size() - 1; i++){ret = min(ret, (long long)abs(a[i] - a[i + 1]));}return ret;}
};
kotori和素因子
解法:dfs
先把每个数的素因子统计到数组中;
在对每一个数的素因子的情况进行递归枚举,找到最小值
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;int n, a[10], path, ret = 0x3f3f3f3f;
bool vis[1001];void dfs(int pos, vector<vector<int>> &sv)
{if (pos == n){ret = min(ret, path);return;}for (auto &e : sv[pos]){if (!vis[e]){path += e;vis[e] = true;dfs(pos + 1, sv);vis[e] = false;path -= e;}}
}bool fun(int a)
{for (int i = 2; i <= sqrt(a); i++){if (a % i == 0)return false;}return true;
}int main()
{cin >> n;vector<vector<int>> sv(n);for (int i = 0; i < n; i++){cin >> a[i];for (int j = 2; j <= a[i]; j++) // 如果本身是素数就选它,所以加上<+{if (a[i] % j == 0 && fun(j))sv[i].push_back(j);}}dfs(0, sv);if (ret == 0x3f3f3f3f)cout << -1 << endl;elsecout << ret << endl;return 0;
}
dd爱科学1.0
解法:贪心 + 二分
题目转化为求最长递增子序列后将总大小减去它即可
#include <iostream>
#include <string>
#include <vector>
using namespace std;int n;
string s;
vector<char> v;int main()
{cin >> n >> s;for (int i = 0; i < n; i++){if (v.empty() || v.back() <= s[i])v.push_back(s[i]);else{int left = 0, right = v.size() - 1;while (left < right){int mid = (left + right) / 2;if (s[i] >= v[mid])left = mid + 1;elseright = mid;}v[left] = s[i];}}cout << n - v.size() << endl;return 0;
}
Day3
kanan和高音
解法:双指针
从前往后遍历,⽤双指针找出⼀段能唱完的区域,然后更新指针继续找下⼀段
#include <iostream>
using namespace std;int n, a[200010], ret;int main()
{cin >> n;for (int i = 0; i < n; i++)cin >> a[i];for (int i = 0; i < n;){int j = i;while (j + 1 < n && a[j + 1] - a[j] <= 8)j++;ret = max(ret, j - i + 1);i = j + 1; // 快}cout << ret << endl;return 0;
}
拜访
解法:bfs
1.开辟俩个二维数组dist,cnt用来储存距离信息与方案信息,dist数组全部初始化为-1,cnt数组全部初始化为0;
2.从开始位置【i,j】出发,即 dist [i][j] = 0表示不用移动距离,cnt [i][j] = 1表示到达开始位置有一种方案数(什么都不用做),把【i,j】加入到队列中开始层序遍历;
3.每次遍历把队列中坐标全部取出来进行上下左右四个方向遍历,第一次到达该位置就统计dist和cnt位置的值;不是第一次就看看此时dist位置的值是否等于上一次dist位置的值加1,是的话就说明又找到了到达该位置的方案,此时cnt位置的值累加上一次cnt位置的值(有点抽象具体看代码好理解)
#include <cstring>
class Solution {
public:// vector<int> ret;// int cnt=0,tmp=0x3f,path=0,dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};// bool vis[10][10]={false};// void dfs(vector<vector<int>>& C,int a,int b,int n,int m)//超时// {// if(C[a][b]==2)// {// ret.push_back(path);// tmp=min(tmp,path);// return;// }// for(int k=0;k<4;k++)// {// int x=dx[k]+a,y=dy[k]+b;// if(x>=0&&x<n&&y>=0&&y<m&&!vis[x][y]&&C[x][y]!=-1)// {// vis[x][y]=true;// path++;// dfs(C,x,y,n,m);// path--;// vis[x][y]=false;// }// }// }queue<pair<int,int>> q;int dist[10][10],cnt[10][10]={0};int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};void bfs(vector<vector<int>>& C,int n,int m){while(q.size()){int sz=q.size();while(sz--){auto [a,b]=q.front();q.pop();if(C[a][b]==2){continue;}for(int k=0;k<4;k++){int x=dx[k]+a,y=dy[k]+b;//if(dist[x][y]==dist[a][b]+1) cnt[x][y]+=cnt[a][b];//统计最短方案数//if(x>=0&&x<n&&y>=0&&y<m&&dist[x][y]==-1&&C[x][y]!=-1)if(x>=0&&x<n&&y>=0&&y<m&&C[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[x][y]==dist[a][b]+1){cnt[x][y]+=cnt[a][b];}}}}}}int countPath(vector<vector<int> >& CityMap, int n, int m) {memset(dist,-1,sizeof(dist));for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(CityMap[i][j]==1){//vis[i][j]=true;//dfs(CityMap,i,j,n,m);//开始位置进行初始化dist[i][j]=0;cnt[i][j]=1;q.push({i,j});bfs(CityMap,n,m);break;}}}for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(CityMap[i][j]==2) return cnt[i][j];}}return -1;// for(auto& e:ret)// {// if(e==tmp) cnt++;// }// return cnt;}
};
买卖股票的最好时机(四)
解法:动态规划
股票问题的多状态问题
#include <cstring>
using namespace std;int n, k, a[1010], f[1010][110], g[1010][110];int main()
{cin >> n >> k;for (int i = 0; i < n; i++)cin >> a[i];memset(f, -0x3f3f3f3f, sizeof(f)); // 有股票状态memset(g, -0x3f3f3f3f, sizeof(g)); // 无股票状态f[0][0] = -a[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] - a[i - 1]);g[i][j] = g[i - 1][j];if (j - 1 >= 0)g[i][j] = max(g[i][j], f[i - 1][j - 1] + a[i - 1]);}}int ret = -0x3f3f3f3f;for (int j = 0; j <= k; j++)ret = max(ret, g[n][j]);cout << ret << endl;return 0;
}
Day4
解法:贪心
当 x 大于等于 n时,使用AOE让怪物掉血快;
当 x 小于 n时,使用单体让怪物掉血快;
以上策略很好想,但如何写代码呢?
数据量很大,一次一次模拟必超时;
优化方案:
使用AOE本质是要消灭n-x个怪物后使用单体,所以:
先排序,使用AOE消灭次数为:a[ n - x -1]* x(数组从0开始要减1);
再让AOE攻击后剩下的n - x怪物使用单体,也就是累加操作
AOE还是单体?
#include <iostream>
#include <algorithm>
using namespace std;typedef long long LL;
const int N = 2e5 + 10;
LL n, x, a[N], ret;int main()
{cin >> n >> x;for (int i = 0; i < n; i++)cin >> a[i];if (x >= n){for (auto &e : a)ret += e;cout << ret << endl;return 0;}sort(a, a + n);ret += a[n - x - 1] * x;for (int i = n - x; i < n; i++)ret += a[i] - a[n - x - 1];cout << ret << endl;return 0;
}
kotori和n皇后
解法:哈希表
用四个哈希表储存坐标行,列,主对角线,副对角线的数据;
如果遇到坐标在四个哈希表内就说明此时的皇后会与前面的皇后相互攻击,用变量tmp将它标记起来即可,接下来的坐标就不用管了:
因为当询问的第i个皇后的i大于tmp时答案都是Yes!不管第i个皇后有没有与前面的i-1个皇后相互攻击
#include <iostream>
#include <unordered_set>
using namespace std;typedef long long LL;
LL k, x1, y1, t, i, tmp;
unordered_set<LL> row, col, dig1, dig2;int main()
{cin >> k;for (int j = 1; j <= k; j++){cin >> x1 >> y1;if (tmp)continue;if (row.find(x1) != row.end() || col.find(y1) != col.end() ||dig1.find(y1 - x1) != dig1.end() || dig2.find(y1 + x1) != dig2.end()){tmp = j;}else{row.insert(x1);col.insert(y1);dig1.insert(y1 - x1);dig2.insert(y1 + x1);}}cin >> t;while (t--){cin >> i;if (tmp == 0 || i < tmp)cout << "No" << endl; // 还有tmp为0找不到相互攻击的情况elsecout << "Yes" << endl;}
}
取金币
解法:动态规划
区间dp问题;
状态表示
dp[i][j]表示从[i,j]区间内的金币全部取走此时的最大积分
状态转移方程
假设在【i,j】区间内定义一个k,我们先取k位置上的金币,所以【i,j】区间被分为三部分:【i,k-1】k 【k+1,j】将三部分使用状态表示,则:
dp[i][j] = dp[i][k-1] + dp[k+1][j] + a[i-1] * a[k] * a[j+1];
初始化
a.取a[i-1] 和 a[j+1]的值可能越界,所以要在原数组中开头和结尾添加1(使得不影响填表),填表就从【1,n】内填表(n为原数组的大小);同样dp表也要多两行两列且为了不影响填表,这里只初始化为0
填表顺序
从下往上,从左往右
返回值
dp[1][n]
class Solution
{
public:int getCoins(vector<int> &coins){int n = coins.size();// 初始化coins.insert(coins.begin(), 1);coins.push_back(1);vector<vector<int>> dp(n + 2, vector<int>(n + 2));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] + coins[i - 1] * coins[k] * coins[j + 1]);}}}return dp[1][n];}
};
Day5
矩阵转置
解法:模拟
按照题意进行模拟
#include <iostream>
using namespace std;long long n, m, a[10][10];int main()
{cin >> n >> m;for (int i = 0; i < n; i++){for (int j = 0; j < m; j++)cin >> a[i][j];}for (int j = 0; j < m; j++){for (int i = 0; i < n; i++)cout << a[i][j] << ' ';cout << endl;}return 0;
}
四个选项
解法:dfs
暴力枚举12个选项,分别填入四个选项看看是否满足要求,知道把12选项都填完就说明找到了一种方案...统计所有合法的方案数返回
怎么知道选择的选项合法?
定义全局数组a把na,nb,nc,nd的值填进来,当填到某个位置时去数组a看看是否还能选择(前面选择后--,为0就说明不能选了)
怎么知道题目给出的位置填的选项都相同?
定义二维数组vis进行标记,把信息统计进vis中:vis[x][y] = vis[y][x] = true
再用vector统计当前选项选的是什么,当递归到某个位置时就结合vis进行判断
#include <iostream>
#include <vector>
using namespace std;int a[5], m, x, y, ret;
bool vis[13][13];
vector<int> path;bool IsSame(int pos, int i)
{// for(int j=1;j<=12;j++)//path越界for (int j = 1; j < pos; j++) // 去pos前面看看是否需要选项与前面的选项相同{if (vis[pos][j] == true && i != path[j])return false;}return true;
}void dfs(int pos)
{if (pos > 12){ret++;return;}for (int i = 1; i <= 4; i++){if (a[i] == 0)continue; // 该选项选完了if (!IsSame(pos, i))continue; // 相同题目是否选的是相同的path.push_back(i);a[i]--;dfs(pos + 1);a[i]++;path.pop_back();}
}int main()
{for (int i = 1; i <= 4; i++)cin >> a[i];cin >> m;while (m--){cin >> x >> y;vis[x][y] = vis[y][x] = true;}path.push_back(0); // 坐标0位置空出dfs(1);cout << ret << endl;return 0;
}
接雨水问题
解法:动态规划 + 双指针
问题:以某个柱子为例,如果求出当前柱子最多能够接多少雨水?
找左右最大值就简单使用动态规划进行预处理
class Solution
{
public:long long n, ret = 0;long long left[200010] = {0}, right[200010] = {0};long long maxWater(vector<int> &arr){n = arr.size();left[0] = arr[0];for (int i = 1; i < n; i++)left[i] = max(left[i - 1], (long long)arr[i]);right[n - 1] = arr[n - 1];for (int i = n - 2; i >= 0; i--)right[i] = max(right[i + 1], (long long)arr[i]);for (int i = 0; i < n; i++)ret += min(left[i], right[i]) - arr[i];return ret;}
};
Day6
疯狂的自我检索者
解法:模拟
简单模拟题
#include <iostream>
using namespace std;int n, m, tmp;
double ret1, ret2;int main()
{cin >> n >> m;for (int i = 0; i < n - m; i++){cin >> tmp;ret1 += tmp;ret2 += tmp;}for (int i = 0; i < m; i++){ret1 += 1;ret2 += 5;}printf("%f %f", ret1 / n, ret2 / n);return 0;
}
栈和排序
解法:栈和贪心
贪心策略:让栈里面的最大值尽可能地出栈;
做法:定义最大值aim,遍历数组a时,遇到最大值进栈后就执行出栈操作,更新aim为次大值,如果次大值也在栈里面的话,就要把次大值及其以上的数从栈里面弹出,再次更新aim...直到aim值不再栈里面就往下遍历数组a...
问题:怎么知道最大值aim此时在栈里面?
使用数组vis进行标记,把进栈的值标记为true;
当找到最大值aim时先更新aim的值,在进行出栈操作
class Solution
{
public:vector<int> solve(vector<int> &a){vector<int> ret;stack<int> s;bool vis[50010] = {false};int n = a.size(), aim = n;for (auto &e : a){s.push(e);vis[e] = true;// 最大值进栈后,先更新下次要找的最大值while (vis[aim])aim--;// 把比下次要找的最大值大的元素出栈while (s.size() && s.top() >= aim){ret.push_back(s.top());s.pop();}}return ret;}
};
加减
解法:贪心 + 前缀和 + 滑动窗口(二分)
问题1:怎样才能快速找到两个值的操作次数最小?
先排序,把接近相等的值依靠在一起;
然后就进行枚举所有区间,找出最小的操作次数的个数(但这里n^2超时),需要进行优化;
问题2:怎么计算一个区间内的最小操作次数?
该问题转化为数学问题:在水平线上找一个位置,使得所有的点到该位置距离最小?
结论:找中间值使得所有的点到该位置距离最小(证明略)
根据该结论来推导一个区间的最小操作次数的公式:
计算一个区间内的个数使用前缀和数组可以快速地计算出来;
有了公式能够直接计算出某个区间的最小代价cost后,可以利用排序数组单调性的特点,使用滑动窗口O(N)快速找到某个区间是否合法,统计并找出最大区间
同样可以枚举‘左端点’,通过二分算法找到’右端点’(这里的)统计并找出最大区间
#include <iostream>
#include <algorithm>
using namespace std;long long n, k, a[100010], sum[100010], SumK;
int ret = 1;long long GetSumK(int left, int right)
{int mid = (left + right) / 2;// return (mid-left)*a[mid]-(sum[mid-1]-sum[left-1])+// (sum[right]-sum[mid])-(right-mid)*a[mid];return (mid - left - right + mid) * a[mid] - (sum[mid - 1] - sum[left - 1]) +(sum[right] - sum[mid]);
}int main()
{cin >> n >> k;for (int i = 1; i <= n; i++)cin >> a[i];sort(a, a + n + 1);for (int i = 1; i <= n; i++)sum[i] = a[i] + sum[i - 1];// 二分for (int i = 1; i <= n; i++){int left = i, right = n;// 枚举i的‘右端点’:其实不是真的右端点,而是left和right的中间值aim// 看看【i,aim】区间是否合法while (left < right){int aim = (left + right + 1) / 2;if (GetSumK(i, aim) <= k){left = aim;}elseright = aim - 1;}ret = max(ret, left - i + 1);}// 滑动窗口// for(int left=1,right=1;right<=n;right++)// {// SumK=GetSumK(left, right);// while(SumK > k)// {// left++;// SumK=GetSumK(left, right);// }// ret=max(ret,right-left+1);// }cout << ret << endl;return 0;
}
以上便是全部内容,有问题欢迎在评论区指正,感谢观看!