題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=4455
一,題意:
給定一個序列ai,序列中元素個數爲n。再給定一個整數w,w表示的是字串長度。
要求你求出給定序列中所有長度爲w的子串中不同元素個數之和。
二,解析:
該題我們主要用:dp+線段樹。
1,在這裏線段數組主要用於快速求和,使得dp遞推效率更高。
2,dp[i]表示區間長度爲i時的個數。求dp[i]
則從區間長度i-1到i,要減去長度爲i-1了最後一個i-1子區間,因爲該區間無法擴展到長度爲i的區間。
然後再其他的長度爲i-1的區間後面加一個數就可以變成長度爲i的區間,而被增加的數應該爲:
a[i],a[i+1],,,,a[n]。所以我們主要的任務就是判斷所增加的節點是否在原來的子串中出現過。
若這些數字在前面的字串子串中出現過則該子串中不同數子的個數不變,即不用增加。
若我們令插入後不用增加數的的個數爲key,記last[i]表示最後一個區間長度爲i的不同數的個數。
則:dp[i]=dp[i-1]+(n-i+1)-key-last[i],
對於last[i] 我們很容易求得,所以我們主要的任務是求解key。這裏我們用到線段樹快速求得key;
2,求key:
我們建立一個線段樹,str[i] ,i表示的是當前結點到在他前面與他相等節點的最短距離,
記錄的是有這樣距離的當前結點的個數。但是這裏我們還要處理一個問題就是當我們
我在需要考慮該節點是要消除前面所插入的距離,避免對後面產生影響。
三:代碼:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
typedef long long LL;
const int Max =1000010;
int N,M;
int a[Max];
int str[Max];//樹狀數組(下標表示距離)
int Hash[Max];
int last[Max];//last[i]記錄最後面的長度爲i的子鏈的不同數的個數
LL dp[Max];//dp[i]表示區間長度爲w=i時總和
int bit(int x)
{//樹狀數組每次移動的位數
return x&(-x);
}
void add(int x,int data)
{//在樹狀數組第i個位置增加data
while(x<=N)
{
str[x]+=data;
x+=bit(x);
}
}
LL sum(int x)
{//求樹狀樹狀求和
LL key=0;
while(x)
{
key+=str[x];
x=x-bit(x);
}
return key;
}
int main()
{
while(scanf("%d",&N)!=EOF&&N)
{
memset(Hash,-1,sizeof(Hash));
memset(str,0,sizeof(str));
for(int i=1;i<=N;i++)
{
scanf("%d",&a[i]);
if(Hash[a[i]]!=-1)
{
add(i-Hash[a[i]]+1,1);//在兩個相同值最短距離處加1
add(i+1,-1);//i+1位置減一
//當你考慮區間所插入點沒有i節點時我們要消除他前面所插入的距離
}
Hash[a[i]]=i;//a[i]最後一次出現的位置
}
memset(Hash,-1,sizeof(Hash));
last[0]=0;
for(int i=N;i>0;i--)
{
last[N-i+1]=last[N-i];
if(Hash[a[i]]==-1)
last[N-i+1]++;
Hash[a[i]]=i;
}
dp[1]=N;
for(int i=2;i<=N;i++)
dp[i]=dp[i-1]+(N-i+1)-sum(i)-last[i-1];
scanf("%d",&M);
for(int i=0;i<M;i++)
{
int k;
scanf("%d",&k);
printf("%lld\n",dp[k]);
}
}
return 0;
}