题一:工作时长
文件中的时间格式我截图了一点
然后我看题解很多人都用EXCE解题 空子有时候也是可以钻的! 学会走捷径
题二:与或异或
这是一道题填空题 我们只需要掌握暴力解法就行
这道题使用二维数组存储数据,使用了dfs深度优先搜索遍历
#include<iostream>
using namespace std;
//右边列表中的数字赋值给数组的前几个数,剩下的填充0
int arr[5][5] = {1,0,1,0,1}; //对于已知数组情况,直接定义全局变量
int ans;
int path[10];
bool solution(int* path){
int n=0;
for(int i=1,m=4;i<=4;i++,m--){
for(int j=0;j<m;j++){
int a = arr[i-1][j],b=arr[i-1][j+1];
if(path[n] == 0){
arr[i][j] = a&b;
}
if(path[n]==1){
arr[i][j] = a|b;
}
if(path[n]==2){
arr[i][j] = a^b;
}
n++;
}
}
if(arr[4][0]==1){
return true;
}else{
return false;
}
}
void dfs(int depth){
if(depth==10)//注意从0开始搜,这里判断条件应为depth==10
//这里path=9时是进行第4层的运算 因此等于10时才是判断最后的运算结果
{
if(solution(path)){
ans++;
}
return;
}
for(int i=0;i<3;i++){ // 相当于进行了3^10次方循环
path[depth] = i;
dfs(depth+1);
// 因为每次都是覆盖path数组的内容,故不用回溯也可
}
}
int main(){
dfs(0);
cout<<ans;
return 0;
}
path取10是因为图中有10次运算,每个位置有3中运算种类,一共有3^10种选法,其中0,1,2对应的与 或 异或,用path数组记录此时选用的运算符情况,再判断是否得到1这个解
最后就是要注意如何实现循环对二维数组进行一个三角形的遍历操作
题三:翻转
这道题还是蛮简单的,需要注意的就是首尾字母由于各缺少字符,因此无法比较
需要单独说明
#include<bits/stdc++.h>
using namespace std;
string s,t;
int main(){
int D;
cin>>D;
while(D--){
// cin>>t;
// cin>>s; //接受两个字符串
cin>>t>>s;
int count = 0;
for(int i=0;i<t.length();i++){
if(s[i]!=t[i]){
if(!i || i==t.length()-1|| s[i]==s[i-1]||s[i]==s[i+1]){
//对于首尾字母,由于左右两边缺少字符,所以一定无法比较,
count=-1;
break;
}
count++;
s[i] = t[i]; //这里需要修改掉方便后续的遍历
}
}
cout<<count<<endl;
}
return 0;
}
题四: 阶乘的和
这里需要找到合并的规律:
如果题目中没有Ai阶乘加和的条件,那么m就是数组A中的最小值(每个数都是阶乘)
而如果要对A[i]的阶乘加和,可能出现两个较小的数的阶乘合并为一个较大数的阶乘的现象
如3个2的阶乘可以合并成3的阶乘,4个3的阶乘可以合并为4的阶乘
故得到规律,若数字num出现了count次,且count可以被(num+1)整除,那么就可以合并成(num+1)的阶乘
如样例中的2,2,2,可以将其合并为一个3,转换为数字3出现了一次
用map容器存储<数字,出现的次数>,再逐个数字验证是否满足以上规律
若满足则合并,并更新其出现的次数,若有不满足的数字则直接输出即可
#include<bits/stdc++.h>
using namespace std;
map<int,int> mp;
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
int temp;
cin>>temp;
mp[temp]++;//用map记录每个数字出现的次数
}
for(map<int,int>::iterator it = mp.beigin();it!=mp.end();it++){
//map默认按照key递增的顺序遍历
int num = it->first;//取出数字
int count = it->second;//取出出现的次数
if(count%(num+1)==0){//满足阶乘合并的条件
mp[num+1]+=count/(num+1);
//例如3个2的阶乘合并成1个3的阶乘,3的出现次数+1
}else{
cout<<num<<endl;
return 0;
}
}
return 0;
}
题五:公因数匹配
本题利用了分解质因子和查找质因子的思想
对于输入的每个元素,都拆解其质因子,若该质因子是首次出现则存入map,记录其出现的位置
若该质因子不是首次出现,说明在该元素的前面已经有元素具有共同的质因子,找到一对下标(i,j)
由于题目要求i和j都尽可能的小,因此要不断比较出最小的(i,j)再输出例子: 5 3 2 6 9
loop1: 新建 mp[5] = 1 (x = 5), pos = 0, i 不变, j 不变
loop2: 新建 mp[3] = 2 (x = 3), pos = 0, i 不变, j 不变
loop3: 新建 mp[2] = 3
(x = 2), pos = 0, i 不变, j 不变
loop4: 找到 mp[2] = 3 (x = 6 / 2 = 3), pos= 3找到 mp[3] = 2 (x = 3 / 3 = 1), pos ==2, i = 2, j = 4
loop5: 找到 mp[3] = 2 (x = 9 / 3 = 3), pos = 2, i 不变, j 不变
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
map<int,int> mp; //存储<质因子,首次出现的下标>
int func(int x,int idx){
int pos = N;//pos存储x之前出现的最早质因子的下标,初始化为最大值
for(int i=2;i*i<=x;i++){
if(x%i) continue;
while(x%i==0) x/=i;
if(mp.find(i)!=mp.end() && mp[i]!=idx){
//该质因子已经存在且首次出现的位置不是当前元素的位置,则用pos记录
pos = min(pos,mp[i]);
}else{
mp.insert(make_pair(i,idx)); //该质因子是首次出现,记录位置并插入
}
}
if(x>1){ //最后可能还是剩下了一个更大的质因子
if(mp.find(x)!=mp.end() && mp[x]!=idx) pos = min(pos,mp[x]);
else mp.insert(make_pair(x,idx));//首次出现则记录位置并插入
}
return pos<N?pos:0;//找到了重复出现的质因子就返回最小的下标,否则返回0.
}
int main(){
int i = N,j;//i为最小前下标,j为最小后下标
int n,x;
cin>>n;
for(int id=1;id<=n;id++){
cin>>x;
int pos = func(x,id);//若存在
if(pos&&pos<i){ //返回值非0且找到了更小的下标pos
i=pos; //i更新为更小的pos
j=id;//j更新为当前下标k
}
}
cout<<i<<" "<<j;
return 0;
}
题六:奇怪的数
主要思路 动态规划 因为是连续的五位数之和不大于m 因此我们能想到使用四位推出第五位
再使用后四位得到新的四位推出第六位。如此只需要一个四维数组。
前提是需要将最开始的四位初始化好,也就是边界条件的确立
#include <iostream>
using namespace std;
#include<string>
int dp[10][10][10][10];
int N = 998244353;
int main()
{
int n,m;
cin>>n>>m;
int res = 0;
//初始化边界
for(int i=1;i<=9;i+=2){
for(int j=0;i<=9 && j<=(m-i);j+=2){
for(int k=1;k<=9 && k<=(m-i-j);k+=2){
for(int t=0;t<=9 && t<= (m-i-j-k);t+=2){
dp[i][j][k][t] = 1;
}
}
}
}
/*
dp[i][j][k][t] -> p j k t q -> dp[j][k][t][q](将之前的结果存在这里)
开始动态规划
q是对应当前的位置,比如n=5的时候,q就是第五位,就应该是奇数位可以用i%2直接得到起始条件,
再基于q得到上面的t k j p即可
*/
for(int i=5;i<=n;i++){ //从第五位开始
for(int p=i%2;p<=9;p+=2){
for(int j = (i+1)%2;j<=9&&(j<=m-p);j+=2){
for(int k=i%2;k<=9&&k<=(m-p-j);k+=2){
for(int t = (i+1)%2;t<=9&&(t<=m-p-j-k);t+=2){
for(int q=i%2;q<=9&&(q<=m-p-j-k-t);q+=2){
//dp[j][k][t][q]是没有被初始化的 我们最开始初始化的是奇偶奇偶 现在是偶奇偶奇
dp[j][k][t][q]+=dp[p][j][k][t];
dp[j][k][t][q] %= N;
}
//这里还需要归零
dp[p][j][k][t] = 0;
}
}
}
}
}
//直接把最后四位数累加起来即可
for(int j = (n+1)%2;j<=9&&(j<=m);j+=2){
for(int k=n%2;k<=9 && (k<=m-j);k+=2){
for(int t=(n+1)%2;t<=9&&t<=(m-j-k);t+=2){
for(int q=n%2;q<=9&&q<=(m-j-k-t);q+=2){
//dp[j][k][t][q]是没有被初始化的 我们最开始初始化是奇偶奇偶 现在是偶奇偶奇
res+=dp[j][k][t][q];
res%=N;
}
}
}
}
cout<<res<<endl;
return 0;
}
题七:太阳
使用了一种高级数据结构——极角排序
#include<bits/stdc++.h>
using namespace std;
//定义一个结构体,用于存储节点的坐标和索引信息
struct node{
int x,y,i;
//结构体构造函数,用于初始化结点的坐标和索引信息
node(int x,int y,int i):x(x),y(y),i(i){}
//重载小于运算符,用于排序结点
bool operator< (const node &t) const
{
//比较两个结点的斜率,斜率为y/x,避免使用浮点数,将斜率的乘积转换为long long类型
long long a = 1LL * t.y*x;
long long b = 1LL*t.x*y;
//按照斜率的大小进行排序
return a<b;
}
//重载等于运算符,用于判断两个节点的斜率是否相等
bool operator== (const node &t) conts{
//比较两个节点的斜率的乘积是否相等
return 1LL*t.y*x == 1LL * y *t.x;
}
};
int main(){
//输入节点数和坐标信息
int n,X,Y;
cin>>n>>X>>Y;
//存储结点信息的向量
vector<node> v;
// 标记节点是否被覆盖的向量
vector<bool> vis(n+1);
// 输入每个节点的坐标和长度信息
for(int i=1;i<=n;i++){
int x,y,l;
cin>>x>>y>>l;
//将节点和其对应的右边节点添加到向量中
v.emplace_back(x - X,Y-y,i);
v.emplace_back(x+l-X,Y-y,-i);
}
//将节点进行排序,排序规则为斜率从大到小
sort(v.begin(),v.end());
//定义一个二元组,存储当前被覆盖的结点信息
typedef pair<int,int> pii;
//定义一个集合,存储当前被覆盖的结点信息
set<pii> st;
//上一个被处理的节点
auto last = node(-1,1,0)
//遍历所有节点
for(auto &t:v){
//如果当前节点和上一个及诶单的斜率不相等,说明需要更新被覆盖的节点信息
if(!(t==last)){
//如果当前被覆盖的节点集合非空,将第一个节点标记为被覆盖
if(!st.empty()){
vis[st.begin()->second] = true;
}
//更新上一个节点信息为当前节点信息
last = t;
}
//如果当前节点是左边节点,将其添加到被覆盖节点集合中
if(t.i>0) st.emplace(t.y,t.i);
//如果当前节点是右边节点,将其从被覆盖的结点集合中移出
else st.erase(make_pair(t.y,-t.i));
}
//统计被覆盖的结点数量
int ans = 0;
for(bool t:vis) ans+=t;
//输出结果
cout<<ans;
return 0;
}
题八:子树的大小
找到m叉树的规律:
对于结点i,需要找到i的最左孩子和最右孩子的下标,便可求解
对于第i个结点,其前面有i-1个结点,每个结点各有m个孩子,再加上1号结点
则第i个结点的左孩子的下标应该为(i-1)m+2;
这里+2是加上1号结点是它前面结点的个数 ,再+1才是他的序号
同理可得第i个结点的右孩子下标为im+1(前i个结点(包括了自己)各有m个孩子,再>加上1号结点)
故对于子树根节点,只需要逐层累加最右孩子-最左孩子即可
需要处理最后一层的特殊情况:
1.最左孩子下标超出了n,说明子树在最后一层没有结点,直接退出
2.最右孩子下标超出n,说明子树在最后一层的结点是未满,将最右孩子下标改为n,累加后退出
有1e5组的询问,对于每组询问,可在近似logmn的时间复杂度内得出结果,不会>超时。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;//注意本题必须开long long,否则会挂掉绝大部分测试点
void solve(){
int n,m,k;
ll cnt = 1;//cnt记录以k为根的子树的结点总数,初始化为1(一个根节点)
//n为结点总数,m为树的叉数,k为待查询节点编号
cin>>n>>m>>k;
ll lchild=k,rchild=k; //开始计算最左孩子和最右孩子的编号
while(1){
bool flag = 0;
lchild = (lchild-1)*m+2;
rchild = rchild*m+1;
if(lchild>n) break;//最左孩子超出n,本层为空,直接退出
if(rchild>=n){ //最右孩子超出n
flag=1;//最后一次累加
rchild = n;
}
cnt+=rchild-lchild+1;//累加最左孩子与最右孩子之差
if(flag) break;
}
cout<<cnt<<endl;
}
int main(){
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
题九:高塔
这道题我看题解在验证级数公式 虽然级数很多公式也只是背了用 但是我考的数二 不想接触级数 后面学习大模型需要的时候再学吧
下面我直接给出代码:
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
#define endl '\n'
const int mod = 998244353;
const int N = 5e5 + 10;
ll inv[N];
unordered_map<ll,ll> pre2;
void init() // initialize the inv prefix
{
inv[1] = 1;
for(int i = 2;i < N;i++) inv[i] = (mod - mod / i) * inv[mod % i] %mod;
for(int i = 2;i < N;i++) inv[i] = inv[i - 1] * inv[i] % mod;
}
ll qpow(ll a,ll b) // fast exponetiation
{
ll res = 1;
while(b)
{
if(b&1) res = res*a%mod;
a = a*a%mod;
b >>= 1;
}
return res;
}
ll INV(ll x) { return qpow(x,mod - 2);}
ll C(ll n,ll m) // combination
{
if(n < m) return 0;
else if(n == m) return 1;
ll res = 1;
res = pre2[n]*INV(pre2[n - m])%mod*inv[m]%mod;
return res%mod;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
init();
ll n,m; cin>>n>>m;
vector<ll> a(n),pre(n + 1);
pre[0] = 1;
for(int i = 0;i < n;i++)
{
cin>>a[i];
pre[i + 1] = (pre[i] * a[i]) % mod;
}
pre2[m - n] = (m - n)%mod;
for(ll x = m - n + 1;x <= m + n;x++)
pre2[x] = pre2[x - 1]*(x%mod)%mod;
ll res = 0;
for(int i = 0;i < n - 1;i++)
{
ll cur = C(m + i,2*i + 1)*pre[i + 1]%mod; // use m points at ith round,i < n
res = (res + cur)%mod;
}
res = (res + C(m + n,2*n)*pre[n]%mod)%mod; // ith round
cout<<(res + mod)%mod;
return 0;
}
题十:反异或01串
本题主要使用manacher算法,该算法由中心扩展演变而来。,因此浅谈一下这个算法
//中心扩展算法:找到中心点,如果进行扩展,如果是偶数就将其添加# 如abba扩展为#a#b#b#a# 下面给出中心扩展的代码:
class Solution{
public:
string longestPalindrome(string s){
if(s.size()<=1){
return s;
}
//通过插入"#",扩展s为exp_s,
string exp_s(s.size()*2+1,'#');
int index = 0;
for(int i=1;i<exp_s.size();i+=2){
exp_s[i] = s[index++];
}
//遍历exp_s
int max_center = -1;
int max_radius = -1;
for(int i=0;i<exp_s.size();i++){
int radius = 1;
while(i+radius<exp_s.size() && i-radius > -1){
if(exp_s[i+radius] == exp_s[i-radius]){
radius++;
}else{
break;
}
}
if(radius>max_radius){
max_center = i;
max_radius = radius;
}
}
string exp_res = exp_s.substr(max_center-max_radius+1,2*max_radius-1);
string res;
for(auto c:exp_res){
if(c!='#'){
res.push_back(c);
}
}
return res;
}
};
有关manacher算法大家可以去看看B站的这个视频,讲的很好:
链接: B站视频链接
下面给出这道题代码:
/*
这里有一个很大的问题就是如果回文串长度是奇数并且最中间的数字是1,
比如010111010,
它是不能由010110000操作得到的,因为中间的1会异或成0,
这个时只能找长度为偶数的回文串或者中间为0长度为奇数的回文串,
所以010111010应该拆解为0+101+11010,答案为1+3=4
*/
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e6+10;
char s[N];
int p[N], m[N], pre[N];
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> s + 1;
int n = strlen(s + 1);
for (int i = 2 * n + 1; i >= 1; --i)
s[i] = (i & 1) ? '#' : s[i >> 1];
s[0] = '^', s[2 * n + 2] = '$';
for (int i = 1; i <= 2 * n + 1; i++)
{
pre[i] = pre[i - 1] + (s[i] == '1');
}
int c = 0, r = 0, maxm = -1;
for (int i = 1; i <= 2 * n + 1; ++i)
{
p[i] = (i < r) ? min(r - i, p[2 * c - i]) : 1;
while (s[i + p[i]] == s[i - p[i]])
++p[i];
if (i + p[i] > r)
c = i, r = i + p[i];
if (((p[i] - 1 & 1) == 0) || ((p[i] - 1 & 1) == 1 && s[i] == '0'))
{
int cnt = pre[i] - pre[i - p[i]];
maxm = max(maxm, cnt);
}
}
cout << pre[2 * n + 1] - maxm;
}