F2. Promising String (hard version)(cf)树状数组

原题链接:Problem - F2 - Codeforces

题目大意:给你一个长度为n的字符串,仅包含'+'和'-'字符。两个连续的'-'可以变为'+',问你有多少子串,可以进行任意次数的两个连续的'-'变为'+'的操作,使得最后的这个串里'-''+'数量相同。问你有多少子串,存在可以满足这样数量的子串。

思路:其实可以发现,'-'的数量一定是会大于等于'+'的数量,而且它们之差余3得为0,所以相当于维护树状数组的三种状态,然后从前往后查询和加入到数组数组中。如果后面有值与前面的值对3余数一样大,那么这两个点i和j之间的部分就是满足的子串。因为会有负数的情况,所以所有的值+( n + 2),这样就不怕下标变为负数了。所以一开始数组大小得开为2N。而且每次查询只能查询值+( n + 2)小于等于现在这个值的所有数量,因为如果大于的话,相当于它们之间的'+'数量大于'-'数量,肯定是不符合的。还有前缀本身余3为0的情况,所以第一次应该在数组数组把值为0(0 + n + 2)加入一个值进入数组数组。

记得每一次更新建的树~

写的是我的理解,感觉思路有点跳跃的,下次再写一遍好好组织语言~

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef pair<int, int> PII;
const double pi = acos(-1.0);
#define rep(i, n) for (int i = 1; i <= (n); ++i)
#define rrep(i, n) for (int i = n; i >= (1); --i)
typedef long long ll;
#define sqar(x) ((x)*(x))

const int N = 2e5 + 10;
ll tree[N << 1][3]; //用树状数组是因为它每次有一个余数值但是它只能取所有小于这个数的而且有相同余数的
char s[N];
int a[N];

int n;

ll query(int c, int x)
{
    ll res = 0;
    while(x > 0) res += tree[x][c], x -= x & -x;
    return res;
}

void add(int c, int x)
{
    while(x <= (n << 1) + 2) tree[x][c]++, x += x & -x;
}

int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        ll ans = 0;
        scanf("%d", &n);
        scanf("%s", s + 1);
        rep(i, (n << 1) + 2) for(int j = 0; j < 3; j++) tree[i][j] = 0;
        rep(i, n) a[i] = a[i - 1] + (s[i] == '-' ? 1 : -1);
        for(int i = 0; i <= n; i++){ //前缀为0的情况单独考虑
        ans += query((a[i] % 3 + 3) % 3, a[i] + n + 2);
        add((a[i] % 3 + 3) % 3, a[i] + n + 2);
        }
        printf("%lld\n", ans);
    }
    return 0;
}

F1数据小,n是3000,直接暴力O(n * n)就可以了,直接枚举每个区间'-'数量是不是大于‘+’且两个数量只差是3的倍数。其实想一想,只要满足这两个情况,那么一定可以有对应数量的'-'是连续的即可以变为‘+’,自己可以想一想画一画就知道了。

简单版暴力枚举代码:

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef pair<int, int> PII;
const double pi = acos(-1.0);
#define rep(i, n) for (int i = 1; i <= (n); ++i)
#define rrep(i, n) for (int i = n; i >= (1); --i)
typedef long long ll;
#define sqar(x) ((x)*(x))

const int N = 3010;
char a[N];
int sum[N];

int main()
{
    int n, t;
    scanf("%d", &t);
    while(t--)
    {
    scanf("%d", &n);
    scanf("%s", a + 1);
    rep(i, n)
    {
        sum[i] = sum[i - 1] + (a[i] == '-');
    }
    ll ans = 0;
    rep(i, n)
     for(int j = i + 1; j <= n; j++)
     {
         if((sum[j] - sum[i - 1] >= (j - i + 1) - (sum[j] - sum[i - 1])) && ((2 * (sum[j] - sum[i - 1]) - (j - i + 1)) % 3 == 0 )) ans++;
     }
     printf("%lld\n", ans);
    }
}

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值