Neko and sequence(HDU - 6539,树状数组维护奇怪の东西♂)

一、题目链接

Neko and sequence

二、题目大意

给一个长度为 n n n 且字符集为 { ′ ( ′ , ′ ) ′ } \{'(', ')'\} {(,)} 的环形字符串 s s s,下标从 0 0 0 开始标号. 还给出一个整数 q q q 和一个整数 k k k.

定义走一步:对于每一个 i    ( 0 ≤ i ≤ n − 1 ) i \; (0 \leq i \leq n - 1) i(0in1),若 s [ i ] = = ′ ( ′ s[i] == '(' s[i]==(,则走一步到达 ( i − k + n ) % n (i - k + n) \% n (ik+n)%n 的位置;若 s [ i ] = = ′ ) ′ s[i] == ')' s[i]==),则走一步到达 ( i + k ) % n (i + k) \% n (i+k)%n 的位置.

定义 f ( i , d ) f(i, d) f(i,d) 表示从位置 i i i 出发,走恰好 d d d 步到达的位置.

一共有 q q q 次询问,每次询问给出三个整数 l , r , d l, r, d l,r,d,表示询问 ∑ i = l r f ( i , d ) \begin{aligned} \sum_{i = l}^{r}{f(i, d)} \end{aligned} i=lrf(i,d).

1 ≤ n , q ≤ 1 0 5 , 1 ≤ k ≤ n , 1 ≤ l ≤ r ≤ n , 1 ≤ d ≤ 1 0 9 1 \leq n, q \leq 10^5, 1 \leq k \leq n, 1 \leq l \leq r \leq n, 1 \leq d \leq 10^9 1n,q105,1kn,1lrn,1d109.

三、分析

一开始想的是倍增 d p dp dp,但对于一段区间来讲,走 1 1 1 步之后可能变得不连续,遂放弃…

