Educational Codeforces Round 66 (Rated for Div. 2)-F. The Number of Subpermutations-哈希方法求子排列个数

Educational Codeforces Round 66 (Rated for Div. 2)-F. The Number of Subpermutations-哈希方法求子排列个数

【Description】

You have an array a1,a2,,an.

Let's call some subarray al,al+1,,ar of this array a subpermutation if it contains all
integers from 1 to r−l+1 exactly once. For example, array a=[2,2,1,3,2,3,1] contains 6 
subarrays which are subpermutations: [a2…a3], [a2…a4], [a3…a3], [a3…a5], [a5…a7], [a7…
a7].

You are asked to calculate the number of subpermutations.

【Input】

The first line contains one integer n (1≤n≤3105).

The second line contains n integers a1,a2,,an (1≤ai≤n).
    
This array can contain the same integers.

【Output】

Print the number of subpermutations of the array a.

【Examples】

Sample Input
8
2 4 1 3 4 2 1 2
Sample Output
7
Sample Input
5
1 1 2 1 2
Sample Output
6

【Problem Description】

给你n个数,求所有子排列的个数。子排列是指,n个数的一个子集,并且其满足长度为k,并且1-k每个数仅出现一次。

【Solution】

随机取n个128bit的数get[n]。预处理Hash数组,Hash数组为,n个数的异或前缀和。
此时Hash[len]表示1~len的排列的Hash值。

再预处理sum数组,sum数组表示,a[i]数组的异或前缀和,但要先将a[i]一一对应到n个128bit数上。

此时如果要判断a[l]~a[r]是否是一个子排列,则可以通过sum[r]^sum[l-1]是否等于Hash[l-r+1]判断。

而一个长度为len子排列一定满足其最大值为len,并且1一定会出现,且出现一次,因此可以通过这个条件来check某一
段中子排列的个数。

具体细节看代码。

ps:不知道为什么是128bit的随机数,我试着将存储随机数的数组改为int型,也可以AC。

【Code】

/*
 * @Author: Simon 
 * @Date: 2019-07-10 12:53:39 
 * @Last Modified by: Simon
 * @Last Modified time: 2019-07-10 14:56:34
 */
#include<bits/stdc++.h>
using namespace std;
typedef int Int;
#define int long long
#define INF 0x3f3f3f3f
#define maxn 300005
typedef unsigned long long ull;
int a[maxn];
ull Hash[maxn],sum[maxn],get1[maxn];
int solve(int i,int n){
    int len=1,ans=0;
    while(i<n&&a[i+1]!=1){ 
        i++;
        len=max(len,a[i]); //最大值即为此排列的长度
        if(i-len>=0) if((sum[i]^sum[i-len])==Hash[len]) ans++; //判断是否是一个子排列
    }
    return ans;
}
Int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    mt19937 rnd(time(NULL)); //随机数产生器
    int n;cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        get1[i]|=rnd();//产生n个随机数
        // get1[i]<<=32;
    }
    for(int i=1;i<=n;i++){
        Hash[i]=Hash[i-1]^get1[i]; //预处理Hash数组异或前缀和
        sum[i]=sum[i-1]^get1[a[i]]; //预处理a[i]数组所对应的 随机数的前缀和
    }
    int ans=0;
    for(int k=0;k<2;k++){ //正反都要做一遍
        for(int i=1;i<=n;i++){
            if(a[i]==1){ //如果遇到1才开始check
                ans+=solve(i,n); 
                if(k) ans++; //加上所有长度为1的排列。即1的个数,注意只处理一次
            }
        }
        reverse(a+1,a+n+1);
        for(int i=1;i<=n;i++) sum[i]=sum[i-1]^get1[a[i]]; //反向重新计算前缀和
    }
    cout<<ans<<endl;
    cin.get(),cin.get();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值