51Nod 1810 连续区间

https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1810

题目给出一个1~n的排列,问有多少连续区间。连续区间的定义为区间内元素排序后之间间隔为1。

对于一个区间[l,r],令mid=(l+r)/2,我们如果能在O(n)内求解出左端点在[l,mid],右端点在[mid+1,r]的连续区间数量,就可以将问题一分为二,递归求解[l,mid] [mid+1,r]。

现在来求解上面所说的这个子问题,首先默认i<j,有一个结论max[i~j]-min[i~j]==j-i时[i,j]是一个连续区间。所以我们维护两组从mid(mid+1)出发,向左(右)延伸的后(前)缀max和min数组。max[i~j]=max(max[i],max[j]),min同理。我们只要找到所有i,j组合使得结论式成立即可。

但是很明显朴素枚举是n^2的,我们能不能优化到n呢?

观察max[i~j],min[i~j],他们的来源有4种组合,因为是两两对应的,我们其中两个组合来讨论,另外两个可以同理推导。

第一种是max和min都来自mid左边。有max[i]-min[i]=j-i,推导得 j=max[i]-min[i]+i,只要枚举左半边的i,并判断j是否合法即可。

第二种是max来自左边min来自右边。有max[i]-min[j]=j-i,推导得max[i]+i=min[j]+j,枚举每个max[i]+i,计算有多少合法的j符合即可。说得轻松,这个j的数量怎么计算呢?

我们发现max想要来自左边,需要满足max[i]>max[j],同理min[i]>min[j]。同时我们发现从中心向外发散,max递增,min递减,也就是从中心向左枚举i的过程中max限制越来越宽松,min越来越严格。我们lp,rp来维护右半区间中满足当前i的[mid+1,r]的j的子窗口,可以预见的是随着i--,这个窗口会向右尺取。我们每加入一个j,就让一个计数数组中的cnt[min[j]+j]++,每剔除一个j则相应减减。对于每个i,完成尺取后,ans+=cnt[max[i]+i]。

对应搞定剩下两个情况后这题就理论AC了,剩下还有一些细节,比如l==r的处理,cnt数组的复原处理,输入挂优化什么的,搞搞就AC了。

#include <iostream>
#include <cmath>
#include <algorithm>
#include <map>
#include <cstring>
#define LL long long
using namespace std;
const LL N = 1000005;
int num[N],n;
LL ans;
int mx[N], mi[N];
int read() {
    char ch;
    for (ch = getchar(); ch<‘0‘ || ch>‘9‘; ch = getchar());
    int x = ch - ‘0‘;
    for (ch = getchar(); ch >= ‘0‘&&ch <= ‘9‘; ch = getchar()) x = x * 10 + ch - ‘0‘;
    return x;
}
void make_pre(LL l, LL r, LL mid)
{
    mi[mid] = mx[mid] = num[mid];
    mi[mid + 1] = mx[mid + 1] = num[mid + 1];
    for (int i = mid - 1; i >= l; i--)
    {
        mx[i] = max(num[i], mx[i + 1]);
        mi[i] = min(num[i], mi[i + 1]);
    }
    for (int i = mid + 2; i <= r; i++)
    {
        mx[i] = max(num[i], mx[i - 1]);
        mi[i] = min(num[i], mi[i - 1]);
    }
}
struct tong
{
    LL cnt[N * 3];
    void clear()
    {
        memset(cnt, 0, sizeof(cnt));
    }
    void setZero(LL num)
    {
        cnt[num + N] = 0;
    }
    void add(LL num,int v)
    {
        cnt[num + N]+=v;
    }
    LL query(LL num)
    {
        return cnt[num+N];
    }
}cnt;

void solve(LL l, LL r)
{

    int temp = ans;
    LL mid = (l + r) /2;
    make_pre(l, r, mid);
    //same i
    for (int i = l; i <= mid; i++)
    {
        int nj = mx[i] - mi[i]+i;
        if (nj>mid&&nj<=r&&mx[i]>mx[nj] && mi[i]<mi[nj]) ans++;
    }
    //same j
    for (int i = mid+1; i <= r; i++)
    {
        int nj = mx[i] - mi[i]-i;
        nj = -nj;
        if (nj<=mid&&nj>=l&&mx[i]>mx[nj] && mi[i]<mi[nj]) ans++;
    }
    //dif mx[i],mi[j]
    LL pl = mid+1,pr=mid+1;
    for (int i = mid; i>=l; i--)
    {
        while (pr <= r&&mx[pr] < mx[i])cnt.add(mi[pr] + pr,1),pr++;
        while (pl < pr&&mi[pl] > mi[i])cnt.add(mi[pl] + pl, -1), pl++;
        //if (cnt.query(mx[i] + i) < 0) cout << l << ‘ ‘ << r << ‘ ‘ << pl << endl;
        ans += cnt.query(mx[i] + i);
    }
    while (pl < pr)cnt.setZero(mi[pl]+pl),pl++;
    //dif mi[i],mx[j]
    pl = mid , pr = mid;
    for (int i=mid+1; i<=r; i++)
    {
        while (pr >=l&&mx[pr] < mx[i])cnt.add(mi[pr] - pr, 1), pr--;
        while (pl > pr&&mi[pl] > mi[i])cnt.add(mi[pl] - pl, -1), pl--;
        //if(cnt.query(mx[i] - i)<0)cout << l << ‘ ‘ << r << ‘ ‘ << pl << endl;
        ans += cnt.query(mx[i] - i);
    }
    while (pl > pr)cnt.setZero(mi[pl] - pl), pl--;
    //cout << ans - temp << ‘ ‘ << l << ‘ ‘ << r << endl;
    if (l == r)return;
    solve(l, mid);
    solve(mid + 1, r);
}
int main()
{
    //cin.sync_with_stdio(false);
    n = read();
        for (int i = 0; i < n; i++)num[i]=read();
        ans = 0;
        cnt.clear();
        solve(0, n - 1);
        printf("%lld\n", ans+n);

    return 0;
}
时间: 2024-10-05 09:30:02

