[JXOI2017]颜色
题目链接
https://www.luogu.org/problemnew/show/P4065
题目描述
可怜有一个长度为 n 的正整数序列 Ai,其中相同的正整数代表着相同的颜色。
现在可怜觉得这个序列太长了,于是她决定选择一些颜色把这些颜色的所有位置都删去。
删除颜色 i 可以定义为把所有满足 Aj = i 的位置 j 都从序列中删去。
然而有些时候删去之后,整个序列变成了好几段,可怜不喜欢这样,于是她想要知道有多少种删去颜色的方案使得最后剩下来的序列非空且连续。
例如颜色序列 {1, 2, 3, 4, 5},删除颜色 3 后序列变成了 {1, 2} 和 {4, 5} 两段,不满足条件。而删除颜色 1 后序列变成了 {2, 3, 4, 5},满足条件。
两个方案不同当且仅当至少存在一个颜色 i 只在其中一个方案中被删去。
输入输出格式
输入格式:第一行输入一个整数 T 表示数据组数。每组数据第一行输入一个整数 n 表示数列长度。第二行输入 n 个整数描述颜色序列。
输出格式:对于每组数据输出一个整数表示答案。
输入输出样例
说明
满足条件的删颜色方案有 {1}, {1, 3}, {1, 2, 3}, {1, 3, 4}, {2, 3, 4}, ∅.
对于 20% 的数据,保证 1 ≤∑n ≤ 20。
对于 40% 的数据,保证 1 ≤∑n ≤ 500。
对于 60% 的数据,保证 1 ≤∑n ≤ 10^4。
对于 100% 的数据,保证 1 ≤ T,∑n ≤ 3 × 10^5, 1 ≤ Ai ≤ n。
题解
每段合法区间都是连续的,自然而然会想到枚举一个端点,然后求另一个端点的合法个数。
感觉这个套路比较常见,但是我依然不会。
我们可以先枚举右端点,然后怎么确定左端点的个数呢,感觉很麻烦是不是,正难则反嘛。
定义a[i]为i点的值,head[a[i]]为a[i]最左端点的位置,tail[i]为其最右端的位置。
枚举右端点,当右端点为第i位,令R=i,假设存在一个L,使得区间(L,R)为合法区间,
对于j(L<=j<=R),满足:
1.tail[a[j]]<=R
2.head[a[j]]>=L
到这一步接下来就有点难想到了。
对于每个点i,如果i=tail[a[i]],那么对于任何区间,head[a[i]]+1~tail[a[i]]不可能是左端点,我们将这段区间赋值成一。
再找到最靠近i的k满足,tail[k]>i,则(1,k)之间不可能存在左端点。
i为右端点的贡献等于,i-k-sum[k+1,i](sum[k+1,i]为k+1到i的被标记了的总和,蓝字部分).
至于怎么找k,可以用单调栈来维护,因为弹出栈的话,就不会再用到了。
代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define N 500050 5 6 int n,top,a[N],sk[N]; 7 int head[N],tail[N],last[N],nex[N]; 8 struct Tree {int l,r,lazy,sum;}tr[N<<2]; 9 template<typename T>void read(T&x) 10 { 11 ll k=0; char c=getchar(); 12 x=0; 13 while(!isdigit(c)&&c!=EOF)k^=c=='-',c=getchar(); 14 if (c==EOF)exit(0); 15 while(isdigit(c))x=x*10+c-'0',c=getchar(); 16 x=k?-x:x; 17 } 18 void read_char(char &c) 19 {while(!isalpha(c=getchar())&&c!=EOF);} 20 void push_up(int x) 21 { 22 int len=tr[x].r-tr[x].l+1; 23 if (len>1)tr[x].sum=tr[x<<1].sum+tr[x<<1|1].sum; 24 else tr[x].sum=0; 25 if (tr[x].lazy!=-1) 26 tr[x].sum=tr[x].lazy*len; 27 } 28 void push_down(int x) 29 { 30 if (tr[x].lazy==-1)return; 31 tr[x<<1].lazy=tr[x].lazy; 32 tr[x<<1|1].lazy=tr[x].lazy; 33 push_up(x<<1); 34 push_up(x<<1|1); 35 tr[x].lazy=-1; 36 } 37 void bt(int x,int l,int r) 38 { 39 tr[x]=Tree{l,r,-1,0}; 40 if (l==r) return ; 41 int mid=(l+r)>>1; 42 bt(x<<1,l,mid); 43 bt(x<<1|1,mid+1,r); 44 } 45 void update(int x,int l,int r) 46 { 47 if (l>r)return; 48 if (l<=tr[x].l&&tr[x].r<=r) 49 { 50 tr[x].lazy=1; 51 push_up(x); 52 return ; 53 } 54 int mid=(tr[x].l+tr[x].r)>>1; 55 push_down(x); 56 if (l<=mid)update(x<<1,l,r); 57 if (mid<r)update(x<<1|1,l,r); 58 push_up(x); 59 } 60 int query(int x,int l,int r) 61 { 62 if (l<=tr[x].l&&tr[x].r<=r) 63 return tr[x].sum; 64 int mid=(tr[x].l+tr[x].r)>>1,ans=0; 65 push_down(x); 66 if (l<=mid)ans=query(x<<1,l,r); 67 if (mid<r)ans+=query(x<<1|1,l,r); 68 push_up(x); 69 return ans; 70 } 71 void work() 72 { 73 read(n); 74 for(int i=1;i<=n;i++) read(a[i]); 75 for(int i=1;i<=n;i++) 76 { 77 if (head[a[i]]==0)head[a[i]]=i; 78 nex[tail[a[i]]]=i; 79 last[i]=tail[a[i]]; 80 tail[a[i]]=i; 81 } 82 bt(1,1,n); 83 sk[top=0]=0; 84 tail[0]=n+1; 85 ll ans=0; 86 for(int i=1;i<=n;i++) 87 { 88 sk[++top]=i; 89 while(top&&tail[a[sk[top]]]<=i)top--; 90 int j=sk[top]; 91 if (tail[a[i]]==i) 92 { 93 update(1,head[a[i]]+1,tail[a[i]]); 94 ans+=i-j-query(1,j+1,i); 95 } 96 } 97 printf("%lld ",ans); 98 } 99 void clear() 100 { 101 memset(head,0,sizeof(head)); 102 memset(tail,0,sizeof(tail)); 103 } 104 int main() 105 { 106 #ifndef ONLINE_JUDGE 107 freopen("aa.in","r",stdin); 108 #endif 109 int q; 110 read(q); 111 while(q--) 112 { 113 clear(); 114 work(); 115 } 116 }