1611F - ATM and Students详细题解(*1800,线段树维护前缀和;双指针算法(思维))

简介: 1611F - ATM and Students详细题解(*1800,线段树维护前缀和;双指针算法(思维))

1611F - ATM and Students

题目大意:

给你一个数组a,找到最大连续段[ l , r ] [l,r][l,r]:

a l + a l + 1 + . . . + a r + s > = 0 al+al+1+...+ar+s>=0al+al+1+...+ar+s>=0 (条件1)

a l + a l + 1 + . . . + a r + s < 0 al+al+1+...+ar+s<0al+al+1+...+ar+s<0 (条件2)

或者l 到 n 都 满 足 a l + a l + 1 + . . . + a n + s > = 0 l到n都满足al+al+1+...+an+s>=0lnal+al+1+...+an+s>=0

如果找不到a l + a l + 1 + . . . + a r + s > = 0 al+al+1+...+ar+s>=0al+al+1+...+ar+s>=0,则输出-1。

1.双指针算法:

结论:如果a l + a l + 1 + . . . + a r − 1 + s > = 0 , al+al+1+...+ar-1+s>=0,al+al+1+...+ar1+s>=0a l + a l + 1 + . . . + a r + s < 0 al+al+1+...+ar+s<0al+al+1+...+ar+s<0,那么以l ll为左端点的满足条件一最大连续子段为:

[ l , r − 1 ] [l,r-1][l,r1],l ll对答案的贡献已经到头了!记录下来l , r l,rl,r

接下来

所以我们只需要将l ll的指针向右移一位:看看以l + 1 l+1l+1为左端点的区间[ l + 1 , r ] [l+1,r][l+1,r],是不是能满足条件1

1..如果能,以l + 1 l+1l+1为左端点移动 r rr 的指针向右找满足条件1最大位置,记录下来。

2.如果不能,继续移动左指针,找到满足条件一的即可,中间的那些点就算有右端点小于r的区间满足条件一,对答案的贡献也小于原先的区间[ l , r − 1 ] [l,r-1][l,r1],所以等于对答案没有贡献,直接跳过就行了

如果此时的左指针指向的数和右指针指向的数满足条件一,则往后移右指针更新答案。

代码如下:

#include <bits/stdc++.h>
using namespace std;
#define x first
#define y second
# define rep(i,be,en) for(int i=be;i<=en;i++)
# define pre(i,be,en) for(int i=be;i>=en;i--)
typedef pair<int, int> PII;
#define ll long long
#define endl "\n"
#define LOCAL
#define pb push_back
const int N = 2e5 + 10, INF = 0x3f3f3f3f;
ll qpow(int a, int b) {
  ll ans = 1;
  while (b) {
    if (b & 1) ans = ans * a;
    a = (ll)a * a;
    b >>= 1;
  }
  return ans;
}
inline int read()
{
  int X = 0;
  bool flag = 1;
  char ch = getchar();
  while (ch < '0' || ch > '9') {
    if (ch == '-') flag = 0;
    ch = getchar();
  }
  while (ch >= '0' && ch <= '9') {
    X = (X << 1) + (X << 3) + ch - '0';
    ch = getchar();
  }
  if (flag) return X;
  return ~(X - 1);
}
inline void write(int X)
{
  if (X < 0) {
    X = ~(X - 1);
    putchar('-');
  }
  if (X > 9) write(X / 10);
  putchar(X % 10 + '0');
}
//有效代码----------------------------------------------------------------------
int n, s;
ll a[N];
void solve()
{
  n = read();
  s = read();
  ll ans = 0;
  ll sum = s;
  int l = 0, r = 0;
  rep(i, 1, n)
  {
    a[i] = read();
  }
  int j = 1;
  for (int i = 1; i <= n; i++)
  {
    sum += a[i];
    while (j <= i && sum < 0)
    {
      sum -= a[j];
      j++;
    }
    if (sum >= 0)
    {
      if (ans < i - j + 1)
      {
        ans = i - j + 1;
        r = i;
        l = j;
      }
    }
  }
  if (l != 0 && r != 0)
  {
    printf("%d %d\n", l, r);
  } else
  {
    cout << -1 << endl;
  }
}
//有效代码-----------------------------------------------------------------------------
int main() {
  ios::sync_with_stdio;
  cin.tie(0);
  cout.tie(0);
  //#ifdef LOCAL
  //freopen("data.in.txt","r",stdin);
  //freopen("data.out.txt","w",stdout);
  //#endif
  int T = 1;
  scanf("%d", &T);
  while (T--)
  {
    solve();
  }
  return 0;
}

