(有任何问题欢迎留言或私聊 && 欢迎交流讨论哦
题目:传送门
(原题目描述及样例在最下面)
题意就是求区间内有多少种颜色,但是带修改。
如果不带修改就是SPOJ-DQUERY,可以莫队,主席树,树状数组搞。
如果有修改操作,分块暴力搞,莫队搞搞也行。(其实是我不会树套树。树状数组可以写吗?
分块:672ms
莫队:492ms
实测:块的大小为2*sqrt(n)最快
(为什么我的cmp函数不加const就CE,一摸一样的代码给别人交就过了,我交就CE了,艹)
分块:
思路参考这位大神:传送门
分块记录每个数字上一次出现的位置和最后出现的位置。
如果在[L, R]区间内数字上一次出现的位置在L前面表示此区间这个数第一次出现,然后ans++。
修改的话你只需要修改 原数字的这一条链和修改后的数字这一条链。
pre数组就像链表一样记录前驱。所以只需要改变这两条链就行了,而不用暴力修改所有区间。
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 50005;
int l, r;
int a[maxn], belog[maxn], n, m, block, pre[maxn], spre[maxn]; //pre[i]=j表示数字a[i]上一次出现在j位置
int lst[1000006]={0,};//lst[i]=j,表示数字i上一次出现的位置是j ·
char op[2];
inline int read() {//读入挂
int ret = 0, c, f = 1;
for(c = getchar(); !(isdigit(c) || c == '-'); c = getchar());
if(c == '-') f = -1, c = getchar();
for(; isdigit(c); c = getchar()) ret = ret * 10 + c - '0';
if(f < 0) ret = -ret;
return ret;
}
inline void reset(int x) {
int l = (x - 1) * block + 1;
int r = min(n, x * block) ;
for(int i = l; i <= r; i++) {
spre[i] = pre[i];
}
sort(spre + l, spre + r + 1);
}
inline void build() {
for(int i = 1; i <= n; i++) {
pre[i] = lst[a[i]];
lst[a[i]] = i;
}
for(int i = 1; i <= belog[n]; i++) {
reset(i);
}
}
inline void query(int l, int r) {
int t1 = belog[l];
int t2 = belog[r];
int ans = 0;
for(int i = l; i <= min(t1 * block, r); i++) {
if(pre[i] < l) ans++;
}
if(t1 != t2) {
for(int i = (t2 - 1) * block + 1; i <= r; i++) {
if(pre[i] < l) ans++;
}
for(int i = t1 + 1; i <= t2 - 1; i++) {
ans += lower_bound(spre + (i - 1) * block + 1, spre + i * block + 1, l) - (spre + (i - 1) * block + 1); //不能用upper_bound()
}
}
printf("%d\n", ans);
}
inline void change(int l, int r) {
if(a[l]==r)return;
int ha=belog[l],hb=1,tl=belog[l],hc=1;
for(int i=lst[a[l]];i>=l;i=pre[i]){
if(pre[i]==0){
lst[a[l]]=0;
}else if(pre[i]==l){
pre[i]=pre[l];
ha=belog[i];
break;
}else if(i==l){
lst[a[l]]=pre[i];
break;
}
}
int flag=1;
for(int i=lst[r];i>=l;i=pre[i]){
if(pre[i]<l){
flag=0;
int t=pre[i];
pre[i]=l;
pre[l]=t;
hb=belog[i];
break;
}
}
if(lst[r]==0){
pre[l]=lst[r];
lst[r]=l;
hb=belog[l];
}else if(flag&&lst[r]<l){
hc=belog[lst[r]];
pre[l]=lst[r];
lst[r]=l;
hb=belog[l];
}
a[l]=r;
reset(ha);reset(hb);reset(tl);reset(hc);
}
int main() {
n = read();
m = read();
block = sqrt(n*1.0)*2;
for(int i = 1; i <= n; i++) {
a[i] = read();
belog[i] = (i - 1) / block + 1;
}
build();
for(int i = 1; i <= m; i++) {
scanf("%s%d%d", op, &l, &r);
if(op[0] == 'Q') {
query(l, r);
} else {
change(l, r);
}
}
return 0;
}
莫队:
查询的话就是和SPOJ-DQUERY一样,就不多讲了。
莫队除了用到已知区间[L, R],还要用到 T 记录已经进行了多少次修改操作
修改操作有点骚。它也是离线下来,记录修改的编号。每个询问也要记录在这次询问前进行了多少次修改。
查询前要让已经进行的修改操作 T 恰好等于这次查询所记录的次数。
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
struct lp{
int l,r,id,last;
int old,now;
}cw[N],tim[N];
int n,m,ar[N],arr[N],belong[N],ans,Ans[N],cnt[N*10];
bool cmp(const lp &a,const lp &b){
if(belong[a.l]!=belong[b.l])return belong[a.l]<belong[b.l];
if(belong[a.r]!=belong[b.r])return belong[a.r]<belong[b.r];
return a.last<b.last;
}
void update(int x,int f){
cnt[ar[x]]+=f;
if(f==1&&cnt[ar[x]]==1)ans++;
if(f==-1&&cnt[ar[x]]==0)ans--;
}
void update_time(int t,int f,int l,int r){
if(tim[t].l>=l&&tim[t].l<=r){
cnt[tim[t].old]-=f;
cnt[tim[t].now]+=f;
if(f==1){
if(cnt[tim[t].old]==0)ans--;
if(cnt[tim[t].now]==1)ans++;
}else{
if(cnt[tim[t].old]==1)ans++;
if(cnt[tim[t].now]==0)ans--;
}
}
if(f==1)ar[tim[t].l]=tim[t].now;
else ar[tim[t].l]=tim[t].old;
}
int main() {
while(~scanf("%d%d",&n,&m)){
int block=2*sqrt(n*1.0);
for(int i=1;i<=n;++i){
scanf("%d",&ar[i]);
arr[i]=ar[i];
belong[i]=(i-1)/block+1;
}
char op[2];
int tot=0,change=0;
for(int i=0,l,r;i<m;++i){
scanf("%s%d%d",op,&l,&r);
if(op[0]=='Q'){
cw[tot].last=change;cw[tot].l=l;cw[tot].r=r;
cw[tot].id=tot;tot++;
}else{
tim[++change].l=l;
tim[change].old=arr[l];
tim[change].now=r;
arr[l]=r;
}
}
ans=0;
memset(cnt,0,sizeof(cnt));
sort(cw,cw+tot,cmp);
for(int i=0,L=1,R=0,t=0;i<tot;++i){
for(;t<cw[i].last;){
update_time(++t,1,L,R);
}
for(;t>cw[i].last;){
update_time(t--,-1,L,R);
}
while(L<cw[i].l)update(L++,-1);
while(L>cw[i].l)update(--L,1);
while(R<cw[i].r)update(++R,1);
while(R>cw[i].r)update(R--,-1);
Ans[cw[i].id]=ans;
}
for(int i=0;i<tot;++i){
printf("%d\n",Ans[i] );
}
}
return 0;
}
题目描述:
Description
墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会像你发布如下指令: 1、 Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔。 2、 R P Col 把第P支画笔替换为颜色Col。为了满足墨墨的要求,你知道你需要干什么了吗?
Input
第1行两个整数N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。第2行N个整数,分别代表初始画笔排中第i支画笔的颜色。第3行到第2+M行,每行分别代表墨墨会做的一件事情,格式见题干部分。
Output
对于每一个Query的询问,你需要在对应的行中给出一个数字,代表第L支画笔到第R支画笔中共有几种不同颜色的画笔。
Sample Input
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6
Sample Output
4
4
3
4
HINT
对于100%的数据,N≤10000,M≤10000,修改操作不多于1000次,所有的输入数据中出现的所有整数均大于等于1且不超过10^6。
2016.3.2新加数据两组by Nano_Ape