zoukankan      html  css  js  c++  java
  • 启发式合并

    题意:给定数组a[],求区间个数,满足区间的数各不同,而且满足maxval-len<=K;

    思路:一看就可以分治做,对于当前的区间,从max位置分治。 对于这一层,需要高效的统计答案,那么对短的一边开始统计。

    (这个过程很像启发式的逆过程,所以叫做启发式分治)

    1,对于数不同,这个可以预处理前缀和后缀的最大区间长度A[],B[]。

    2,st表得到区间最大值位置,然后就可以搞了。

    题意:给定N,表示N堆石子,每堆石子数为a[],问多少个区间,可以满足“石子总和若为偶数,那么可以两两取来自不同堆的石子,直到取完; 如果为奇数,那么排除其中一个,然后可以两两取来自不同堆的石子,直到取完”。

    思路:结论是,如果一个区间的区间和大于等于区间最大值的两倍,则这个区间合法。 考虑分治,我们首先找到区间最大值(为了不重复统计,多个最大值时,统一取最左边的,这个可以ST表示实现),然后考虑跨越这个位置的合法区间个数。枚举一端,另外一段二分即可。

    由于分治的性质,我们每次的复杂度要倾向于小的那边,即是一个启发式合并的逆过程,所以启发式分治复杂度是O(NlogN)的,加上二分,这个做法的复杂度是O(Nlog^2N)。

    #include<bits/stdc++.h>
    #define ll long long
    #define rep(i,a,b) for(int i=a;i<=b;i++)
    using namespace std;
    const int maxn=300010;
    int lg[maxn],a[maxn],dp[maxn][20],N;
    ll sum[maxn],sum2[maxn],ans;
    void RMQ()
    {
        rep(i,1,N) dp[i][0]=i;
        for(int i=1;(1<<i)<=N;i++){
            for(int j=1;j+(1<<i)-1<=N;j++){
               dp[j][i]=a[dp[j][i-1]]>=a[dp[j+(1<<(i-1))][i-1]]?
                 dp[j][i-1]:dp[j+(1<<(i-1))][i-1];
            }
        }
    }
    void solve(int L,int R)
    {
        if(L>=R) return ;
        int k=lg[R-L+1];
        int Mid=(a[dp[L][k]]>=a[dp[R-(1<<k)+1][k]]?
          dp[L][k]:dp[R-(1<<k)+1][k]);
        if(Mid-L<R-Mid){
            rep(i,L,Mid) {
                int pos=lower_bound(sum+Mid,sum+R+1,sum[i-1]+2LL*a[Mid])-sum;
                ans+=R-pos+1;
            }
        }
        else {
            rep(i,Mid,R) {
                int pos=lower_bound(sum2+N-Mid+1,sum2+N-L+2,sum2[N-i]+2LL*a[Mid])-sum2;
                ans+=N+1-L-pos+1;
            }
        }
        solve(L,Mid-1); solve(Mid+1,R);
    }
    int main()
    {
        lg[0]=-1;
        rep(i,1,maxn-1) lg[i]=lg[i>>1]+1;
        int T;
        scanf("%d",&T);
        while(T--){
            scanf("%d",&N); ans=0;
            rep(i,1,N)
              scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
            rep(i,1,N) sum2[i]=sum2[i-1]+a[N+1-i];
            RMQ();
            solve(1,N);
            printf("%lld
    ",ans);
        }
        return 0;
    }
    

    题意:一个序列被称作是不无聊的,当且仅当,任意一个连续子区间,存在一个数字只出现了一次,问给定序列是否是不无聊的。

    对于一个区间 [l, r] 来说,符合条件时,会存在一个独特的数字位于 m,则对于 i in [l, m-1] and j in [m+1, r],所有 [i, j] 产生的区间必然会符合,则递归下去查找 [l, m-1] 和 [m+1, r] 有没有符合。

    这题有趣的地方在于-如何快速得到这个独特数字,不过也不难想地,建出数字的前一个相同和下一个相同。判断该数字在区间是独特时,则前一个和下一个皆不会在区间中。

    这样仍不会通过测资,如果是想要接近中间的方式去切割,则会接受 TLE 的残酷事实,当初在想是不是递迴太深还怎么的,这裡留下困惑,根据快速排序的经验,如果切割点在中间是比较好的,复杂度为 O(n log n)。

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+10;
    int a[maxn],pre[maxn],nex[maxn],n;
    map<int,int>R;
    bool dfs(int l,int r) {
        if (l >= r) return 1;
        for (int i = 0; i <= (r - l + 1)/2; i++) {
            if (nex[l + i] > r && pre[l + i] < l) return dfs(l, l + i - 1) && dfs(l + i + 1, r);
            if (nex[r - i] > r && pre[r - i] < l) return dfs(l, r - i - 1) && dfs(r - i + 1, r);
        }
        return 0;
    }
    int main() {
        int _;
        scanf("%d", &_);
        while (_--) {
            scanf("%d", &n);
            for (int i = 1; i <= n; i++) {
                scanf("%d", &a[i]);
                nex[i] = n + 1;
                pre[i] = 0;
            }
            R.clear();
            for (int i = 1; i <= n; i++) {
                pre[i] = R[a[i]];
                nex[pre[i]] = i;
                R[a[i]] = i;
            }
            if (dfs(1, n)) printf("non-boring
    "); else printf("boring
    ");
        }
    }
    
  • 相关阅读:
    根据现有文件生成图形化的目录树
    一个最简的短信验证码倒计时例子
    将指定目录下的所有资源整体迁移到另一个目录下
    通过 url 获取相应的 location 信息
    node-glob的*匹配
    mysql将查询出来的一列数据拼装成一个字符串
    Call to undefined function mysql_connect()错误原因
    JavaScript转unix时间戳
    .net3.0 中跨线程访问控件
    WPF的线程模型
  • 原文地址:https://www.cnblogs.com/Accpted/p/11391570.html
Copyright © 2011-2022 走看看