文章目录
时间复杂度
在竞赛中,一般计算一秒能运行5 * 10 ^ 8次计算, 一般 算法数据范围:
O(n) : n < 10 ^ 8;
O(n logn) : n <= 10 ^ 6;
O(n sqrt(n)) n < 10 ^ 5;
O(n ^ 2): n < 5000;
O(n ^ 3): n < 300
O(2 ^ n): n < 25;
O(n!): n < 11
搜索00
dfs
精髓:递归。
- 要用到递归:参数的传入和return退出本次递归
- dfs中循环的变量i的含义(表示填入的行数?填入的数字?)及其中现场的还原
- 对角线 dg[i + x] , 反对角线udg[u - x + i]的标记。两者开的大小为 2 * n。
- 记录路径的数组,根据所求答案的性质而来。
bfs
精髓:队列。
- 方向数组 dx[] = {}, dy[] = {};
2.难免用到队列,结构体(存储的坐标、步数等);
typedef pair <int, int> PII; PII s[N * N] , int x = s[i].first + dx[i], y = s[i].second + dy[i]; - 有的时候,我们不必开一个book数组判断该点走没走过。直接一个d[x][y]初始化全部为-1, 当走过时,更新为走的步数。
- 很重要的一个问题: 判断是否出界。
- 关于八数码问题:
字符串存储状态,map存储的为字符串及对应的步数。
寻找目标值x, 调用字符串中find函数,int k = t.find(‘x’)如何转化为坐标? int i = k / 3, j = k % 3;
主义的一个细节: 循环为4次(上下左右)每次都需要还原回去状态,使得对上下左右是公平的。
搜索
与素数相结合
题目大意:给出两个四位数的素数,求a变换到b需要几步,要求变幻时只有一个数字不同,并且是素数。
【直接自敲】小编上来直接bfs,错因(1)prime是质数的意思,大意了啊,直接bfs;(2)脑子是个好东西。。我直接两重循环 记录偏移量的数组dn[] = {1, 10, 100, 1000}, j为 [0,9], 每次原数增加dn[i] * j, 违背了只改变一个数如 1230 + 80 = 1310, 改变了两个数了。。
【正确思路】
- 仔细审题,不合法时,输出impossible,审题不细
- 打印一个素数表,判断是否为素数直接count(n)即可。
- 针对第二个问题:我们开一个数组,记录这四个数字。
细节
4. 第高位不能为0, 每次必须只改变一个数, 注意写bfs时continue的写法
5. 两者相等时,直接输出 “0”, 需要特判。
6. t[j] = tmp, 四位数,当我们讨论完任意一个,继续讨论下一个时,我们要把之前讨论的还原回去。
7. 判断该数是不是为质数,小编用的筛法并把所有质数加到一个map中, mp.insert({i, 1}), mp.count(i)即可。但我们也可开一个数组st, st[i] = true or false, 直接判断该数是不是质数
代码
大家闲的时候可以看看,解解闷。复习一下bfs, emmm
#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
#include <map>
using namespace std;
const int N = 10010;
int n, m;
int book[N];
int t[4];
int primes[N];
struct f{
int x;
int s;
};
queue <f> qu;
map <int, int> mp;
int bfs(void){
memset(book, 0, sizeof(book));
while(qu.size()) qu.pop();
int tmp;
struct f a;
a.x = n;
a.s = 0;
qu.push(a);
book[n] = 1;
while(!qu.empty()){
struct f b = qu.front();
qu.pop();
t[0] = b.x / 1000;
t[1] = b.x % 1000 / 100;
t[2] = b.x % 100 / 10;
t[3] = b.x % 10;
for(int j = 0; j < 4; j ++){
tmp = t[j];
for(int i = 0; i < 10; i ++){
if(i == tmp) continue;
if(j == 0 && i == 0) continue;
t[j] = i;
struct f c;
c.x = t[0] * 1000 + t[1] * 100 + t[2] * 10 + t[3];
c.s = b.s + 1;
if(book[c.x] == 0 && mp.count(c.x)){
book[c.x] = 1;
qu.push(c);
if(c.x == m){
return c.s;
}
}
}
t[j] = tmp;
}
}
return -1;
}
int main(void){
int t;
cin >> t;
int cnt = 0;
for(int i = 2; i <= 10000; i ++){
if(!book[i]){
mp.insert({i, 1});
primes[cnt ++] = i;
}
for(int j = 0; primes[j] <= 10000 / i; j ++){
book[primes[j] * i] = 1;
if(i % primes[j] == 0) break;
}
}
// printf("%d\n", cnt);
while(t --){
cin >> n >> m;
if(n == m){
cout << "0" << endl;
continue;
}
int ans = bfs();
if(ans == -1) cout << "Impossible" << endl;
else cout << ans <<endl;
}
return 0;
}
双起点搜索
题目描述:
两人在n*m的平地上同时放火,’#‘表示草,’.’ 表示石头, 两人分别选一个’#'格子点火,火可以向上向下向左向右在有草的格子蔓延,点火的地方时间为0,蔓延至下一格的时间依次加一。求烧完所有的草需要的最少时间。如不能烧完输出-1。
思路
- 找出所有草地的坐标, 存储到一个结构体数组中,两两组合枚举。
- 双起点bfs跟普通的差不大,只是在加入起点时,加入两个。其他没有不同
- 判断每组情况烧没烧完,在bfs’返回后,设置一个book数组:
if(g[i][j] == '#' && book[i][j] == 0)
则这种组合烧法不合题意。 - 如何判断有无解,设置一个flag, 有符合题意的解,就设置为1,最后判断flag为1, 还是0即可。
Fire and run out ?
题目大意:
一个人要从火场里出来,火的起点“F”可能有多个,但人“J”只有一个,火可以上下左右蔓延,人不能走墙“#”,不能走火;问人是否能出来,最少多久
题目分析
多起点bfs, 人和火需要一起讨论(bfs压进队列,完美解决该问题),但又要区分人和火(可用两个数组标记人火走没走过的情况, 如下代码 b[2][100000]
, 第一层下标为0表示火走过的, 第二层下标为1表示人走过的, 同时, 在用来表示位置、时间的结构体里也应该设置一个no,0表示火,1表示人, 分别讨论 )
代码亮点:
bfs函数中,表示设置一个flag变量,
当循环完毕后, 判断flag是否为0, 便知有无解输入时,直接找出了所有火的位置,以便同时传入bfs中队列里
·最新鲜的是, 设置一个变量 no表示 1表示人, 0 表示火
, 这要的讨论,写法,很棒!!- 注意:先把火压进队列,在压进去人。
【放下代码。 有兴趣的小伙伴们可以看看哦】
#include <iostream>
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int r, c;
char mp[N][N];
struct node{
int x, y, time, no;
}fire[1000009];
void bfs(int jx, int jy, int I){
bool b[2][1010][1010];
memset(b, 0, sizeof(b));
queue <struct node> que;
struct node t, tt;
for(int i = 1; i <= I; i ++){
t = fire[i];
b[0][t.x][t.y] = true;
que.push(t);
}
t.y = jy, t.x = jx, t.time = 0, t.no = 1;
que.push(t);
b[1][t.x][t.y] = true;
int d[4][2] = {1, 0, -1, 0, 0, 1, 0, -1};
bool flag = false;
while(!que.empty()){
t = que.front();
if(t.no == 1 && (t.x == r || t.x == 1 || t.y == c || t.y == 1)){
flag = true;
break;
}
que.pop();
for(int i = 0; i <= 3; i ++){
tt.x = t.x + d[i][0];
tt.y = t.y + d[i][1];
tt.time = t.time + 1;
tt.no = t.no;
if(tt.x < 1 || tt.x > r || tt.y < 1 || tt.y > c) continue;
if(tt.no == 1){
if(b[1][tt.x][tt.y] == 0 && b[0][tt.x][tt.y] == 0 && mp[tt.x][tt.y] == '.'){
que.push(tt);
b[tt.no][tt.x][tt.y] = true;
}
}
if(tt.no == 0){
if(b[0][tt.x][tt.y] == 0 && mp[tt.x][tt.y] == '.'){
que.push(tt);
b[tt.no][tt.x][tt.y] = true;
}
}
}
}
if(flag){
printf("%d\n", t.time + 1);
}
else printf("IMPOSSIBLE\n");
}
int main(void){
int t, jx, jy;
scanf("%d", &t);
while(t --){
scanf("%d%d", &r, &c);
getchar();
int I = 0;
for(int i = 1; i <= r; i ++){
for(int j = 1; j <= c; j ++){
scanf("%c", &mp[i][j]);
if(mp[i][j] == 'J'){
jx = i, jy = j;
}
else if(mp[i][j] == 'F'){
fire[++ I].y = j;
fire[I].x = i;
fire[I].time = 0;
fire[I].no = 0;
}
}
getchar();
}
bfs(jx, jy, I);
}
return 0;
}
dfs解决连通问题
问题描述:
就是找出有多少块有石油的区域,石油对应数组中的@,这边相邻指的是是周围的八个位置。
细节
dfs函数中,如果该点是石油(@), 我们对他周围的八个点都要进行dfs处理(因为连通块不一定是规则的图形)
【放下代码,主要是复习dfs】
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 110;
int d[8][2] = {-1, 0, -1, 1, 0, 1, 1, 1, 1, 0, 1, -1, 0, -1, -1, -1};
int m, n, flag;
char str[N][N];
void dfs(int x, int y){
if(str[x][y] == '*') return ;
flag = 1;
str[x][y] = '*';
int dx, dy;
for(int i = 0; i < 8; i ++){
dx = x + d[i][0];
dy = y + d[i][1];
if(dx < 0 || dx >= m || dy < 0 || dy >= n) continue;
dfs(dx, dy);
}
return ;
}
int main(void){
while(1){
scanf("%d%d", &m, &n);
int num = 0;
if(m == 0) return 0;
for(int i = 0; i < m; i ++) scanf("%s", str[i]);
for(int i = 0; i < m; i ++){
for(int j = 0; j < n; j ++){
flag = 0;
dfs(i, j );
if(flag){
num ++;
}
}
}
printf("%d\n", num);
}
}
双起点(2)
题目大意:
两人’Y’, 'X’从各自家出发, 前往同一家奶茶店‘@’, “#”表示墙, 求两人到达奶茶店的最小步数和(可走上下左右四个方向, 地图上每走一步,人需要走11步)。
思路
对每个人搜索一次, 数组 w[i][j]
记录两人到达@的步数和, 最后扫一遍数组,找到最小值即可。
一个小细节: 题目说至少有一组解, 说明每个人都可以走到对方家,则,Y能到达的奶茶店,X也能到达, 反之,亦然。
代码亮点
- 输入: 直接找到两人位置, 找到奶茶店数量。
- 找到奶茶店数量,进行bfs时,进行剪枝操作(使while循环早点结束)
#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int N = 210;
int n, m, num;
char str[N][N];
int book[N][N];
int w[N][N];
int d[4][2] = {-1, 0, 0, 1, 1, 0, 0, -1};
struct pp{
int x, y, step;
};
queue <pp> que;
void bfs(int x, int y){
while(que.size()) que.pop();
memset(book, 0, sizeof(book));
struct pp q;
int numm = 0;
q.x = x, q.y = y, q.step = 0;
que.push(q);
while(!que.empty()){
q = que.front();
que.pop();
struct pp q2;
for(int i = 0; i < 4; i ++){
q2.x = q.x + d[i][0];
q2.y = q.y + d[i][1];
q2.step = q.step + 1;
if(q2.x < 0 || str[q2.x][q2.y] == '#' || q2.x >= n || q2.y < 0 || q2.y >= m || book[q2.x][q2.y] == 1) continue;
book[q2.x][q2.y] = 1;
que.push(q2);
if(str[q2.x][q2.y] == '@'){
numm ++;
w[q2.x][q2.y] += q2.step * 11;
if(numm == num) break;
}
}
}
}
int main(void){
while(scanf("%d%d", &n, &m) != EOF){
memset(w, 0, sizeof(w));
int yx, yy, mx, my;
num = 0;
for(int i = 0; i < n; i ++){
scanf("%s", str[i]);
for(int j = 0; j < m; j ++){
if(str[i][j] == 'Y') yx = i, yy = j;
else if(str[i][j] == 'M') mx = i, my = j;
else if(str[i][j] == '@') num ++;
}
}
bfs(yx, yy);
bfs(mx, my);
int ans = 0x3f3f3f3f;
for(int i = 0; i < n; i ++){
for(int j = 0; j < m; j ++){
if(w[i][j] == 0) continue;
ans = min(ans, w[i][j]);
}
}
printf("%d\n", ans);
}
}
最短路
注意点
- 注意所求点时谁,曾记否:青蛙在编号为2的点,自己wa了好几回,才发现。
- 明确自己想要求得问题,进而找解决的方法,套模板时,要紧紧关注数组含义,更新时得条件,赋值等。
细节题1
N头母牛在N个牛场,都要前往编号为X的牛场,共m条路,都为单向路,每头牛都会走用时最短的那条路。求用时最长的那头牛花费的时间。
细节(敲重点)
- 我们不能用floyd,因为数据量较大,时间超时
- 我们选择Dijkstra算法,我们求某点k到n,还得求n到k的最短距离,这里我们便用到一个逆矩阵的思想。如我们从 k到 x 需要
g[k][x]
的距离,我们把g[x][k]
等于该值,这时我们便可转化为求x到k点的最短距离。(可能不太好理解:从k点经过 g点 到 x点的距离,这时我们把g到k点的距离d3 = k到g点的距离d1 , x到g点的距离d4 = g点到x点的距离d2, 那么我们的g点走到x点的距离 = x点到g点的距离)
我们必须得把整个数组都逆一下,这样才能保证我们用到的路径都是一个方向的
dijkstra(x);
for(int i = 1; i <= n; i ++){
for(int j = i + 1; j <= n;j ++){
int tmp = g[i][j];
//g[i][j] = g[j][i];
g[i][j] = g[j][i];
g[j][i] = tmp;
}
}
dijkstra2(x);
- 我们写的用的dis数组:由于我们求得是某头牛的时间,那么就得用两个数组来记录,一个记录去时花费的最短时间,一个记录回时的最短时间, 只有两者相加最大时,才是我们所求的答案。(痛哭流涕:错因1)
- 在写dijkstra函数时,我们有一个细节
if(t == -1) break
(错因二)
void dijkstra2(int x){
for(int i = 1; i <= n; i ++){
dis2[i] = g[x][i];
book[i] = 0;
}
book[x] = 1;
for(int i = 1; i <= n; i ++){
int t = -1;
for(int j = 1; j <= n; j ++){
if(!book[j] && (t == -1 || dis2[t] > dis2[j])) t = j;
}
if(t == -1) break;
book[t] = 1;
for(int j = 1; j <= n; j ++) dis2[j] = min(dis2[j], dis2[t] + g[t][j]);
}
}
Currency Exchange(Bellman-Ford判断正权回路)
题目描述:
多种汇币,汇币之间可以交换,需要手续费。如:当你用100A币交换B币时,A到B的汇率是29.75,手续费是0.39,那么你可以得到(100 - 0.39) * 29.75 = 2963.3975 B币, 问S币的金额经过交换最终得到的S币金额能否增加。
输入:n种货币,m个交换点,nick拥有的货币种类S, nick拥有的货币数量v。
分析
- 货币的交换是可以重复多次的,所以我们需要找出是否存在正权回路
正权回路:在这一回路上,顶点的权值能不断增加, 即能一直进行松弛
- 一种货币就是一个点,一个“兑换点”就是图上两种货币之间的一个兑换方式,是双边,但A到B的汇率和手续费与B到A的可能不同。
- 我们只能在两个货币间进行交换,两个货币间可能交换一次,两次甚至多次,但也有可能0次。不存在“绕弯”(A <–> B <-- > C, 但我们不能AC间直接交换),
- 所以我们需要一个while循环,让能让数值变大的两者间一直交换(多换几回),若是不能再变大时,且未超过初始值,我们跳出循环,
#include <iostream>
#include <cstring>
using namespace std;
struct node
{
int a,b;
double r,c;
}mp[250];
int n,m,s,all,a,b;
double v,dis[250];
double rab,cab,rba,cba;
int bell()
{
int i,j;
memset(dis,0,sizeof(dis));
dis[s]=v;
while(dis[s]<=v)//一直处理到钱变多为止
{
int flag=0;
for(j=0;j<all;j++)
{
if(dis[mp[j].b]<(dis[mp[j].a]-mp[j].c)*mp[j].r)//肯定从s点开始
{
dis[mp[j].b]=(dis[mp[j].a]-mp[j].c)*mp[j].r;//不管跟起始点有无关系只要兑换完了面值增大就兑换,等处理到该点时该点则是最大面值
flag=1;
}
}
if(!flag)//都是越兑越小就不换
return 0;
}
return 1;
}
int main()
{ int i,j;
while(cin>>n>>m>>s>>v)
{all=0;
for(i=1;i<=m;i++)
{
cin>>a>>b>>rab>>cab>>rba>>cba;
mp[all].a=a;
mp[all].b=b;
mp[all].r=rab;
mp[all++].c=cab;
mp[all].a=b;
mp[all].b=a;
mp[all].r=rba;
mp[all++].c=cba;
}
if(bell())
cout<<"YES"<<endl;
else cout<<"NO"<<endl;}
return 0;
}
Bellman_Ford判断负权回路
题目大意:
T组数据, 每组n个农场, m条正权边, w条负权边, 给出m + w个边的起始点s和终止点e和花费时间t,求是否可能走完任意条边时可以使时间回到最开始之前。
基本内容补充
- 回路包括:
正权回路:回路权值之和为正
负权回路: 回路权值之和为负
如果最短路径种包含正权回路,那么去掉这个回路,一定可以得到更短的路径。如果最短路径中包含负权回路, 那么必然无最短路径
因为每多走一次负权回路就可以得到更短的路径. 因此最短路径肯定是一个不包含回路的最短路径,即最多包含n-1条边。
- 我们在 n - 1次松弛后, 如果依然可以继续松弛成功,那么必然存在负环。
关键代码
for(k=1;k<=n-1;k++) //外循环循环n-1次,n为顶点个数
for(i=1;i<=m;i++)//内循环循环m次,m为边的个数,即枚举每一条边
if(dis[v[i]]>dis[u[i]]+w[i])//尝试对每一条边进行松弛,与Dijkstra算法相同
dis[v[i]]=dis[u[i]]+w[i];
//检测负权回路
flag=0;
for(i=1;i<=m;i++)
if(dis[v[i]]>dis[u[i]]+w[i])
flag=1;
if(flag==1)
printf("此图有负权回路");
【仔细看如下代码:对bellman_frod函数里的循环有个优化,即不一定要进行n-1次循环】
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 5500;
int k; //记录一共有多少有向边
typedef struct edge{
int u, v; //起点u, 终点v
int w;
}Edge;
Edge edge[N]; //保存边的值
int dis[N]; //结点道原点的最小距离
int n, m, w, s;
int bellman_ford(){
memset(dis, 0x3f, sizeof(dis));
dis[1] = 0;
int check = 0;
for(int i = 1; i <= n - 1; i ++){
check = 0;
for(int j = 1; j < k; j ++){
if(dis[edge[j].v] > dis[edge[j].u] + edge[j].w){
check = 1;
dis[edge[j].v] = dis[edge[j].u] + edge[j].w;
}
}
if(check == 0) break; //优化,因为最短路至多为n条边, 但有可能不到n
}
int flag = 1;
for(int i = 1; i < k; i ++){
if(dis[edge[i].v] > dis[edge[i].u] + edge[i].w){
flag = 0;
break;
}
}
return flag;
}
int main(void){
int t;
scanf("%d", &t);
int s, e, v;
while(t --){
scanf("%d%d%d", &n, &m, &w);
k = 1;
for(int i = 1; i <= m; i ++){
scanf("%d%d%d", &s, &e, &v);
edge[k].u = s, edge[k].v = e, edge[k ++].w = v;
edge[k].u = e, edge[k].v = s, edge[k ++].w = v;
}
for(int i = 1; i <= w; i ++){
scanf("%d%d%d", &s, &e, &v);
edge[k].u = s, edge[k].v = e, edge[k ++].w = -v;
}
int ans = bellman_ford();
if(ans) cout << "NO" << endl;
else cout << "YES" << endl;
}
return 0;
}
一个新鲜的输入问题
简单描述:
地图上有n个点, 第一行输入n。输入的其他部分定义了一个邻接矩阵a(大小 n * n),每个条目都将是一个整数或字符 x, a(i, j)表示的是从结点i 向 结点 j 发送消息的费用, j向i的费用与其相同; a(i, i) = 0, 所以我们只输入a的下三角形部分即可(因为对称)
。求第一个点向其他点传入最短时间的最大值。
主要见输入的如下处理:
atoi
的应用。该代码对应第一个点的坐标是 0, 0,即对应dis[0]
while(scanf("%d", &n) != EOF){
char s[3];
for(int i = 0; i < n; i ++){
g[i][i] = 0;
for(int j = 0; j < i; j ++){
cin >> s;
if(s[0] != 'x') g[i][j] = g[j][i] = atoi(s);
else g[i][j] = g[j][i] = 0x3f3f3f3f;
}
}
cout << dijkstra() << endl;
}
Floyd 的包装1
题目大意: n头牛比赛, 给出m场两两比赛获胜方,问有几头牛可以确定名次(A打败B, B打败C, 则A打败C),
将标记数组book改为计数数组, 当book[i] = k , 即认为 第i头牛打了k场比赛, 当k = n - 1 时,我们即可确定这头牛的名次
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 105;
int dis[N], book[N];
int g[N][N];
int n, m;
int main(void){
while(scanf("%d%d", &n, &m) != EOF){
memset(book, 0, sizeof(book));
memset(g, 0, sizeof(g));
for(int i = 1; i <= m; i ++){
int a, b;
scanf("%d%d", &a, &b);
g[a][b] = 1;
book[a] ++;
book[b] ++;
}
int num = 0;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++){
for(int k = 1; k <= n; k ++){
if(i == j || j == k || i == k) continue;
if(!g[j][k] && g[j][i] && g[i][k]){
g[j][k] = 1;
book[j] ++;
book[k] ++;
}
}
}
}
for(int i = 1; i <= n; i ++){
if(book[i] == n - 1) num ++;
}
cout << num << endl;
}
return 0;
}
二分
跳石头(贪心+二分转换)
题目大意:
组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。
输入:
第一行包含三个整数 L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。
接下来 N 行,每行一个整数,第 i 行的整数 Di(0<Di<L)表示第 i 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同 一个位置。
输出:
输出文件只包含一个整数,即最短跳跃距离的最大值。
思路
- 求移走M块石头后最短距离的最大值。
贪心
的 策略:找到一个dist, 使移走的的最短距离的最小值不大于dist。 - 当我们的中间值mid满足check时,试着往右找最大值,所以变化的区间为
l = mid
- 当中间值不满足check时,说明这个值太大了,挪走的石头数量比题意多,自然要
r = mid - 1
- 细节
mid = (l + r + 1) / 2
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 50010;
long long arr[N], l, m, n;
bool check(long long mid){
long long res = 0, last = 0;
for(int i = 1; i <= n; i ++){
if(arr[i] - last < mid) res ++; //当小于mid时,我们挪走该石头,此时last并未改变
else last = arr[i];
}
return res <= m;
}
int main(void){
scanf("%lld%lld%lld", &l, &n, &m);
for(int i = 1; i <= n; i ++) scanf("%lld", &arr[i]);
arr[++ n] = l;
l = 0;
long long r = 1e9;
while(l < r){
long long mid = (l + r + 1) / 2;
if(check(mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
return 0;
}