51nod 1280 前缀后缀集合 (集合hash或者map)

探讨一种高效算法,用于计算数组中前缀与后缀集合相等的下标对数量,通过哈希和映射技巧实现快速匹配,解决重复元素问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

1280 前缀后缀集合

  1. 1.0 秒
  2.  
  3. 131,072.0 KB
  4.  
  5. 40 分
  6.  
  7. 4级题

一个数组包含N个正整数,其中有些是重复的。一个前缀后缀集是满足这样条件的下标对(P,S), 0<= P,S < N 满足数组元素A[0..P]的值也在A[S..N - 1]的值中出现,并且A[S..N - 1]中的值也再A[0..P]中出现。换句话说前缀的集合A[0..P]与后缀集合A[S..N - 1]包含完全相同的值。求这样的前缀后缀集合的数量。

 

例如:3 5 7 3 3 5,共有14个集合符合条件:(1, 4), (1, 3), (2, 2), (2, 1), (2, 0), (3, 2), (3, 1), (3, 0), (4, 2), (4, 1), (4, 0), (5, 2), (5, 1), (5, 0)

本题由 @javaman 翻译。

 收起

输入

<span style="color:#676a6c"><span style="color:#333333">第1行:一个数N, 表示数组的长度(1 <= N <= 50000)。
第2 - N + 1行:每行1个数,对应数组中的元素Ai。(1 <= Ai <= 10^9)</span></span>

输出

<span style="color:#676a6c"><span style="color:#333333">输出符合条件的集合数量。</span></span>

输入样例

<span style="color:#676a6c"><span style="color:#333333">6
3
5
7
3
3
5</span></span>

输出样例

<span style="color:#676a6c"><span style="color:#333333">14</span></span>

此题如果用Hash来做的话,需要将按照顺序将映射,我暴力+set写了一发,T了四个点。

网上有一篇,但迷之hash,没看到Hash是如何用的,只知道他实现了集合的hash对应。

或者用map模拟一下,直接看代码,有注释。

 

集合Hash

#include<bits/stdc++.h>

using namespace std;
const int mod = 1e9 + 7;
const int M =1e5 + 50;

#define mst(a,b) memset(a,b,sizeof(a))
#define rush() int T;cin>>T;while(T--)

map<int,bool> mp;
map<pair<int,int>,int> pre;
int a[M];

int main()
{
    int n,k = 0,kk = 0;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        if(mp[a[i]] == 0)
        {
            k = k + a[i] * 131 % mod;
            kk = kk + a[i] % mod * a[i] % mod;
            
            mp[a[i]] = 1;
        }
        pre[ {k,kk}]++;
    }
    mp.clear();
    k = kk = 0;
    long long ans = 0;
    for(int i=n; i>=1; i--)
    {
        if(mp[a[i]] == 0)
        {
            k = k + a[i] * 131 % mod;
            kk = kk + a[i] % mod * a[i] % mod;
            
            mp[a[i]] = 1;
        }
        ans += pre[ {k,kk}];
    }
    cout<<ans;

}

 

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=100005;
int n,a[N];
LL ans;
map<int,int> mp;
void solve()
{
	
	int r=n;
	LL temp=0,num=0;//temp前i个字符已经匹配的数量,num两边差别的数量
	for(int i=1;i<=n;i++)
	{
		if(mp[a[i]]!=0) //前面的字符已经出现了,说明前面的集合不变,直接累加前面的答案即可
		{
			ans+=temp;
		}
		else   //该字符没有出现,前缀集合改变,后缀集合也需要改变,temp清0
		{
			mp[a[i]]=1;
			temp=0;
			num++;
			while(r>=1&&mp[a[r]]!=0)
			{
				if(mp[a[r]]==1)
				{
					mp[a[r]]=2;
					num--;
				}
				
				if(num==0)
				{
					ans++;
					temp++;
				}
				r--;
			}
		}
	}
}

int main()
{
	scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
    }
    solve();

    printf("%lld\n",ans);

}

T了四个点

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const int N=1000005;
const int P=131;
ULL hashv_pre[N],hashv_suff[N],power[N];
int n,a[N];
LL ans=0;
void calHash()
{
    map<int,int> mp;
    set<int> set_pre;
    map<ULL,int> hash_mp;
    for(int i=1; i<=n; i++)
    {
        if(mp[a[i]]==0)
        {
            set_pre.insert(a[i]);
            ULL s=0;
            for(auto item:set_pre)
			{
				 s=s*P+item;
			}
			hashv_pre[i]=s;
            hash_mp[hashv_pre[i]]++;
			mp[a[i]]++;
        }
        else
        {
            hashv_pre[i]=hashv_pre[i-1];
            hash_mp[hashv_pre[i]]++;
        }
    }
   
    set<int> set_suff; 
    mp.clear();

    for(int i=n; i>=1; i--)
    {
        if(mp[a[i]]==0)
        {
            set_suff.insert(a[i]);
            ULL s=0;
            for(auto item:set_suff)
			{
				 s=s*P+item;
			}
			hashv_suff[i]=s;
            //cout<<"#"<<hashv_suff[i]<<endl;
            ans+=hash_mp[hashv_suff[i]];
            mp[a[i]]++;
        }
        else
		{
			hashv_suff[i]=hashv_suff[i+1];
			//cout<<hashv_suff[i]<<endl;
			ans+=hash_mp[hashv_suff[i]];
		}

    }
}

int main()
{
	scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
    }
    calHash();

    printf("%lld\n",ans);

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值