一:dfs
1.题目:老子全排列呢
解题思路:
枚举每一位填什么
代码块:
void dfs(int u)
{
//枚举到第8位,到达终点
if(u == 8)
{
for(int i = 0; i < 8; i++)
{
if(i != 8)
{
cout << num[i] << " ";
}else cout << num[i];
}
cout << endl;
return;
}
//对于当前第 u 位,枚举一下选哪个
for(int i = 1; i <= 8; i++)
{
//选过的不能再选
if(!st[i])
{
num[u] = i;
st[i] = true;
dfs(u + 1);
st[i] = false; //回复现场,保证其他搜索路径可以用 i
}
}
}
2.题目:N 皇后
解题思路:
题目规定同一行,列,对角线,不能同时存在。
为了方便,我们一行一行来放置,每一行必定存在一个皇后,对于当前行的位置,我们枚举哪一列可以放置,放置完之后
枚举下一行。
代码块:
void dfs(int u){
if(u==n+1)
{
ans++;
}
for(int i=1;i<=n;i++){
if(!lie[i]&&!sd[u+i]&&!usd[n-u+i]){
lie[i]=sd[u+i]=usd[i-u+n]=true;
dfs(u+1);
lie[i]=sd[u+i]=usd[i-u+n]=false;
}
}
}
3.题目:幸运数字 2
解题思路:
首先对于幸运数字,我们可以先通过 dfs 构造出来,然后再二分快速找到 l 和 r 数字的范围
dfs:开头的数字可以是 4 / 7, 所以 dfs两遍。
之后存储所有构造的数字,二分找到。
lowerbound(起始地址,终止地址,对于要查找的数字 (x)) - 起始地址得到下标。
因为最终函数返回的是地址所以需要减去起始地址来得到下标。 lower 寻找的是 >= x 的第一个数字
upperbound 同理,找的 > x 的第一个数字。
二分函数要求数组必须是有序的。
代码块:
void dfs(long long x)
{
if(x>1e10)return;
num[cnt++]=x;
dfs(x*10+7);
dfs(x*10+4);
}
int main()
{
long long l,r;
cin>>l>>r;
dfs(4),dfs(7);
sort(num,num+cnt+1);
long long x=lower_bound(num,num+cnt+1,l)-num;
long long y=upper_bound(num,num+cnt+1,r)-num;
long long ans=0;
for(int i=x;i<=y;i++)
{
ans+=(min(num[i],r)-l+1)*num[i];
l=num[i]+1;
}
cout<<ans;
return 0;
}
二:bfs
1.题目:校门外的树
解题思路:
思路 普通 bfs 即可
代码块:
int fx[5]={0,0,1,-1},fy[5]={1,-1,0,0};
bool bfs()
{
queue<PII>que;
que.push({qx,qy});
while(!que.empty())
{
PII tem=que.front();
que.pop();
int x=tem.first;
int y=tem.second;
sd[qx][qy]=true;
for(int i=0;i<4;i++)
{
int gx=x+fx[i];
int gy=y+fy[i];
if(!sd[gx][gy]&&gx>0&&gx<=n&&gy>0&&gy<=m&&mapp[gx][gy]!='#')
{
if(gx==zx&&gy==zy)return true;
que.push({gx,gy});
sd[gx][gy]=true;
}
}
}
return false;
}
int main()
{
while(cin>>n>>m)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
cin>>mapp[i][j];
if(mapp[i][j]=='S')qx=i,qy=j;
else if(mapp[i][j]=='E')zx=i,zy=j;
}
if(bfs())cout<<"Yes"<<endl;
else cout<<"No"<<endl;
memset(sd,false,sizeof(sd));
}
return 0;
}
2.题目:jelly
解题思路:
三维度bfs
代码块:
struct zb
{
int gx,gy,gz;
};
char mapp[110][110][110];
bool sd[110][110][110];
int num[110][110][110];
int n;
int fx[] = {0, 1, 0, -1 ,0 , 0}, fy[] = {1, 0, -1, 0, 0, 0}, fz[] = {0, 0, 0, 0, 1, -1};
void bfs()
{
queue< zb >que;
num[1][1][1]=1;
que.push({1,1,1});
sd[1][1][1]=true;
while(!que.empty())
{
zb st=que.front();
que.pop();
for(int i=0;i<6;i++)
{
int x=st.gx+fx[i];
int y=st.gy+fy[i];
int z=st.gz+fz[i];
if(x>0&&x<=n&&y>0&&y<=n&&z>0&&z<=n&&!sd[x][y][z]&&mapp[x][y][z]!='*')
{
que.push({x,y,z});
num[x][y][z]=num[st.gx][st.gy][st.gz]+1;
sd[x][y][z]=true;
}
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
{
cin>>mapp[i][j][k];
}
bfs();
if(sd[n][n][n])
cout<<num[n][n][n];
else cout<<"-1";
return 0;
}
3.题目:八数码
代码块:
string bfs(string start)
{
string end="12345678x";
queue<string>q;
unordered_map<string,string>d;
q.push(start);
int mapp[5][5];
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
char zm[5]={'d','u','r','l'};
while(!q.empty())
{
auto t=q.front();
q.pop();
string fx=d[t];
if(t==end)return fx;
int ji=0;
int x,y;
// 化成 3 * 3 的原本样子
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
{
mapp[i][j]=t[ji++];
if(mapp[i][j]=='x')
{
x=i,y=j;
}
}
//枚举四种变化
for(int i=0;i<4;i++)
{
int a= x+dx[i],b=y+dy[i];
if(a>=0 && a<3 && b>=0 && b<3 )
{
swap(mapp[x][y],mapp[a][b]);
ji=0;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
t[ji++]=mapp[i][j];
//判断当前状态有没有出现过
if(!d.count(t))
{
q.push(t);
string tem=fx;
tem+=zm[i];
d[t]=tem; //每一个状态对应上一个操作字符串
}
swap(mapp[x][y],mapp[a][b]);
}
}
}
return "";
}
int main()
{
string c,start;
for(int i=0;i<9;i++)
{
cin>>c;
start+=c;
}
cout<<bfs(start);
return 0;
}
三:线性筛
1.题目:欧拉筛
解题思路:
欧拉筛的同时找到最小质因子
代码块:
signed main()
{
cin >> n;
for(int i = 2; i <= n; i++)
{
if(!st[i]) primes[cnt++] = i, ans += i;
for(int j = 0; primes[j] <= n / i; j++)
{
st[primes[j]*i] = true;
ans += primes[j];
if(i % primes[j] == 0) break;
}
}
cout << ans;
return 0;
}
四:快速幂
1.题目:.快速幂
解题思路:
由题目可知
x^(p - 1) = 1 % p 那么, x^((p - 1) -1) = x ^ (-1) %p
所以
x ^ (p - 2) % p= x ^ (-1) % p
代码块:
long long qmi(long long a, long long b, int p)
{
long long res = 1;
while (b)
{
if (b & 1) res = (res % p) * (a % p) % p;
a = (a % p) * (a % p) % p;
b >>= 1;
}
return res % p;
}
void solved()
{
int n, m;
cin >> n >> m;
int p = 998244353;
cout << (n * n - m) % p * (qmi(n * n, p - 2, p)) % p;
}
五:贪心
1.题目:丢手绢
解题思路:
我们可以先枚举其中一个孩子,然后遍历找离这个孩子最远的距离,然后所有取max。
我们先统计所有距离的和,因为如果 > 距离的一半就意味着我们取了另一个半圆
然后每次一步步遍历每一个孩子,只要不超过刚好到另一个半圆
代码块:
int main(){
int n;
cin>>n;
int yz=0;
for(int i=1;i<=n;i++){
cin>>s[i];
yz+=s[i];
}
int ans=0;
int num=0;
int r=1;
for(int i=1;i<=n;i++){
if(num==yz/2){
ans=num;
break;
}
while(num<yz/2){
num+=s[r];
r++;
r%=n;
if(r==0)r=n;
ans=max(ans,min(yz-num,num));
}
num-=s[i];
}
cout<<ans;
return 0;
}
2.题目:可持久化动态图上树状数组维护01背包
解题思路:
删的代价为 ai * i, 因为 ai 不变的 i 是可以变化的。
对于 ai 存在两种 正数负数,我们希望正数 * i 尽可能小, 负数 * i尽可能大
因为 i 只会变小不会变大,所以我们先把负数都删掉,保证他们的 i 是最大的。
然后序列只剩下正数,每次我们都从第一个删,保证 i 是最小的。
这样他们的 i 都是1。
代码块:
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++)
cin >> a[i];
long long sum = 0;
for(int i = n -1 ; i >= 0; i--)
{
if(a[i] < 0) sum += a[i] * (i + 1);
}
for(int i = 0; i < n; i++)
{
if(a[i] >= 0)
{
sum += a[i];
}
}
cout << sum;
return 0;
}
六:队列
1.题目:栈和排序
解题思路:
先想到栈的规律 是先进后出,后进先出,然后读题发现 需要输出 “字典序最大”的出栈序列,并且保证是1->n的排列。关于字典序排序,就是从前往后依次比较,能够比较出大小就退出 返回比较结果。(在做字符串类的问题中遇到的比较多)例如 321 大于 312. 所以我们只需要保证 从前往后都尽可能大就可以了。我们可以用一个数组 b记录从该位置到最后的最大值,即b(i)等于 数组a中 索引 k为(i,n)中的最大值,然后就模拟出栈顺序就可以了,如果b(i)>s.top(),就入栈,反之出栈,最后如果栈不为空的话,在依次弹出栈即可。
代码块:
#include<iostream>
#include<stack>
using namespace std;
#define ll long long
const int N=3e6+10;
ll a[N],b[N];
int main(){
ll n;cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=n;i>=1;i--)b[i]=max(a[i],b[i+1]);
stack<ll> s;
for(int i=1;i<=n;i++)
{
if(s.size()==0)s.push(a[i]);
else{
if(b[i]>s.top())s.push(a[i]);
else {
while(s.size()&&s.top()>b[i]){
cout<<s.top()<<" ";
s.pop();}
s.push(a[i]);
}
}
}
while(s.size()){
cout<<s.top()<<" ";s.pop();
}
}
2.题目:队列Q
解题思路:
根据题意,因为在一个队列上 进行若干次操作,每次放在队头一定比队列中所有数靠前,每次放在队尾一定比队列中所有数靠后,最终只需要输出最终排列方式,所以可以 使用桶排 进行排序即可,观察数据范围 只需要 开 3e5大小的数组就可以实现了。可以使用 一个ma 记录该数的索引号,每次操作之后进行更新,最后从前往后输出符合要求(该数字所在位置 等于 该数所记录的 索引号)的就行。
代码块:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+10;
ll a[N],ma[N];
int main(){
ll n;cin>>n;
ll l=1e5,r=1e5+n+2,s=1e5+1;
for(int i=1;i<=n;i++){
ll x;cin>>x;
a[s]=x;
ma[x]=s++;
}
ll k;cin>>k;
while(k--){
string s;cin>>s;
ll p;cin>>p;
if(s=="FIRST"){
a[l]=p; ma[p]=l--;
}
else{
a[r]=p; ma[p]=r++;
}
}
for(int i=1;i<=3e5;i++){
if(a[i]&&ma[a[i]]==i)cout<<a[i]<<" ";
}
return 0;
}
3.题目:蚯蚓
解题思路:
题意特别好理解,大家模拟一下 就可以看出来,每次取最长的一根蚯蚓 切割成两半。首先大家可以想到用朴素做法,即用大根堆来维护蚯蚓长度,但是通过观察 数据范围 这种办法不行。 然后通过 模拟我们发现 首先对原来的蚯蚓进行排序,从大到小切,每次切割 后的蚯蚓分成两堆,也呈单调性,所以我们可以 通过维护 三个队列 来每次找到 最长的蚯蚓。 然后 我们在维护蚯蚓单调性时,因为没有被切割的所以其它蚯蚓都需要增加一段长度,我们可以记一个总的时间变量用于标记 蚯蚓增加的长度,在切割的时候 加上增加的长度,在放回队列的时候 要减去蚯蚓增加的长度。
代码块:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<ll,ll>
int main(){
// 原来部分 切割后左部分 切割后右部分
deque<ll>q1,q2,q3;
long double u,v;
ll n,m,q,t;
cin>>n>>m>>q>>u>>v>>t;
ll cnt=n+m;
for(ll i=1;i<=n;i++){
ll x;cin>>x; q1.push_back(x);
}
sort(q1.begin(),q1.end());
// 总的时间 变量
ll k=0;
// 输出答案 所用的时间变量
ll ks=1;
while(m--){
// 找到 三个队列中的 最长的蚯蚓
ll k1=-1e9,k2=-1e9,k3=-1e9,k4;
if(q1.size())k1=q1.back();
if(q2.size())k2=q2.front();
if(q3.size())k3=q3.front();
if(k1>=k2&&k1>=k3){k4=k1;q1.pop_back();}
else if(k2>=k1&&k2>=k3){k4=k2;q2.pop_front();}
else if(k3>=k1&&k3>=k2){k4=k3;q3.pop_front();}
// 加上 它增加的长度
ll kk1=k4+k;
if(ks%t==0)printf("%lld ",kk1);
ks++;
k+=q;
ll kk2=(floor)(u*(double)kk1/v);
// 再放回的时候 要减去增加的 长度
q2.push_back(kk2-k);
q3.push_back(kk1-kk2-k);
}
cout<<endl;
// 输出答案用到的 时间变量
int kx=1;
for(int i=1;i<=cnt;i++){
// 找到 三个队列中的 最长的蚯蚓
ll k1=-1e9,k2=-1e9,k3=-1e9,k4;
if(q1.size())k1=q1.back();
if(q2.size())k2=q2.front();
if(q3.size())k3=q3.front();
if(k1>=k2&&k1>=k3){k4=k1;q1.pop_back();}
else if(k2>=k1&&k2>=k3){k4=k2;q2.pop_front();}
else if(k3>=k1&&k3>=k2){k4=k3;q3.pop_front();}
// 加上 它增加的长度
ll kk1=k4+k;
if(kx%t==0)printf("%lld ",kk1);
kx++;
}
return 0;
}
七:堆
1.题目:合并果子
解题思路:
根据题意,因为要尽可能地节省体力,所以 每次合并都需要合并相对最小的两堆果子。这个刚好符合堆 的应用,小根堆 堆顶 是二叉树中权值最小的数字。大家可以 自己手写实现小根堆,或者直接调用 stl 中提供的小根堆。
代码块:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main() {
ll n;cin>>n;
priority_queue<ll, vector<ll>, greater<ll> >a;
for (int i = 1; i <= n; i++) {
ll b;cin >> b;
a.push(b);
}
ll sum = 0;
while (a.size() >= 2) {
ll x, y;
x = a.top();
a.pop();
y = a.top();
a.pop();
sum = sum + x + y;
a.push(x + y);
}
cout << sum;
return 0;
}
2.题目:建筑抢修
解题思路:
理解题意,发现需要尽可能的抢修多的建筑,所以可以 根据建筑的报废截止时间从小到大排序,保证 尽可能修先报废的,报废截止时间相同的 可以根据该建筑维修时间 从小打到排序,保证 修的越快越好 可以尽可能 留出更多的时间 抢修尽可能多的建筑。可以定义一个遍历 k 做工作截止时间,然后从前往后遍历,当该工作的开始时间大于该工作的截止时间的时候,直接cnt++就可以了,重点就是 不可以放的时候,需要考虑是否可以 从之前已经维修过的建筑中找到 一个维修时间最长的 看能否替换成该工作,可以使工作截止时间尽可能小。这里可以 使用一个大根堆 维护 已经维修的所有建筑的 维修时间。
代码块:
#include<iostream>
#include<queue>
#include<string.h>
#include<map>
#include<cmath>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
const int N =4e5;
#define P pair<ll,ll>
priority_queue<int, vector<int>, less<int> > heap;//大根堆
#define ll long long
struct xx
{
// a:抢修这个建筑最晚开始的时间
//b:抢修这个建筑所需要的时间
//c:这个建筑报废的时间
ll a, b, c;
};
// 根据 优先级进行排序分别是 按照报废截止的时间从小到大排序,截止时间相同 将抢修建筑所用时间短的放前面
bool cmp(xx p1, xx p2)
{
if (p1.c < p2.c)return true;
else if (p1.c == p2.c && p1.b < p2.b)return true;
else return false;
}
int main()
{
ll n; cin >> n;
vector <xx>v1;
for (int i = 1; i <= n; i++)
{
ll x, y; cin >> x >> y;
xx x1; x1.a = y - x; x1.b = x; x1.c = y;
v1.push_back(x1);
}
sort(v1.begin(), v1.end(), cmp);
// cnt:抢修 建筑成功的个数
ll cnt = 1;
// k:现在工作截止的时间
ll k =v1[0].b;
heap.push(v1[0].b);
for (int i = 1; i < v1.size(); i++)
{
//当开始时间 大于上一个工作截止的时间 就直接cnt++ 更新 k
if (v1[i].a >= k)
{
cnt++;
k += v1[i].b;
heap.push(v1[i].b);
}
else
{
//找到 堆中 工作时间最长的一个 看能否替换 (这样可以保证工作截止的时间尽可能小 就可以尽可能多的抢修建筑)
ll p = heap.top();
if (p>v1[i].b)
{
k = k - p + v1[i].b;
// 假设 将v1[i]放进堆中(即已抢修的建筑里面) 更新k 看是否符合要求 即是否小于该建筑的报废时间 如果可以就 更新k
if (k <= v1[i].c)
{
heap.pop();
heap.push(v1[i].b);
}
}
}
}
cout << cnt << endl;
return 0;
}
构造
1.字符串构造
解题思路:
根据题意构造 因为可以在任意位置插入若干字符, 保证 包含"SANTA”不包含 “SATAN”。 因为 任意字符 所以只需要保证开始不包含 “SATAN”。直接输出空字符即可,通过插入“SANTA”即符合 题目解。
解题代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main()
{
cout<<"";
return 0;
}
2.字符串构造
解题思路:
先分析题意 需要找到 两个正整数a,b使得 b整除a,且 a/b=s1,ab=s2,化简式子 之后 a=sqrt(s1s2),b=sqrt(s2/s1); 可以发现 s1*s2 和s2/s1 一定是完全平方数, 我们先假设 s1=1 的时候,只需要满足s2是完全平方数 即可。 因为题目 需要满足至少两种方案 故我们需要找到在满足第一个方案的前提下找到符合要求的第二个方案,即s1 是以1开头的 并且将s1的剩余部分给到s2的开头仍然满足完全平方数。 先假设如果我们找到符合要求的 s1 和s2 我们只需要在 s2的后面添偶数个0 就可以构造到后面的所有情况。 所有我们只要分别找到一个奇数和偶数长度的答案 就可以构造出后面所有的答案。 然后我们就可以暴力枚举一下 就可以找到长度为 4和长度为5的答案,之后分别在后面 添偶数个0就可以构造出所有答案。通过暴力枚举 我们发现n<=3是没有符合要求的答案串,就输出“-1”。
解题代码:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main(){
ll n;cin>>n;
if(n<=3)cout<<"-1"<<endl;
else{
if(n%2){
cout<<"16400";
n-=5;
}
else {cout<<"1144";
n-=4;}
for(int i=1;i<=n;i++)cout<<"0";
cout<<endl;
}
return 0;
}
位运算
1.牛牛的xor
解题思路:
先阅读题意需要构造数组a 每个数a(i)保证小于等于x(i),然后使得数组a 异或值最大 先了解按位异或(xor)不同为1相同则为0,如果我们需要保证这个和最大,就尽可能让 每一位 能填1就填1,等价与 将数组a中的所有数以二进制的形式展开 尽可能每一个位上的 1在a数组中只出现一次。例如:100^101 等于001 100^010等于100。 因为我们要保证 最后的异或值最大 所以我们要尽可能让 高位上填上1. 当数为x (1000)时,x可以填写的 位是[1,4] 当数为 y(100)时 ,x可以填写的位是[1,3].
解题代码:
#include<iostream>
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int main() {
ll n;cin>>n;
vector<ll>v;
for(int i=1;i<=n;i++){
ll x;cin>>x;v.push_back(x);
}
ll sx=0;
for(int i=0;i<v.size();i++){
// 贪心 因为我们要 尽可能让高的位上填1 所有 我们从最高位开始试
for(int j=32;j>=0;j--){
// s 就是 该位代表的数字
ll s=(ll)((ll)1<<j);
// 因为 要满足 数组a(i)要小于等于x(i) 所以当 s>v[i] 直接跳过
if(s>v[i])continue;
// & 运算是 全为1的时候才为1 当sx 在该位上已经被填写的时候 sx&s=s 如果未被填写 sx&s=0 例如 10100&00100 =100 10000&00100=0
// 在这里判断的是 sx 该位是否被填写
if(!(sx&s)){
//| 运算是 有 1 就为1 10100|00100=10100 10000|00100=10100
// sx|s 就是将该位置 填写到答案串中
sx|=s;
//因为 以及贡献了 s 所以在v[i]中要减去s
v[i]-=s;
}
}
}
// 最后输出答案
cout<<sx<<endl;
return 0;
}
2.托米的位运算
解题思路:
因为 要最大化给价 所以我们可以 从高位往低位进行 遍历 符合要求就输出答案。按位与(&)1&1=1 1&0=0 0&0=0 ,这里讲一些代码的两处问题,第一点:为什么在 遍历给价的时候,在该位上不为1的数不能放进答案串,因为假设有一个 数在该位上不为1 则进行&操作后 该数列的按位与之后在该位上为0,不满足枚举 给价的前提。第二点:为什么在该位上为1的数全部都要放进答案串,因为 能够被整除表示,数列按位与后 在该位后面全部为0,因为& 操作 有0即为0,即我们只需要保证在 该位后面 每位只需要有一个0既可,所以只要满足在该位上为1的数全部都要放进答案串中。
解题代码:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main(){
ll n;cin>>n;
vector<ll>v;
for(int i=1;i<=n;i++){
ll x;cin>>x;v.push_back(x);
}
// 因为要保证 最大化 给价 所以 从高位往低位遍历即可
for(int i=31;i>=0;i--){
vector<ll>v1;
// sum 是 该位上的数字是多少 也就是 的2的i次方
ll sum=((ll)1<<i);
for(int j=0;j<v.size();j++){
// 因为 在最大化 给价的情况下 最大化 k 所以 只要该数在 该位上有1 就将其放进答案里
if(v[j]&sum)v1.push_back(v[j]);
}
if(v1.size()){
ll s=v1[0];
// 计算 答案数组的 给价
for(int j=1;j<v1.size();j++)s&=v1[j];
// 如果该 该给价能够整除 该位(sum) 就说明符合要求 输出答案
if(s%sum==0){
cout<<v1.size()<<endl;
for(int i=0;i<v1.size();i++){cout<<v1[i];if(i!=v1.size()-1)cout<<" ";}
return 0;
}
}
else continue;
}
cout<<"-1"<<endl;
return 0;
}