不过我们可以观察到一点就是:对于一个左括号 ′ ( ′ '(' (,它会一直向左走,直到它又返回原地或者遇到一个右括号从而在二元环里循环跳,右括号同理.

所以,我们首先统计出对于每个 i i i,它需要走多少步才能进入二元环状态,并记为 a [ i ] a[i] a[i].

因此对于一次询问 ( l , r , d ) (l, r, d) (l,r,d) 来讲,我们可以把区间 [ l , r ] [l, r] [l,r] 的数分为两类。一类是已经进入二元环状态,另一类是还未进入二元环状态.

由于有 q q q 次询问,直接做不好搞,我们可以把所有询问离线下来,按照 d d d 从小到大排序。这样处理之后,上述提到的两种状态个数前者单调增加,后者单调减少. 具体实现像扫描线那样,对 a [ i d [ i ] ] a[id[i]] a[id[i]] 从小到大排序后,对于当前的 d i d_i di,只需要把介于 ( d i − 1 , d i ] (d_{i - 1}, d_i] (di1,di] 之间的 a [ i d [ i ] ] a[id[i]] a[id[i]] 置为二元环状态.

因此对于当前询问,答案等于区间 [ l , r ] [l, r] [l,r] 中走 d d d 步之后处于二元环状态的终点位置和 + 区间 [ l , r ] [l, r] [l,r] 中走 d d d 步之后不处于二元环状态的终点位置和.

对于前者,显然答案只与 d d d 的奇偶性有关. 也就是说,奇数 d d d 对应一个终点集合,偶数 d d d 对应另一个终点集合. 因此,我们可以用树状数组 c [ 0 ] [ i ] c[0][i] c[0][i] 维护偶数 d d d 对应的终点集合,树状数组 c [ 1 ] [ i ] c[1][i] c[1][i] 对应奇数 d d d 的终点集合. 对于新插入的每一个 a [ i d [ i ] ] a[id[i]] a[id[i]],单点更新一下两个树状数组. 对于查询操作,就相当于查询区间和.

对于后者需要查询区间 [ l , r ] [l, r] [l,r] 中走 d d d 步之后不处于二元环状态的终点位置和,记记移动距离 x = d k x = dk x=dk, c a l c ( p , x ) calc(p, x) calc(p,x) 表示前 p p p 个位置走 x x x 步处于非二元环状态的终点位置和,则答案为 c a l c ( r , x ) − c a l c ( l − 1 , x ) calc(r, x) - calc(l - 1, x) calc(r,x)calc(l1,x).

对于求解 c a l c ( p , x ) calc(p, x) calc(p,x), 先考虑右括号的影响,分两段考虑. 第一段是区间 [ 1 , n − x − 1 ] [1, n - x - 1] [1,nx1],这段区间的所有位置走 x x x 步之后的位移一定是不小于 0 0 0 的(位移为 x % n x \% n x%n),因此答案等于区间 [ 1 , n − x − 1 ] [1, n - x - 1] [1,nx1] 中走 x x x 步处于非二元环状态的原位置和 + ( x % n ) × (x \% n) \times (x%n)×区间 [ 1 , n − x − 1 ] [1, n - x - 1] [1,nx1] 中走 x x x 步处于非二元环状态的原位置的个数,可使用两个树状数组维护. 第二段是区间 [ n − x , p ] [n - x, p] [nx,p],这段区间的所有位置走 x x x 步之后的位移一定是不大于 0 0 0 的(位移为 x % n − n x \% n - n x%nn),因此答案等于区间 [ n − x , p ] [n - x, p] [nx,p] 中走 x x x 步处于非二元环状态的原位置和 + ( x % n − n ) × (x \% n - n) \times (x%nn)×区间 [ n − x , p ] [n - x, p] [nx,p] 中走 x x x 步处于非二元环状态的原位置的个数,可使用两个树状数组维护. 左括号的影响同理,此处不再赘述.

四、代码实现

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;

const int M = (int)1e5;
const int N = (int)1e5;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;

int n, q, k;
char s[M + 5];
int nx[M + 5];
int a[M + 5];
int id[M + 5];
bool vis[M + 5];
int b[2][M + 5];
ll c[2][M + 5];
ll lft[M + 5], lftNum[M + 5];
ll rht[M + 5], rhtNum[M + 5];

struct qnode
{
    int l, r, d, id;
    bool operator<(const qnode& b)const
    {
        return d < b.d;
    }
} v[M + 5];

ll ans[M + 5];

int dfs(int u)
{
    if(vis[u])
    {
        if(~a[u])   return a[u];
        else        return inf;
    }
    vis[u] = 1;
    if(nx[nx[u]] == u)  return a[u] = 0;
    else                return a[u] = dfs(nx[u]) + 1;
}

bool cmp(int x, int y)
{
    return a[x] < a[y];
}

inline int lowbit(int n)
{
    return n&-n;
}

void add(ll tree[], int p, ll x)
{
    ++p;
    while(p <= n)
    {
        tree[p] += x;
        p += lowbit(p);
    }
}

ll ask(ll tree[], int p)
{
    ++p;
    ll ans = 0;
    while(p)
    {
        ans += tree[p];
        p -= lowbit(p);
    }
    return ans;
}

void change(int p)
{
    if(s[p] == '(')      add(lft, p, -p), add(lftNum, p, -1);
    else if(s[p] == ')') add(rht, p, -p), add(rhtNum, p, -1);
    add(c[0], p, b[0][p]), add(c[1], p, b[1][p]);
}

ll calc(int p, int x)
{
    ll ans = 0;
    int pos = min(n - x - 1, p);
    ans += ask(rht, pos) + ask(rhtNum, pos) * x;
    ans += ask(rht, p) + ask(rhtNum, p) * (x - n) - ask(rht, pos) - ask(rhtNum, pos) * (x - n);
    x = n - x;
    pos = min(n - x - 1, p);
    ans += ask(lft, pos) + ask(lftNum, pos) * x;
    ans += ask(lft, p) + ask(lftNum, p) * (x - n) - ask(lft, pos) - ask(lftNum, pos) * (x - n);
    return ans;
}

void work()
{
    while(~scanf("%d %d %d", &n, &q, &k))
    {
        for(int i = 0; i < n; ++i)
        {
            a[i] = -1;
            id[i] = i;
            vis[i] = 0;
            c[0][i] = c[1][i] = 0;
            lft[i] = lftNum[i] = 0;
            rht[i] = rhtNum[i] = 0;
        }
        scanf("%s", s);
        for(int i = 0; i < n; ++i)
        {
            if(s[i] == '(')         nx[i] = (i - k + n) % n;
            else if(s[i] == ')')    nx[i] = (i + k) % n;
        }
        for(int i = 0; i < n; ++i) if(!vis[i]) dfs(i);
        sort(id, id + n, cmp);
        for(int i = 0, f; i < n; ++i)
        {
            if(s[i] == '(')      add(lft, i, i), add(lftNum, i, 1), f = -1;
            else if(s[i] == ')') add(rht, i, i), add(rhtNum, i, 1), f = 1;
            if(a[i] < inf)
            {
                b[a[i] % 2][i] = (i + 1ll * f * a[i] * k % n + n) % n;
                b[1 - a[i] % 2][i] = nx[b[a[i] % 2][i]];
            }
        }
        for(int i = 0; i < q; ++i)
        {
            scanf("%d %d %d", &v[i].l, &v[i].r, &v[i].d);
            v[i].id = i;
        }
        sort(v, v + q);
        for(int i = 0, j = 0, l, r, d; i < q; ++i)
        {
            l = v[i].l, r = v[i].r, d = v[i].d;
            while(j < n && a[id[j]] <= d) change(id[j++]);
            ans[v[i].id] = ask(c[d % 2], r) - ask(c[d % 2], l - 1);
            ans[v[i].id] += calc(r, 1ll * k * d % n) - calc(l - 1, 1ll * k * d % n);
        }
        for(int i = 0; i < q; ++i) printf("%lld\n", ans[i]);
    }
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
//    ios::sync_with_stdio(0);
//    cin.tie(0); cout.tie(0);
//    init();
//    int T; scanf("%d" ,&T);
//    for(int ca = 1; ca <= T; ++ca)
//    {
        printf("Case %d: ", ca);
//        work();
//    }
    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值