51Nod 1810 连续区间的相关文章

51Nod - 1094 和为k的连续区间

51Nod - 1094 和为k的连续区间 一整数数列a1, a2, ... , an(有正有负),以及另一个整数k,求一个区间[i, j],(1 <= i <= j <= n),使得a[i] + ... + a[j] = k. Input 第1行:2个数N,K.N为数列的长度.K为需要求的和.(2 <= N <= 10000,-10^9 <= K <= 10^9) 第2 - N + 1行:A[i](-10^9 <= A[i] <= 10^9). Ou

51nod 1094 和为k的连续区间(暴力和map优化)

题目意思: http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1094 一整数数列a1, a2, ... , an(有正有负),以及另一个整数k,求一个区间[i, j],(1 <= i <= j <= n),使得a[i] + ... + a[j] = k. Input 第1行:2个数N,K.N为数列的长度.K为需要求的和.(2 <= N <= 10000,-10^9 <= K <= 10^9

51nod 1094 和为k的连续区间【前缀和/区间差/map】

1094 和为k的连续区间 基准时间限制:1 秒 空间限制:131072 KB 分值: 10 难度:2级算法题  收藏  关注 一整数数列a1, a2, ... , an(有正有负),以及另一个整数k,求一个区间[i, j],(1 <= i <= j <= n),使得a[i] + ... + a[j] = k. Input 第1行:2个数N,K.N为数列的长度.K为需要求的和.(2 <= N <= 10000,-10^9 <= K <= 10^9) 第2 - N 

51Nod 1094 和为k的连续区间 | 水

Input示例 6 10 1 2 3 4 5 6 Output示例 1 4 #include "cstdio" #include "algorithm" #include "iostream" #include "set" using namespace std; #define LL long long #define N 10010 int arr[N]; int main() { int k,n; while(~scan

51nod 1201 整数划分(dp)

题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1201 题解:显然是一道dp,不妨设dp[i][j]表示数字i分成j个一共有几种分法. 那么转移方程式为: dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1] 表示将i - 1划分为j个数,然后j个数都+1 还是不重复,将i - 1划分为j - 1个数,然后j - 1个数都+1,再加上1这个数. 然后就是j的范围要知道1+2+

51nod 1138 连续整数的和(数学)

题目描述: http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1138 给出一个正整数N,将N写为若干个连续数字和的形式(长度 >= 2).例如N = 15,可以写为1 + 2 + 3 + 4 + 5,也可以写为4 + 5 + 6,或7 + 8.如果不能写为若干个连续整数的和,则输出No Solution. Input 输入1个数N(3 <= N <= 10^9). OutPut 输出连续整数中的第1个数,如果有多

51nod 1463 找朋友(线段树+离线处理)

http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1463 题意: 思路: 好题! 先对所有查询进行离线处理,按照右区间排序,因为k一共最多只有10个,所有在该区间内的B数组,每次枚举K值,通过这样的方式来得到另外一个B值.但是这样得到的B值它在B数组中的位置必须在当前数的左边.如下图:(j为当前数在B数组中的位置,pos为计算得到的另一个B值在数组中的位置) 这两个数的和记录在pos中,这里pos的位置必须在j的左边,假

51nod 1437

http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1437 1437 迈克步 题目来源: CodeForces 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 收藏 关注 有n只熊.他们站成一排队伍,从左到右依次1到n编号.第i只熊的高度是ai. 一组熊指的队伍中连续的一个子段.组的大小就是熊的数目.而组的力量就是这一组熊中最小的高度. 迈克想知道对于所有的组大小为x(1 ≤ x ≤ n

51nod 1272 思维/线段树

http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1272 1272 最大距离 题目来源: Codility 基准时间限制:1 秒 空间限制:131072 KB 分值: 20 难度:3级算法题 收藏 关注 给出一个长度为N的整数数组A,对于每一个数组元素,如果他后面存在大于等于该元素的数,则这两个数可以组成一对.每个元素和自己也可以组成一对.例如:{5, 3, 6, 3, 4, 2},可以组成11对,如下(数字为下标):