2.线段树写法

线段树维护区间[ l , r ] [l,r][l,r]的最小前缀和

如果[l,r]区间可行满足条件一,那么,向后找最大的满足条件的r(二分)

如果[l,r]区间不满足可行条件,l指针向左移,继续找答案

那么什么时候可行呢?

如果[l,r]区间的最小的前缀和sum[x] (x属于[l,r]),sum[x]-sum[l-1]+s>=0,那么这个区间[l,r]肯定满足sum[r]-sum[l-1]+s>=0,因为sum[r]>=sum[x],所以区间[l,r]满足条件一

如果sum[x]-sum[l-1]+s<0,那么[l,x]区间不满足条件一,那么[l,r]区间肯定也不满足,因为r>=x.

所以证明完毕,代码如下:

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const LL INF = 1e15;
int n, s;
LL sum[N];
struct node
{
  int l;
  int r;
  LL p;
} tr[N << 2];
void build(int u, int l, int r)
{
  tr[u].l = l;
  tr[u].r = r;
  if (l == r)
  {
    tr[u].p = sum[l];//线段树维护前缀和
    return;
  }
  int mid = l + r >> 1;
  build(u << 1, l, mid);
  build(u << 1 | 1, mid + 1, r);
  tr[u].p = min(tr[u << 1].p, tr[u << 1 | 1].p);
}
LL query(int u, int l, int r) //找区间[l,r]的最小前缀和
{
  if (tr[u].l >= l && tr[u].r <= r)
  {
    return tr[u].p;
  }
  int mid = tr[u].r + tr[u].l >> 1;
  LL mi = 1e15;
  if (l <= mid) mi = min(query(u << 1, l, r), mi);
  if (r > mid) mi = min(query(u << 1 | 1, l, r), mi);
  return  mi;
}
bool check(int l, int r)
{
  if (query(1, l, r) - sum[l - 1] + s >= 0) return true;
  return false;
}
int main()
{
  int T;
  scanf("%d", &T);
  while (T--)
  {
    scanf("%d%d", &n, &s);
    for (int i = 1; i <= n; i++)
    {
      int x;
      scanf("%d", &x);
      sum[i] = sum[i - 1] + x;
    }
    build(1, 1, n);
    int ans = 0;
    int ll = 0, rr = 0;
    for (int i = 1; i <= n; i++)
    {
      int l = i, r = n;
      while (l < r)//找最大的mid(r)
      {
        int mid = l + r + 1 >> 1;
        if (check(i, mid)) l = mid;
        else r = mid - 1;
      }
      if (check(i, l) && ans < (l - i + 1))//检查[i,l]区间是否合格
      {
        ans = l - i + 1;
        ll = i;
        rr = l;
      }
    }
    if (ans)
    {
      cout << ll << ' ' << rr << "\n";
    } else
    {
      cout << -1 << "\n";
    }
  }
  return 0;
}


目录
相关文章
|
1天前
|
算法 索引
数据结构与算法-线段树(segment-tree)
数据结构与算法-线段树(segment-tree)
7 0
|
4天前
|
算法 前端开发 JavaScript
< 每日算法:一文带你认识 “ 双指针算法 ” >
`双指针`并非指的是一种具体的公式或者范式。而是一种运算思路,用于节省逻辑运算时间的`逻辑思路`!双指针算法通常用于`优化时间复杂度`!
< 每日算法:一文带你认识 “ 双指针算法 ” >
|
15天前
|
算法
优选算法|【双指针】|202.快乐数
优选算法|【双指针】|202.快乐数
|
16天前
|
机器学习/深度学习 算法
【优选算法专栏】专题四:前缀和(二)
【优选算法专栏】专题四:前缀和(二)
21 1
|
16天前
|
算法
【优选算法专栏】专题一:双指针--------1.移动0
【优选算法专栏】专题一:双指针--------1.移动0
19 0
|
18天前
|
人工智能 算法
基础算法--前缀和与差分
基础算法--前缀和与差分
|
30天前
|
算法 关系型数据库 MySQL
大厂算法指南:优选算法 ——双指针篇(下)
大厂算法指南:优选算法 ——双指针篇(下)
26 0
|
1月前
|
算法
算法思想总结:前缀和算法
算法思想总结:前缀和算法
|
1月前
|
算法 容器
算法思想总结:双指针算法
算法思想总结:双指针算法