一、本周小结
1、本周大摆,暑期时间呆久的心浮气躁,也看到了心性未调整好的结果,多失。
2、本周收益甚微,主要还是图论和线段树,但是碍于畏惧心理过大一直没有大段时间刷题单,看课不刷题等于白学。。。
3、更加需要提高自己迅速灵活的思维水平和扎实牢固且能够考虑周全的模拟能力【小数据多操作,以小数据为基点遍历模拟出答案】,还有很重要的赛时造数据debug能力。对于非算法题,图论与dp基础要打基础,刷题单,见世面。
4、总而言之,八月出头,盛夏晚晴,再续!
5.下周重点:图论,dp,线段树,思维
A.二分or双指针
题目
有n本书,每本用时ai,求m时间内能读的最多的书
思路
1.n<=10^5,,用暴力(O2)超时,用二分(NlogN)
2.用双指针【for(while(条件))】
代码实现:
1.二分
vector<int>a,sum;
void solve()
{
int n, m,ans=0;
cin >> n >> m;
a.resize(n + 1);
sum.resize(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
}
for (int i = 1; i <= n; i++)
{
//以第i+1个点为起点的最长数量
if (a[i] > m)
continue;
//套模板,l为起始下标-1,r为终点下标+1,+1为精度
int l = i, r = n+1;
while (l + 1 < r)
{
int mid = l + r >> 1;
if (sum[mid] - sum[i - 1] > m)
{
r = mid;
}
else
l = mid;
}
ans = max(ans, r - i);
}
cout << ans << endl;
}
2.双指针
模板:【自己总结,暂时只能过两题,不确定性max】
粉色为逻辑
蓝色为代码实现
//指针,与相关条件初始化
int l = 1, r = 1;
(sum,ans)
//while(左或右指针的限制条件)
while (r <= n)
{
//如果满足题目条件,则【更新答案or满足更新条件的操作】,移动指针+移动后【对已有数据影响】的【处理】
if (check())
{
(ans) //更新答案
r++; //指针移动
(vis,cnt,sum) //指针移动造成的已有数据的改变
}
//如果不满足题目条件,移动指针+移动后【对已有数据影响】的【处理】
else
{
l++; //指针移动
(vis,cnt,sum) //指针移动造成的已有数据的改变
}
}
int l = 1, r = 1;
int ans = 0;
while (r <= n)
{
if (check())
{
r++;
}
else
{
l++;
}
}
代码实现:
1.【不用前缀和优化】
const int N = 1e5 + 10;
int a[N];
int n, m;
void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
int ans = 0;
//指针,与相关条件初始化
int l = 1, r = 1;
int sum = a[1];
//while(左或右指针的限制条件)
while (r <= n)
{
//如果满足题目条件,则【更新答案or满足更新条件的操作】,移动指针+移动后【对已有数据影响】的【处理】
if (sum<=m)
{
ans = max(ans, r - l + 1); //【更新答案】
r++; //【指针移动】
sum += a[r]; //【对已有数据的处理】
}
//如果不满足题目条件,则移动指针+移动后【对已有数据影响】的【处理】
else
{
sum -= a[l]; //【对已有数据的处理】
l++; //【指针移动】
}
}
cout << ans << endl;
}
2.【使用前缀和优化】
const int N = 1e5 + 10;
int a[N], sum[N];
int n, m;
void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
sum[i] = a[i] + sum[i - 1];
}
int ans = 0;
int l = 1, r = 1;
//while(左或右指针的限制条件)
while (r <= n)
{
//如果满足题目条件,则更新答案,移动指针+移动后【对已有数据影响】的【处理】
if (sum[r] - sum[l - 1] <= m)
{
ans = max(ans, r - l+1);
r++;
}
//如果不满足题目条件,则移动指针+移动后【对已有数据影响】的【处理】
else
{
//2.ans=max(ans,r-l);
l++;
}
}
//2.ans=max(r-l,ans)
//在2情况 更新答案的时候要补缺处理,当r==n时的答案还没有更新呢!!!
cout << ans << endl;
}
双指针VS二分:
相似处:都是(l,r)来对数据进行遍历
差别:
双指针:在移动的时候会及时更新(l,r)对数据造成的影响【eg.vis[ l / r ] = 0 / 1 , cnt ++ / --,sum+ / - 】 O(n)
二分:在对区间内的数据进行大范围处理 O(logn)
补题:
题目:【逛画展 - 洛谷】
思路参考:
【洛谷】P1638 逛画展(双指针)_gentle coder的博客-CSDN博客
代码实现:
const int N = 1e6 + 10, M = 2e3 + 10;
int a[N], b[M], n, m, L, R, k;
void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
int ans = inf;
int l = 1, r = 1;
b[a[l]] = 1, k = 1;
//while(左或右指针的限制条件)
while (r <= n )
{
//如果满足更新答案的条件,更新答案,移动指针+移动后【对已有数据影响】的【处理】
if (k == m)
{
if ((r - l + 1) < ans)
{
L = l, R = r;
ans = r - l + 1;
}
b[a[l]]--;
if (b[a[l]] == 0)
k--;
l++;
}
//如果不满足更新答案的条件,移动指针+移动后【对已有数据影响】的【处理】
else
{
r++;
b[a[r]]++;
if (b[a[r]] == 1)
k++;
}
}
cout << L << " " << R;
}
收获++:
一、二分来暴力真的很方便,双指针来写要多练,双指针还可以用队列来实现
B.减肥【贪心啊】
思路:
从前往后战胜k人 --- >队首权值单调不减,因此进行到最大值时,队首必然是最大值,队首不可能输
记录pre[i]为前i位的最大,再用a[i]==pre[i-k+1]来判定战胜k-1人+战胜前一个人=k人
注意特判第一位要战胜k人,也就是a[1]=pre[i+k]
代码实现:
vector<int>a;
void solve()
{
int n, k, idmax = 0;
cin >> n >> k;
a.resize(2 * n + 10);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
a[n + i] = a[i];
if (a[i] > a[idmax])
idmax = i;
}
if (k >= n - 1)
{
cout << idmax;
return;
}
vector<int>pre(2 * n + 1);//前i位的最大值
for (int i = 1; i <= 2 * n; i++)
{
pre[i] = max(pre[i - 1], a[i]);
}
for (int i = 1; i <= n; i++)
{
if (i!=1&&a[i] == pre[i] && a[i] == pre[i + k - 1])
{
cout << i;
return;
}
else
{
if (i == 1 && a[1] == pre[i + k])
{
cout << 1;
return;
}
}
}
}
C.cf1856【div2 C】
题目:
思路:
//二分找答案,验证答案的可行性,答案可行,答案++;答案不可行,答案--
//遍历1~n,每个位置的数字作为最大值的可行性【可行性为,总的操作次数<p】
代码实现:
void solve()
{
int n, k;
cin >> n >> k;
vector<int>a(n + 1);
int maxnum;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
maxnum = *max_element(a.begin() + 1, a.end())+k;
int l = 0, mid,r =maxnum+1, ans = 0;
while (l + 1 < r)
{
mid = l + r >> 1;//二分答案
int vis = 0;
for (int i = 1; i <= n; i++)//枚举每一个位置作为答案可行与否
{
vector<int>b(n + 1);
b[i] = mid;
int cnt = 0;
for (int j = i; j <= n; j++)
{
if (a[j] >= b[j])
break;
if (j >= n)
{
cnt = k + 1;
break;
}
cnt += (b[j] - a[j]);
b[j + 1] = max(b[j]-1, 0ll);
}
if (cnt <= k)
{
vis = 1;
}
}
if (vis)
{
ans = mid;
l = mid;
}
else
{
r = mid;
}
}
cout << ans << endl;
}
void solve()
{
int n, k;
cin >> n >> k;
vector<int> a(n + 10), sum(n + 10);
int ans = 0;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
ans = max(ans, a[i]);
sum[i] = sum[i - 1] + a[i];
}
for (int len = 1; len <= n; len++)
{
for (int l = 1; l + len - 1 <= n; l++)
{
int r = l + len - 1;
int R = max(a[r], a[r + 1] + 1); // 右端点
int left = a[r] - 1;
int right = R + 1;
while (left + 1 < right)
{
int mid = (left + right)/2;
if (len * mid - (sum[r] - sum[l - 1]) + ((len - 1) * len) / 2 <= k)
{
left = mid;
if (left >= a[r]+1)
{
ans = max(ans, left + len - 1);
}
}
else
{
right = mid;
}
}
}
}
cout << ans << endl;
}
收获++:
1.二分找答案,验证答案的可行性可以通过【枚举每一个位置为答案是否可行】
2.有规律呈【递增、递减】可以通过规律的性质进行二分,寻找答案