1.轨道炮(数学+模拟)
#include <iostream>
#include <map>
using namespace std;
const int N=1010;
int x[N],y[N],v[N];
char d[N];
int main()
{
int n;
int ans=-100;
cin>>n;
for(int i=1;i<=n;i++)
cin>>x[i]>>y[i]>>v[i]>>d[i];
for(int t=0;t<=1000;t++)
{
map<int,int> col,row; //col代表每一列上有几个点,row代表每一行上有几个点
for(int i=1;i<=n;i++)
{
if(d[i]=='R') //如果是向右移的话,只有横坐标会发生改变,也就是不同的列,纵坐标不会随时间的改变而改变
{
col[x[i]+v[i]*t]++;
row[y[i]]++;
}
if(d[i]=='L') //左边同理
{
col[x[i]-v[i]*t]++;
row[y[i]]++;
}
if(d[i]=='U') //如果是向上移的话,只有纵坐标会发生改变,也就是不同的行,横坐标不会随时间的改变而改变
{
col[x[i]]++;
row[y[i]+v[i]*t]++;
}
if(d[i]=='D') //向下同理
{
col[x[i]]++;
row[y[i]-v[i]*t]++;
}
}
for(auto item :col)
{
ans=max(ans,item.second);
}
for(auto item:row)
{
ans=max(ans,item.second);
}
}
cout<<ans<<endl;
return 0;
}
2.抓娃娃(贪心+数学)
#include <iostream>
#include <algorithm>
using namespace std;
/*
题目中提到max(ri-li)<=min(Ri-Li),说明最小的区间长度都大于最大的线段长度,所以只要线段的中点在区间的范围内,
这个区间就能够包含这条线段,所以先对所有线段的中点进行排序,再找第一个大于区间左端点的线段,
然后找第一个大于区间右端点的线段,将两个线段的下标索引相减,中间的所有线段都能够满足条件
*/
const int N=100010;
double c[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
float l,r;
cin>>l>>r;
}
sort(c+1,c+n+1);
for(int i=1;i<=m;i++)
{
int l,r;
cin>>l>>r;
int left=lower_bound(c+1,c+n+1,l)-c;
int right=upper_bound(c+1,c+n+1,r)-c;
cout<<right-left<<endl;
}
return 0;
}
3.弹珠堆放(数学)
#include <iostream>
using namespace std;
//一定要先判断,再++
int main()
{
int n=1;
long long sum=0;
while(1)
{
long long tmp=0;
for(long long i=1;i<=n;i++)
tmp+=i;
sum+=tmp;
if(sum>20230610)
{
cout<<n-1<<endl;
break;
}
if(sum==20230610){
cout<<n<<endl;
break;
}
n++;
}
return 0;
}
4.扩散(BFS)
#include <iostream>
#include <queue>
using namespace std;
/*
一个基础的BFS,需要通过距离长短来代替时间
同时为了防止数组下标出现负数,需要先加上2020偏移量,否则会产生越界
需要在devc++中运行得到结果,在蓝桥评测系统中会报出运行错误(爆内存)
*/
typedef pair<int,int> PII;
const int N=7000;
bool st[N][N];
int dis[N][N];
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int main()
{
queue<PII> q;
q.push({0+2020,0+2020});
q.push({2020+2020,11+2020});
q.push({11+2020,14+2020});
q.push({2000+2020,2000+2020});
st[0+2020][0+2020]=true;
st[2020+2020][11+2020]=true;
st[11+2020][14+2020]=true;
st[2000+2020][2000+2020]=true;
while(q.size())
{
PII tmp=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int a=tmp.first+dx[i];
int b=tmp.second+dy[i];
if(st[a][b]==false)
{
if(dis[tmp.first][tmp.second]<2020)
{
dis[a][b]=dis[tmp.first][tmp.second]+1;
q.push({a,b});
st[a][b]=true;
}
}
}
}
int count=0;
for(int i=0;i<7000;i++)
{
for(int j=0;j<7000;j++)
{
if(st[i][j]) count++;
}
}
cout<<count<<endl;
return 0;
}
5.班级活动(贪心+分情况讨论)
#include <iostream>
#include <map>
using namespace std;
/*思路:先用map统计出每个同学的id出现的次数,题目中要求任意两个同学id相同,因此map中每个元素的值为2才符合要求
因此,可以把2当作平均值,比这个平均值大的可以补给平均值小的上面,因此分三种情况
(1)如果比2大的数量等于比2小的数量,那么正好可以用多的补给少的
(2)如果比2小的数量多于比2大的数量,那么在多补少之后,小于2的可以任意两两组合凑成一对,所以只需要剩余的一半
(3)如果比2大的数量多于比2小的数量,那么多补少之后,所有多的都需要移动,因此不需要除以2
*/
const int N=100010;
int a[N];
map<int,int> mp;
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
mp[a[i]]++;
}
int upper_count=0,lower_count=0;
int ans=0;
for(auto item:mp)
{
if(item.second>2) upper_count+=(item.second-2);
if(item.second==2) continue;
if(item.second<2) lower_count+=(2-item.second);
}
//cout<<upper_count<<" "<<lower_count<<endl;
if(upper_count==lower_count){
ans=upper_count;
}
else if(upper_count>lower_count){
upper_count=upper_count-lower_count;
ans=lower_count;
//if(upper_count%2==0)
ans+=(upper_count);
//else ans+=(upper_count)/2+1;
}
else{
lower_count=lower_count-upper_count;
ans=upper_count;
if(lower_count%2==0) ans+=(lower_count)/2;
else ans+=(lower_count)/2+1;
}
cout<<ans<<endl;
return 0;
}
6.青蛙过河(前缀和+二分+贪心)
#include <iostream>
using namespace std;
/*思路:很明显需要二分,来回走x次可以看成是走2*x次
结论:如果能够使情况成立,那么长度为y的区间[l,r],全程需要经过这个区间2*x次
反证:如果可能存在一个长度大于y的区间,使青蛙能够不经过这个区间,那么跳跃能力至少大于y,与题意矛盾
*/
const int N=100010;
long long s[N];
int n,x;
bool check(int mid)
{
for(int i=1;i<=n-mid;i++)
{
if(s[i+mid-1]-s[i-1]<2*x) return false; //这里注意要减1,因为存在岸边的概念,相当于从第0个位置开始跳而并非第一个
}
return true;
}
int main()
{
cin>>n>>x;
for(int i=1;i<=n;i++){
int t;
cin>>t;
s[i]=s[i-1]+t;
}
int l=1,r=n;
while(l<r)
{
int mid=(l+r)/2;
if(check(mid)) r=mid; //如果发现可以的话,看有没有可能有更小的可能的解
else l=mid+1;
}
cout<<l<<endl;
return 0;
}
7.串的处理(字符串处理)
#include <iostream>
using namespace std;
/*
要点: 1.读入有空格的字符串要用getline(cin,字符串名)的形式
2. 大写字母A的ASCII码值比小写字母A的ASCII码值少32,这个关系要记得
3. string中的erase函数 erase(pos,count) 从pos位置开始,删除count个字符
4. string中的insert函数 insert(pos,str) 在pos位置开始插入一个字符串
*/
int main()
{
string str;
getline(cin,str);
for(int i=0;i<str.size();i++)
{
if(i==0&&str[i]>='a'&&str[i]<='z') str[i]-=32;
if(str[i]==' ')
{ //如果遇到的是空格,那么就移除,直到剩一个空格
int j=i+1;
while(str[j]==' ') str.erase(j,1);
if(str[j]>='a'&&str[j]<='z') str[j]-=32; //如果不是空格了,说明遇到下一个单词的首字母,要变成大写
}
if(str[i]>='0'&&str[i]<='9'&&str[i+1]>='a'&&str[i+1]<='z') //如果遇到数字且下一个是字母,就需要添加下划线
str.insert(i+1,"_");
if(str[i]>='0'&&str[i]<='9'&&((str[i-1]>='A'&&str[i-1]<='Z')||(str[i-1]>='a'&&str[i-1]<='z'))) //如果遇到数字且上一个是字母,就需要添加下划线
str.insert(i,"_");
}
cout<<str<<endl;
return 0;
}
8.移动字母(BFS,八数码简化题)
#include <iostream>
#include <string>
#include <algorithm>
#include <queue>
#include <unordered_map>
using namespace std;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int bfs(string end)
{
string start="ABCDE*";
queue<string> q;
unordered_map<string,int> mp;
q.push(start);
mp[start]=0;
while(q.size())
{
auto tmp=q.front();
q.pop();
if(tmp==end) return 1;
int pos=tmp.find('*'); //找到*在字符串中的位置
int x=pos/3,y=pos%3; //还原回去的位置
for(int i=0;i<4;i++)
{
int a=x+dx[i];
int b=y+dy[i];
if(a>=0&&a<2&&b>=0&&b<3)
{
swap(tmp[pos],tmp[a*3+b]);
if(mp.count(tmp)==0){
q.push(tmp);
mp[tmp]=1;
}
swap(tmp[pos],tmp[a*3+b]);
}
}
}
return 0;
}
int main()
{
int T;
cin>>T;
while(T--)
{
string ends;
cin>>ends;
cout<<bfs(ends)<<endl;
}
return 0;
}
9.日志统计(滑动窗口+双指针)
#include <iostream>
#include <algorithm>
#include <map>
#include <set>
using namespace std;
/* 思路:滑动窗口+排序
先按照时间进行排序,因为大小是固定的,所以用滑动窗口,让i往前移,按时间顺序遍历整个数组
每次移动之前判断一下j到i的距离是否小于d
如果j有不符合条件的,那么应该在滑动窗口中清除掉对应的值
每次判断当前这条记录对应的id号的帖子是否是热帖,根据cnt[]来判断,cnt由于滑动窗口保证其是动态变化的
*/
const int N=100010;
typedef pair<int,int> PII;
PII a[N];
bool st[N];
int cnt[N];
int main()
{
int n,d,k;
cin>>n>>d>>k;
for(int i=1;i<=n;i++) cin>>a[i].first>>a[i].second;
sort(a+1,a+n+1);
int j=1;
for(int i=1;i<=n;i++)
{
cnt[a[i].second]++;
while(a[i].first-a[j].first>=d)
{
cnt[a[j].second]--;
j++;
}
if(cnt[a[i].second]>=k) st[a[i].second]=true;
}
for(int i=0;i<=100000;i++)
if(st[i]) cout<<i<<endl;
return 0;
}
10.十进制转换为n进制(短除法)
#include <iostream>
#include <algorithm>
using namespace std;
//十进制转换成其他进制:短除法
//下面写法是十进制转换为b进制的通用写法
char get(int x)
{
if(x<=9) return x+'0';
else return x-10+'A';
}
int base(int n,int b)
{
string num="";
while(n){
num+=get(n%b);
n/=b;
}
reverse(num.begin(),num.end());
return num.size();
}
int main()
{
cout<<base(2022,2)<<endl;
return 0;
}
11.卡牌(二分 注意数据范围)
#include <iostream>
using namespace std;
const int N=200010;
long long a[2*N],b[2*N];
long long n,m;
bool check(long long mid)
{
long long sum=0;
for(int i=1;i<=n;i++)
{
if(a[i]<mid){
long long need=mid-(long long)a[i];
if(need>b[i]) return false;
else sum+=(long long)need;
}
if(sum>m) return false;
}
return true;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
long long l=0,r=49000000000;
while(l<r)
{
long long mid=(long long)(l+r+1)>>1;
if(check(mid)) l=mid; //如果成功,看有没有有可能能凑成更多的牌
else r=mid-1;
}
cout<<r<<endl;
return 0;
}
12.砍竹子(大根堆,贪心,区间合并)
#include <iostream>
#include <queue>
#include <vector>
#include <cmath>
using namespace std;
/*
预备知识:大根堆和小根堆的STL写法
需要引入头文件#include <queue>
priority_queue<int> q //默认大根堆 堆顶元素是堆中最大的
priority_queue<int,vector<int>,greater<int>> q //默认小根堆 堆顶元素是堆中最小的
*/
/*
思路:将每一个值当作一个节点,这个与正常大根堆解决问题的区别在于,这个需要判断是不是连续区间,也就是说,
需要动态记录每一个值所在的左右区间,并且在每次操作时检查是否有区间可以合并,如果有,这些区间需要进行合并
直到最大的元素是1,说明全部的元素都被置为1了
*/
typedef long long LL;
struct node{
LL val,l,r; //节点值,区间左端点,区间右端点
};
bool operator<(node a,node b)
{
return a.val==b.val?a.l<b.l:a.val<b.val; //返回值较大的那个,如果值相同,返回左端点大的那个
}
int main()
{
LL n=0,ans=0;
priority_queue<node> q;
cin>>n;
for(int i=1;i<=n;i++)
{
LL x;
cin>>x;
q.push({x,i,i}); //点一开始左右区间都是自己
}
while(q.top().val!=1)
{
node tmp=q.top(); //这里拿出的是值最大,而且左端点也是最大的一个点的值(相当于向从右往左进行合并)
q.pop();
node sd;
while(!q.empty())
{
//接下来这个点能够合并的前提是这个点的值相同,并且两个点的区间能够重叠
sd=q.top();
if(sd.val==tmp.val&&sd.r>=tmp.l-1) q.pop(),tmp.l=sd.l; //更新tmp的左端点即可
else break;
}
LL h=sqrtl(tmp.val/2+1);
q.push({h,tmp.l,tmp.r}); //新高度肯定要重新入队,值正常计算,只是区间的左右端点有可能会发生变化
ans++;
}
cout<<ans<<endl;
return 0;
}
13.迷宫与陷阱(BFS)
#include <iostream>
#include <queue>
using namespace std;
//有无敌状态可以随便走,没有无敌状态只能往没标记过的地方走
//而且无敌状态不能累加,这里题目没有明确说明
const int N=1010;
char g[N][N];
bool st[N][N];
int dis[N][N];
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int n,k;
struct node{
int x,y,time;
};
int bfs(int x,int y,int time)
{
queue<node> q;
q.push({x,y,time});
st[x][y]=true;
dis[x][y]=0;
while(q.size())
{
auto tmp=q.front();
q.pop();
if(tmp.x==n&&tmp.y==n) return dis[tmp.x][tmp.y];
for(int i=0;i<4;i++)
{
int a=tmp.x+dx[i];
int b=tmp.y+dy[i];
if(a<1&&a>n&&b<1&&b>n) continue; //如果越界,肯定不行
if(g[a][b]=='#') continue; //如果遇到了墙,那么肯定不能走
if(g[a][b]=='X'&&tmp.time==0) continue; //如果遇到了陷阱,如果没有无敌时间,也不能走
if(g[a][b]=='%') //如果遇到了道具,只有第一次是有效的,将其变成.
{
int now_time=k;
g[a][b]='.';
st[a][b]=true;
q.push({a,b,now_time});
dis[a][b]=dis[tmp.x][tmp.y]+1;
}
else if(g[a][b]=='.'&&tmp.time>0) //如果是.并且在无敌状态下,需要将无敌状态的时间减1,无敌状态下随便走
{
int now_time=0;
if(tmp.time>0) now_time=tmp.time-1;
q.push({a,b,now_time});
st[a][b]=true;
dis[a][b]=dis[tmp.x][tmp.y]+1;
}
else if(g[a][b]=='.'&&tmp.time==0&&!st[a][b]) //如果是. 并且不是无敌状态 那么就要看这个点是否被走过
{
q.push({a,b,0});
st[a][b]=true;
dis[a][b]=dis[tmp.x][tmp.y]+1;
}
else if(g[a][b]=='X'&&tmp.time>0) ///陷阱有可能被多次走,不必标记
{
int now_time=tmp.time-1;
q.push({a,b,now_time});
dis[a][b]=dis[tmp.x][tmp.y]+1;
}
}
}
return -1;
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>g[i][j];
cout<<bfs(1,1,0)<<endl;
return 0;
}
14.重新排序(差分)
#include <iostream>
#include <algorithm>
using namespace std;
int n,m;
const int N=100010;
int a[N],b[N];
void insert(int l,int r)
{
b[l]+=1;
b[r+1]-=1;
}
int main()
{
long long old_sum=0,new_sum=0;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cin>>m;
for(int i=1;i<=m;i++)
{
int l,r;
cin>>l>>r;
insert(l,r);
}
for(int i=1;i<=n;i++) b[i]=b[i-1]+b[i];
for(int i=1;i<=n;i++) old_sum+=(long long)a[i]*b[i];
sort(b+1,b+n+1);
sort(a+1,a+n+1);
for(int i=1;i<=n;i++) new_sum+=(long long)a[i]*b[i];
//cout<<old_sum<<" "<<new_sum<<endl;
cout<<new_sum-old_sum<<endl;
return 0;
}
15.123(二分+前缀和)
#include <iostream>
using namespace std;
/*
思路:右区间最多10^12,说明项数最多是10^12 数列为1 1,2 1,2,3 1,2,3,4
所以项数和可以用等差公式来求,即(n+1)*n/2<=10^12 解得数组大小开到1500000足够
a[i]代表第i个区间的项数,可以用前缀和得出a[i]=a[i-1]+i
s[i]为前i个区间的和,有s[i]=s[i-1]+a[i] (因为第i个区间内所有的元素和恰好可以用a[i]来表示)
这里的a[i]很特殊,因为a[i]不光代表各个区间项数的前缀和,还代表每个区间内元素的前缀和
所以对于任何一个位置pos,一定是在j+1这个区间内,所以可以分成两部分来求
一部分是前j个区间的元素和,还有就是pos在j+1这个区间中的相对位置得出的元素和
*/
typedef long long ll;
const int N=1414215;
ll a[N],s[N];
ll presum(ll pos)
{
ll l=0,r=N;
while(l<r)
{
ll mid=(ll)(l+r+1)/2;
if(a[mid]>pos) r=mid-1;
else l=mid;
}
return s[l]+a[pos-a[l]];
}
int main()
{
int T=0;
cin>>T;
for(ll i=1;i<=N;i++) a[i]=a[i-1]+(ll)i;
for(ll i=1;i<=N;i++) s[i]=s[i-1]+(ll)a[i];
while(T--)
{
ll l,r;
cin>>l>>r;
cout<<presum(r)-presum(l-1)<<endl;
}
return 0;
}
16. 重复字符串(贪心)
#include <iostream>
#include <map>
using namespace std;
int main()
{
string s;
int k=0,ans=0;
cin>>k;
cin>>s;
if(s.size()%k!=0){
cout<<-1<<endl;
return 0;
}
for(int i=0;i<s.size()/k;i++)
{
map<char,int> mp;
int start=i;
while(start<s.size())
{
mp[s[start]]++;
start+=s.size()/k;
}
int max_need=0;
for(auto item:mp)
{
max_need=max(max_need,item.second);
}
ans+=k-max_need;
}
cout<<ans<<endl;
return 0;
}
17.挖矿(前缀和)
#include <iostream>
using namespace std;
const int N=1000010;
int l[N],r[N];
/*
思路:维护两个前缀和数组,分别表示从0点分别向左向右能挖到多少,后在分别枚举向左向右挖的数,取max就行
先向右走m以内的距离,再算能否再向左走,如果能,再加上向左走的那部分的矿石数
另外一侧同理
*/
int main()
{
int n=0,m=0,f=0,ans=0;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int pos;
cin>>pos;
if(pos>0) r[pos]++;
else if(pos<0) l[abs(pos)]++;
else if(pos==0) f=1;
}
for(int i=1;i<=N;i++) l[i]+=l[i-1];
for(int i=1;i<=N;i++) r[i]+=r[i-1];
for(int i=1;i<=m;i++)
{
int sum=r[i];
if(m-2*i>0) sum+=l[m-2*i];
ans=max(ans,sum);
sum=l[i];
if(m-2*i>0) sum+=r[m-2*i];
ans=max(ans,sum);
}
cout<<ans+f<<endl;
return 0;
}
18.充电能量(格式化输入)
#include <iostream>
using namespace std;
int main()
{
int sum=0;
int T,h,m,s,oldu,oldi,u,i,start_time,end_time;
cin>>T;
T--;
scanf("%d:%d:%d",&h,&m,&s);
scanf("%d%d",&oldu,&oldi);
start_time=h*3600+m*60+s;
while(T--)
{
//以格式化的形式读入
scanf("%d:%d:%d",&h,&m,&s);
scanf("%d%d",&u,&i);
//都转换成秒数
end_time=h*3600+m*60+s;
int length=end_time-start_time;
sum+=length*oldu*oldi;
oldu=u;
oldi=i;
start_time=end_time;
}
cout<<sum<<endl;
return 0;
}
19.拔河(STL+前缀和+区间处理)
#include <iostream>
#include <set>
using namespace std;
/*
思路:将所有区间的和记录到multiset中,multiset中可以有重复的元素,并且是自动排序的
然后遍历每一个区间,这里遍历第一个区间的右端点,因为后面要在multiset中找绝对值之差最小的另外一个区间和
所以两个区间不能有重叠(如果有重叠,说明这个人既在第一个队,又在第二个队,不符合题意)
所以要删掉以第一个区间右端点为左端点的区间
接下来枚举第一个区间的左端点,这样能够确定第一个区间,也就能拿出来区间和,然后去multiset中
找第一个大于等于这个和的,说明找到的是离这个和最近的,当然不一定比它大,也有可能比它小
所以如果找到的这个不是第一个,说明往前还有。这个和前面的元素与这个和的差的绝对值可能更小,遍历完所有第一个区间即可
这里需要注意的是,为什么只删掉以第一个区间右端点为第二个区间左端点的第二个区间,因为除了这种情况
其他情况都可以看作是这两个区间有公共的元素,可以同时加上或减去元素,不影响绝对值的差
只有这种情况会造成两个区间共用一人的情况出现,其他情况虽然也会造成区间重叠,但本质上与都减掉相同
只有这个情况需要手动处理
*/
typedef long long ll;
const int N=1010;
ll a[N],s[N];
ll ans=1e9;
multiset<ll> ms;
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
for(int i=1;i<=n;i++) //将所有区间和全部放到multiset中
for(int j=i;j<=n;j++)
ms.insert(s[j]-s[i-1]);
for(int i=1;i<=n;i++) //枚举第一个区间的右端点
{
//删掉以第一个区间右端点为左端点的区间
for(int j=i;j<=n;j++)
{
ll tmp=s[j]-s[i-1];
ms.erase(ms.find(tmp));
}
//枚举第一个区间的左端点,通过两个端点来确定第一个区间
for(int j=1;j<=i;j++)
{
ll tmp=s[i]-s[j-1];
auto pos=ms.lower_bound(tmp); //lower_bound函数是找到第一个大于等于目标值元素
if(pos!=ms.end()) ans=min(ans,abs(*pos-tmp)); //如果找到了
if(pos!=ms.begin()){
pos--;
ans=min(ans,abs(*pos-tmp));
}
}
}
cout<<ans<<endl;
return 0;
}
20.最大数字(DFS)
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
ll N,ans;
string s;
vector<string> vec;
int A,B;
void dfs(int level,int a,int b)
if(level==s.size()){
vec.push_back(s);
//cout<<s<<endl;
return;
}
char stmp=s[level];
int tmp=s[level]-'0';
if(A>=a+9-tmp){
s[level]='9';
dfs(level+1,a+9-tmp,b);
s[level]=stmp;
}
if(B>=b+tmp+1){
s[level]='9';
dfs(level+1,a,b+tmp+1);
s[level]=stmp;
}
if(A<a+9-tmp&&B<b+tmp+1)
{
s[level]=s[level]+A-a;
dfs(level+1,A,b);
s[level]=stmp;
}
}
int main()
{
cin>>N>>A>>B;
s=to_string(N);
dfs(0,0,0);
string max_ans="0";
for(auto item:vec){
if(item>=max_ans) max_ans=item;
}
// cout<<s<<endl;
cout<<max_ans<<endl;
return 0;
}
21.扫地机器人(二分)
#include <iostream>
#include <algorithm>
using namespace std;
/*
思路:二分时间,每个机器人能走的格子数是时间/2,记为cnt
每个机器人先往左边看上一个空白的位置到这个机器人所在位置是否大于机器人能走的最大格子数
如果不大于的话,说明机器人往左清扫完之后,还有剩余可以往右清扫
如果左边已经没有需要被清扫的格子,那么机器人可以直接一直往右清扫cnt个格子
最后看一下是不是所有的格子都已经被清扫过
*/
const int N=100010;
int a[N];
bool st[N];
int n,k;
bool check(int mid)
{
int cnt=mid/2;
int blank=1;
for(int i=1;i<=k;i++)
{
if(a[i]>blank){
if(a[i]-blank>cnt) return false;
else {
blank=a[i]+cnt-(a[i]-blank)+1;
while(st[blank]) blank++;
}
}
else if(a[i]<blank){
blank=a[i]+cnt+1;
while(st[blank]) blank++;
}
}
if(blank<=n) return false;
return true;
}
int main()
{
cin>>n>>k;
for(int i=1;i<=k;i++){
cin>>a[i];
st[a[i]]=true;
}
sort(a+1,a+k+1);
int l=0,r=N*2;
while(l<r)
{
int mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<endl;
return 0;
}
22.迷宫(BFS+记录路径)
#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
int g[30][50] = {
{0,1,0,1,0,1,0,1,0,0,1,0,1,1,0,0,1,0,0,1,0,1,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,0,0,0,1,0,0,0,1,0,1,0,1,0},
{0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,1,0,0,1,1,0,1,0,0,1,0,1},
{0,1,1,1,1,0,1,1,0,1,0,0,1,0,0,0,1,0,0,0,0,0,1,1,0,1,0,0,1,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0},
{0,1,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,1,1,0,1,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,1,0,1,0,1,1,0,0,1,0,1,1},
{0,0,0,1,1,1,1,1,0,0,0,0,0,0,1,0,1,0,0,0,0,1,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0},
{1,1,0,0,1,0,0,0,1,1,0,1,0,1,0,0,0,0,1,0,1,0,1,1,0,0,0,1,1,0,1,0,0,1,1,0,1,0,1,0,1,0,1,1,1,1,0,1,1,1},
{0,0,0,1,1,0,1,1,0,1,0,1,0,1,0,0,1,0,0,1,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,1,1,1,0,0,0,0,0,0,0},
{1,0,1,0,0,0,0,0,1,0,1,0,0,0,1,0,0,1,1,0,1,0,1,0,1,0,1,1,1,1,1,0,0,1,1,0,0,0,0,1,0,0,0,0,1,1,1,0,1,0},
{0,0,1,1,1,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,1,1,0,0,0,0,1,0,0,1},
{1,1,0,0,0,1,1,0,1,0,0,0,0,1,1,1,0,0,1,0,0,0,1,0,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,1,1,0,1,0,0,0},
{0,0,0,1,0,0,0,0,1,0,0,1,0,0,0,0,0,1,0,1,0,0,1,0,1,0,1,0,1,1,1,0,1,0,0,0,1,0,1,0,1,0,1,0,0,0,0,1,0,1},
{1,1,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,1,0,1,0,1,0,1,0,0,1,0,0,1,0,0,0,1,0,1,0,0},
{0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,1,0,1,1,0,0,1,1,1,1,0,1,0,0,0,1,1,0,0,0,0,0,1,0,1,0,1,0,1,0,0,0,1,1},
{1,0,1,0,1,0,1,0,0,1,1,1,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,1,1,0,0,1,1,1,1,0,1,1,0,1,0,0,0,0,1,0,0,0},
{1,0,1,0,1,0,1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,0,1,0,1,0,0,0,0,1,0,1,0,0,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1},
{1,0,0,0,0,0,0,0,1,0,1,1,0,0,0,1,0,0,0,0,1,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,1,0,0,0,0,0,0,0,0,1,0,0},
{1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,1,0,1,0,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,1,1,0,1,0,1,0,0,1},
{0,0,1,0,1,0,0,1,0,1,0,1,0,1,1,0,1,0,0,1,0,1,0,1,0,0,0,1,1,0,1,0,1,0,1,1,0,1,1,1,0,0,0,0,1,1,0,1,0,1},
{1,1,0,0,1,0,1,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,0,1,0},
{0,0,0,0,1,0,0,0,1,1,0,0,0,0,1,1,0,1,0,1,1,0,1,0,0,0,0,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,0,0,1,1,1,0,1},
{1,0,1,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,1,1,1,0,1,1,0,0,1,0,1,1,0,1,0,1,1,0,1,0,1,0,1,0,0,0,0,1},
{0,0,1,0,1,0,0,0,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,1,0,0,0,1,0,0,0,1,0,1,0,1},
{1,0,1,0,0,0,0,1,0,0,0,1,1,0,0,1,0,0,0,1,0,0,0,0,1,0,1,0,1,0,0,1,0,1,0,1,0,1,0,1,1,1,1,1,0,1,0,0,1,0},
{0,0,0,0,0,1,0,0,1,0,1,0,0,0,0,0,0,1,1,0,0,1,0,1,0,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0},
{1,1,0,1,0,0,0,0,0,0,1,0,0,1,1,1,0,1,1,1,0,0,1,0,0,1,0,0,0,0,1,1,1,0,1,0,0,1,0,1,1,0,1,1,1,0,1,0,0,0},
{0,0,0,0,0,1,1,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,1,1,1,0,1,0,0,0,0,0,0,1,1,0,0,1,1},
{1,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,1,0,0,0,1,0,1,0,1,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0},
{1,0,0,0,0,0,1,0,1,0,0,1,0,1,0,0,1,0,1,0,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1,0,1,0,0,0,1,0,1,1,1,0,1,0,0,0},
{0,0,1,1,1,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,1,1},
{1,0,0,0,0,0,0,1,1,0,0,1,1,1,0,1,0,1,1,1,0,1,0,0,0,1,0,0,0,1,1,0,1,1,1,0,1,0,1,0,1,1,0,1,1,1,1,0,0,0},
};
PII father[30][50];
vector<char> vec;
//上下左右方向一定要注意
int dx[4] = {1,0,0,-1},dy[4] = {0,-1,1,0};
bool st[30][50];
int dis[30][50];
void bfs() {
queue<PII> q;
q.push({0, 0});
st[0][0] = true;
father[0][0] = {-1, -1}; // 起点的父节点设为无效,一定要做
while (!q.empty()) {
auto tmp = q.front();
q.pop();
if (tmp.first == 29 && tmp.second == 49) {
int x = 29, y = 49;
while (father[x][y].first != -1 && father[x][y].second != -1) {
int fx = father[x][y].first;
int fy = father[x][y].second;
// 计算移动方向
int delta_x = x - fx;
int delta_y = y - fy;
if (delta_x == 1) vec.push_back('D');
else if (delta_y == -1) vec.push_back('L');
else if (delta_y == 1) vec.push_back('R');
else if (delta_x == -1) vec.push_back('U');
x = fx;
y = fy;
}
reverse(vec.begin(), vec.end());
for (auto c : vec) cout << c;
return;
}
for (int i = 0; i < 4; ++i) {
int a = tmp.first + dx[i];
int b = tmp.second + dy[i];
if (a >= 0 && a < 30 && b >= 0 && b < 50 && !st[a][b] && g[a][b] == 0) {
st[a][b] = true;
q.push({a, b});
father[a][b] = tmp;
dis[a][b] = dis[tmp.first][tmp.second] + 1;
}
}
}
}
int main() {
bfs();
return 0;
}
23.宝石组合(推公式,数学)
公式推导过程:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/*
思路:经过推导,S=gcd(Ha,Hb,Hc),所以只需要找三个数最大公约数最大的,然后字典序最小的即可
*/
const int N=100010;
int cnt[N]; //用于记录每个数字出现的次数
vector<int> v[N]; //用于记录以这个数作为公约数对应的候选数
int main()
{
int n,m;
cin>>n;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
cnt[x]++; ///记录这个数出现了多少次
m=max(m,x);
}
//将所有数预处理,这里取最大的,但哪怕取1e5,都不会超时
for(int i=1;i<=m;i++) //枚举以每个数作为公约数,所有可能的候选数(一定是它的倍数)
for(int j=i;j<=m;j+=i) //复杂度为1e5(1e5+1e5/2+1e5/3...)=1e5ln1e5 满足时间复杂度
if(cnt[j]) //枚举倍数的时候,只有这个数出现过(cnt[j]>0),才能将其作为候选数
{
for(int k=0;k<cnt[j];k++)
{
v[i].push_back(j);
}
}
for(int i=m;i;i--) //从最大的公约数开始枚举,只要候选数大于3 ,那么就是最大的公约数
{
if(v[i].size()>=3)
{
sort(v[i].begin(),v[i].end());
cout<<v[i][0]<<" "<<v[i][1]<<" "<<v[i][2]<<endl;
break;
}
}
return 0;
}
24.路径之谜(DFS)
#include <iostream>
using namespace std;
typedef pair<int,int> PII;
const int N=22;
const int M=100010;
int g[N][N];
bool st[N][N];
int north[N],west[N];
int a[N],b[N];
PII path[M];
int n;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
void dfs(int x,int y,int level)
{
for(int i=1;i<=n;i++) if(b[i]>north[i]) return;
for(int i=1;i<=n;i++) if(a[i]>west[i]) return;
if(x==n&&y==n)
{
for(int i=1;i<=n;i++)
if(b[i]!=north[i]) return;
for(int i=1;i<=n;i++)
if(a[i]!=west[i]) return;
for(int i=1;i<level;i++)
{
int tx=path[i].first;
int ty=path[i].second;
cout<<g[tx][ty]<<" ";
}
return;
}
for(int i=0;i<4;i++)
{
int nx=x+dx[i];
int ny=y+dy[i];
if(nx>=1&&nx<=n&&ny>=1&&ny<=n&&!st[nx][ny])
{
path[level]={nx,ny};
st[nx][ny]=true;
b[nx]++,a[ny]++;
dfs(nx,ny,level+1);
st[nx][ny]=false;
b[nx]--,a[ny]--;
path[level]={-1,-1};
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>west[i]; //自西向东
for(int i=1;i<=n;i++) cin>>north[i]; //自北向南
int cnt=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
g[i][j]=cnt;
cnt++;
}
a[1]++,b[1]++;
st[1][1]=true;
path[1]={1,1};
dfs(1,1,2);
return 0;
}
25.特殊日期(日期类问题模版题)
#include <iostream>
using namespace std;
int months[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
bool check(int year,int month,int day)
{
if(year==9999&&month==12&&day==31) return true;
return false;
}
int getsum(int n)
{
int res=0;
while(n)
{
int tmp=n%10;
res+=tmp;
n/=10;
}
return res;
}
bool is_leap(int year)
{
if((year%400==0)||(year%4==0&&year%100!=0)) return true;
return false;
}
int get_days(int year,int month)
{
if(month==2) return months[2]+is_leap(year);
else return months[month];
}
void get_next_day(int& year,int& month,int& day)
{
day++;
if(day>get_days(year,month))
{
day=1;
month++;
if(month>12)
{
year++;
month=1;
}
}
}
int main()
{
int cnt=0;
int year=1900,month=1,day=1;
while(!check(year,month,day))
{
get_next_day(year,month,day);
if(getsum(year)==getsum(month)+getsum(day)) cnt++;
}
cout<<cnt<<endl;
return 0;
}
26.商品库存管理(差分+前缀和)
#include <iostream>
using namespace std;
/*
思路:要在O(n)的时间复杂度内完成 首先先假设所有操作全部执行,计算出每个商品的库存量
然后思考,如果不执行某个操作后,商品的库存量变为0,那么说明,这个商品的库存量只能为1
所以我们统计库存量为1的商品,只要库存量为1,就在st数组中将其置1,并将其用另外一个前缀和数组维护
同时还会出现另外一种情况,就是某种商品从始至终库存量一直为0,需要把这类商品单独处理,也就是ans
最后的答案就是给定区间内所有库存量为1的商品数量加上库存量一直为0的商品数量
*/
const int N=300010;
int a[N],b[N],l[N],r[N];
int st[N],cnt[N];
int n,m;
void insert(int l,int r)
{
a[l]+=1;
a[r+1]-=1;
}
int main()
{
int ans=0;
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>l[i]>>r[i];
for(int i=1;i<=m;i++) insert(l[i],r[i]);
for(int i=1;i<=n;i++) b[i]=b[i-1]+a[i];
for(int i=1;i<=n;i++) if(b[i]==1) st[i]=1;
for(int i=1;i<=n;i++) if(b[i]==0) ans++;
for(int i=1;i<=n;i++) cnt[i]=cnt[i-1]+st[i];
for(int i=1;i<=m;i++) cout<<cnt[r[i]]-cnt[l[i]-1]+ans<<endl;
return 0;
}
27.迷宫(BFS 反向搜图)
#include <iostream>
#include <vector>
#include <queue>
#include <iomanip>
#include <cstring>
using namespace std;
/*
思路:从终点开始反向bfs,第一次到达某点的距离就是最短的距离
*/
const int N=2010;
typedef pair<int,int> PII;
int dist[N][N];
bool st[N][N];
int n,m,ans;
vector<PII> door[N][N];
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
void bfs()
{
queue<PII> q;
q.push({n,n});
st[n][n]=true;
while(q.size())
{
auto tmp=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int a=tmp.first+dx[i];
int b=tmp.second+dy[i];
if(a>=1&&a<=n&&b>=1&&b<=n&&!st[a][b])
{
q.push({a,b});
st[a][b]=true;
dist[a][b]=dist[tmp.first][tmp.second]+1;
}
}
for(auto s:door[tmp.first][tmp.second])
{
if(!st[s.first][s.second])
{
q.push({s.first,s.second});
st[s.first][s.second]=true;
dist[s.first][s.second]=dist[tmp.first][tmp.second]+1;
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
door[x1][y1].push_back({x2,y2});
door[x2][y2].push_back({x1,y1});
}
memset(st,0,sizeof(st));
memset(dist,0,sizeof(dist));
bfs();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
ans+=dist[i][j];
cout<<fixed<<setprecision(2)<<ans*1.0/(n*n)<<endl;
return 0;
}
28.最少刷题数(二分+前缀和)
#include <iostream>
using namespace std;
const int N=100010;
/*
思路:用cnt数组来维护从1到N刷题数对应的学生数量
当一个学生再刷题的数量大于0时,比他小的应该是s[刷题数-1]-s[0-1]-1
这里s[0-1]是因为学生的刷题数有可能为0,不能把这种情况忽略掉,而s[-1]正常不存在,即为0
而再减去1是因为增加刷题数变为新的刷题数后,要减去自己原来的刷题数(原来的一定比现在的小)
而当一个学生再刷题的数量为0时,则不需要减这个1
*/
int a[N],cnt[N],s[N];
int n;
int max_d=-1;
bool check(int mid,int index)
{
int tmp=a[index]+mid;
if(mid==0){
if(s[100000]-s[tmp+1-1]<=s[tmp-1]) return true;
return false;
}
else{
if(s[100000]-s[tmp+1-1]<=(s[tmp-1]-1)) return true;
return false;
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
cnt[a[i]]++;
max_d=max(max_d,a[i]);
}
s[0]=cnt[0];
for(int i=1;i<=N-1;i++) s[i]=s[i-1]+cnt[i];
for(int i=1;i<=n;i++)
{
int l=0,r=max_d-a[i];
while(l<r)
{
int mid=(l+r)/2;
if(check(mid,i)) r=mid;
else l=mid+1;
}
cout<<l<<" ";
}
return 0;
}
29.保险箱(动态规划)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
/*
思路:
先看这道题的一些性质:操作顺序不影响结果。对于某一位操作时,不会影响右边数位的结果。
对于某一位操作次数只在[-9,+9]之间,而且最多进一位,并且最多借一位。
也就是说,某一位经过操作后,跟当前位要么相等,要么多10(借了一位),要么少10(进了一位) 即(-10,0,10)三种情况
分析DP,用f[i][j]来表示,i代表从i到n的数位都已经相等,j表示向前一位进位,借位还是直接操作后就能得到
也就是说,j代表的是对下一位进位方案是j(-1,0,1)的所有方案的集合
属性:最小操作次数
从右往左是从n-1到0,0代表最高位,因此答案为f[0][-1],f[0][0],f[0][1]中的最小值
但是在写程序的时候由于下标不存在-1,所以给每个下标都加上1个单位偏移量,变为0,1,2
集合划分方法:以操作次数来划分,将集合分成若干个子集,即从-9到+9,每个数代表一个子集
对每个子集还需要枚举上一位的进位(或者借位或者不变)是什么,想让a[i]变成b[i],需要让a[i]+操作次数+进位
这样才得到最后的b[i],每哥子集再划分成三类,分别表示上一位的进位是1,上一位的进位是0,上一位的进位是-1(借位)
所以需要满足a[i]+操作数k+上一位的进位t-要变成的数b[i]==j*10,才说明这个集合存在,f[i][j]才会被更新
更新条件就是前一位(第i+1位)进位是t的状态f[i+1][t]+|k|(这一次的操作数)
时间复杂度3n*19*3
*/
const int N=100010;
int n;
char a[N],b[N];
int f[N][3];
int main()
{
scanf("%d%s%s",&n,a,b);
memset(f,0x3f,sizeof f);
f[n][1]=0; //代表个位没有进位,1这里正常是0,代表没有进位,但由于加1个单位偏移量,所以是1
for(int i=n-1;i>=0;i--)
for(int j=0;j<3;j++)
for(int k=-9;k<=9;k++)
{
if(a[i]+k+t-1-b[i]==(j-1)*10)
f[i][j]=min(f[i][j],f[i+1][t]+abs(k));
}
printf("%d",min({f[0][0],f[0][1],f[0][2]}));
return 0;
}
30.求阶乘(二分+数学)
#include <iostream>
using namespace std;
/*
思路:由于N!数值过大,所以不可能通过计算出N!具体的值来数出末尾的0
经发现可得,想凑出0,分解质因子后,只能由2和5这两个质数来凑出一个10,包含0
也就是说,每个数的因子中有可能包含2和5,只需要找出N能凑出多少对2和5,就有多少个0
每个数的因子中含有2的可能性大于含有5的可能性,因此只需要找出1到N中每个数可以被拆成几个5就行
最后将所有个数相加得到总和,即为末尾有多少个0
但是这样做时间复杂度为O(N),题中需要的时间复杂度为O(logN),所以还需要快速判断的方法
可以通过直接统计5的个数的方式来进行判断
long long是10^19
*/
typedef long long ll;
ll k;
ll check(ll mid)
{
ll res=0;
while(mid)
{
res+=mid/5;
mid/=5;
}
return res;
}
int main()
{
cin>>k;
ll l=0,r=1e19;
while(l<r)
{
ll mid=(l+r)/2;
if(check(mid)>=k) r=mid;
else l=mid+1;
}
if(check(l)==k) cout<<l<<endl;
else cout<<-1<<endl;
return 0;
}
31.赢球票(模拟 ,DFS+队列会超空间)
#include <iostream>
#include <cstring>
using namespace std;
/*虽然标签写的搜索,但就是大模拟*/
const int N=110;
int a[N];
bool st[N];
int max_ans;
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
memset(st,0,sizeof st);
int pos=i; //代表数组的第几个位置,不代表上面的数
int cnt=1; //数的数
int tmp=0; //总和
int cards=0; ///卡片数
while(1)
{
if(cnt>n||cards==n) break;
if(!st[pos]) //如果这个数没被用过
{
if(cnt==a[pos])
{
cnt=1;
tmp+=a[pos];
cards++;
st[pos]=true;
pos++;
if(pos==n+1) pos=1;
}
else
{
cnt++;
pos++;
if(pos==n+1) pos=1;
}
}
else
{
pos++;
if(pos==n+1) pos=1;
}
}
max_ans=max(max_ans,tmp);
}
cout<<max_ans<<endl;
return 0;
}
32.修路(状态机DP,线性DP)
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=2010;
double a[N],b[N];
double f[N][N][2]; //0表示停在A这边 1表示停在B这边
int n,m,d;
double dis(double a,double b)
{
return sqrt((ll)(a-b)*(a-b)+(ll)d*d); //注意负负得正
}
int main()
{
cin>>n>>m>>d;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++) cin>>b[i];
sort(a+1,a+n+1);
sort(b+1,b+m+1);
//初始化
for(int i=1;i<=n;i++) f[i][0][0]=a[i],f[i][0][1]=0x3f3f3f3f;
for(int i=1;i<=m;i++) f[0][i][0]=f[0][i][1]=0x3f3f3f3f;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
//最后停在A这边,有两种情况,一种是从i-1这个点过来的(一定在A这边),另外一种是从j斜跨过来的(一定在B这边)
f[i][j][0]=min(f[i-1][j][0]+a[i]-a[i-1],f[i-1][j][1]+dis(a[i],b[j]));
//最后停在B这边,有两种情况,一种是从j-1这个点过来的(一定在B这边),另外一种是从i斜跨过来的(一定在A这边)
f[i][j][1]=min(f[i][j-1][1]+b[j]-b[j-1],f[i][j-1][0]+dis(a[i],b[j]));
}
printf("%.2lf",min(f[n][m][0],f[n][m][1]));
return 0;
}
33.2022(01背包)
#include <iostream>
using namespace std;
/*
思路:相当于一共有2022个物品,编号从1到2022,每个物品的价值就是它自己的编号
设置三维状态f[i][j][k],表示从前i个物品中选重量为j的价值为k的所有方案的集合,j不超过10,每个物品的重量是1
由于要求互不相同,因此属于01背包问题,只有选和不选两种情况
如果不选的话,f[i][j][k]=f[i-1][j][k]
如果选的话,f[i][j][k]=f[i-1][j-1][k-i] 因为物品的编号i就代表物品的价值,而且k要大于等于i,否则下标越界
*/
long long f[2023][11][2023];
int main()
{
for(int i=0;i<=2022;i++) f[i][0][0]=1; //初始化 体积为0且选0个物品只有一种情况那就是什么也不选
for(int i=1;i<=2022;i++) //枚举每个物品
{
for(int j=1;j<=10;j++) //枚举选了几个物品
{
for(int k=0;k<=2022;k++) //枚举价值
{
f[i][j][k]=f[i-1][j][k];
if(k>=i) f[i][j][k]+=f[i-1][j-1][k-i];
}
}
}
cout<<f[2022][10][2022]<<endl;
return 0;
}
34.李白打酒加强版(动态规划)
#include <iostream>
using namespace std;
/*
思路:将时间复杂度控制在O(n^3)之内
f[i][j][k]代表遇到i个店,j朵花,有k斗酒的方案数的集合
这里在枚举K的时候要注意,k的值不能超过花的数量,这是这道题的关键,如果超过花的数量,那一定喝不完
遇到店:f[i][j][k]+=f[i-1][j][k/2] 前提k要能够整除2
遇到花:f[i][j][k]+=f[i][j-1][k+1]
答案为f[N][M-1][1] 因为题目中说最后一次遇到的是花且刚好把酒喝完
*/
const int N=110,MOD=1e9+7;
int f[N][N][N];
int main()
{
int n,m;
cin>>n>>m;
f[0][0][2]=1;
for(int i=0;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=0;k<=m;k++)
{
if(i>=1&&k%2==0) f[i][j][k]=(f[i][j][k]+f[i-1][j][k/2])%MOD;
if(j>=1) f[i][j][k]=(f[i][j][k]+f[i][j-1][k+1])%MOD;
}
}
}
cout<<f[n][m-1][1]<<endl;
return 0;
}
35.买二赠一(贪心,STL)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
/*
思路:每个物品只能被获得一次,需要用st数组来记录,由于大价格的物品肯定需要被购买而不是免费获得,所以将数组从大到小排序
每买两个物品,就检查一次会不会免费获得物品,这里用count来控制,然后使用lower_bound函数,找到第一个不大于P/2的元素
如果这个商品已经被买过或者已经被免费获得过一次,那么就找它的下一个元素,直到找到符合条件的未被买过的元素
lower_bound函数正常是找到第一个不小于给定值的数组元素,但由于倒序排列,变成找第一个不大于给定值的数组元素
*/
const int N=500010;
int main()
{
int n=0;
long long sum=0;
int a[N];
bool st[N];
int count=0;
memset(st,0,sizeof st);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1,greater<int>()); //倒序排列
for(int i=1;i<=n;i++)
{
if(!st[i])
{
sum+=a[i];
count++;
if(count==2)
{
count=0;
int pos=lower_bound(a+1,a+n+1,a[i]/2,greater<int>())-a;
while(st[pos]) pos++;
st[pos]=1;
}
}
}
cout<<sum<<endl;
return 0;
}
36.背包与魔法(01背包)
#include <iostream>
#include <cstring>
using namespace std;
/*
思路:01背包需要进行一维优化,然后用0或1来表示是否施展过魔法,这样开f[N][2]这么大的数组就足够
正常集合划分为选或者不选,在选这一栏里又可以分为不翻倍的选和翻倍的选
0表示之前没有施展过魔法 1表示之前施展过魔法 并且只有背包体积j大于等于v[i]+k时,这次才会有施展魔法的可能性
由于01背包进行了降维,所以直接把不选的情况给优化掉了
*/
typedef long long ll;
const int N=10010;
int f[N][2];
int v[N],w[N];
int main()
{
int N,M,K;
cin>>N>>M>>K;
for(int i=1;i<=N;i++) cin>>v[i]>>w[i];
for(int i=1;i<=N;i++)
for(int j=M;j>=v[i];j--)
{
f[j][0]=max(f[j][0],f[j-v[i]][0]+w[i]);
f[j][1]=max(f[j][1],f[j-v[i]][1]+w[i]);
if(j>=K+v[i]) f[j][1]=max(f[j][1],f[j-v[i]-K][0]+2*w[i]);
}
int ans=0;
ans=max(f[M][0],f[M][1]);
cout<<ans<<endl;
return 0;
}
37.子串简写(前缀和)
#include <iostream>
using namespace std;
/*
思路:用一个数组a来表示在这个字符之前有多少个开始字符,用a数组来统计
然后遍历串,如果发现当前字符是结束字符,那么只需要知道在这个字符k个单位之前有多少个开始字符,就有多少个答案
由于a数组做了这样的一个功能,所以a数组i-k+1存放的就是代表在这个字符之前有多少个开始字符,加上即可
*/
const int N=500010;
int a[N];
int main()
{
int k=0,num=0;
string s;
char st,ed;
cin>>k>>s>>st>>ed;
for(int i=0;i<s.size();i++)
{
if(s[i]==st) num++;
a[i]=num;
}
long long ans=0;
for(int i=0;i<s.size();i++)
{
if(i>=k-1&&s[i]==ed) ans+=a[i-k+1];
}
cout<<ans<<endl;
return 0;
}
38.最少砝码(贪心)
#include <iostream>
#include <cmath>
using namespace std;
/*
思路: 当只有1个砝码的时候,重量区间为[1,1] 只能表示重量为1的物品
当有2个砝码的时候,上一次表示不了的重量是2,所以这一次必须要满足的一个条件是:
选的第二个砝码和第一个砝码的组合能表示出来2,所以可选的有1,2,3 因为要找最小砝码数来表示最大区间
所以我们第二个砝码选择重量为3 ,区间为[1,4](不能选4,一旦选4,则凑不出来2了)
当有三个砝码时,上一次凑不出来的数是5,所以这一次选的数要能够凑出来5 可选的数为1-9中任意一个数
不能选10 如果选10 则凑不出来5 最小能表示的是6(10-3-1=6)因为还要选最大的区间
所以第三个砝码选9 可以表示出[1,13]内任意一个数
可以看出 选砝码的规律为 1,3,9,..,3^k..
而能表示的区间的右端点正好是它们的和,也就是说 只要右端点大于目标值 就代表已经找到
*/
int main()
{
long long sum=0;
int n=0,cnt=0;
cin>>n;
while(1)
{
if(sum>=n) break;
sum+=pow(3,cnt);
cnt++;
}
cout<<cnt<<endl;
return 0;
}
39.R格式(高精度乘法,高精度加法)
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
void mul(vector<int>& a,int b)
{
int t=0;
for(int i=0;i<a.size();i++)
{
t=a[i]*b+t;
a[i]=t%10; //因为这里低精度数b只有一位数,所以可以直接这么写,不需要另外一个vector
t/=10;
}
if(t) a.push_back(t);
}
void add(vector<int>& a,int pos,int b)
{
int t=b;
for(int i=pos;i<a.size();i++)
{
t=t+a[i];
a[i]=t%10; //同样也是因为b只有一位数,才可以这么做 否则还需要再开一个vector处理
t/=10;
}
if(t) a.push_back(t);
}
int main()
{
int n;
string d;
cin>>n>>d;
reverse(d.begin(),d.end());
vector<int> A;
int pos=d.find('.'); //记录下小数点的位置,便于最后四舍五入时候用
for(int i=0;i<d.size();i++)
if(d[i]!='.') A.push_back(d[i]-'0');
while(n--) mul(A,2); //这步处理d*(2^n) 将浮点数看成一个高精度数 记录下小数点的位置 然后正常高精度乘法做
if(A[pos-1]>=5) add(A,pos,1); //因为pos-1正好是小数点应该在的位置的前一位数,只需要从后面开始处理即可 所以刚好是pos
for(int i=A.size()-1;i>=pos;i--) printf("%d",A[i]);
return 0;
}
40.填充(贪心)
#include <iostream>
using namespace std;
/*
思路:如果字符相同或者有一个为?,则直接cnt++,并跳过这个字符
相当于跳着跳着找
*/
int main()
{
int cnt=0;
string s;
cin>>s;
for(int i=0;i<s.size()-1;i++)
{
if(s[i]==s[i+1]||s[i]=='?'||s[i+1]=='?')
{
cnt++;
i++;
}
}
cout<<cnt<<endl;
return 0;
}