HDU 5997 rausen loves cakes (启发式合并+树状数组)

问题描述
rausen喜欢吃蛋糕。某天,他买了nn个蛋糕,每个蛋糕都有一个颜色,用\left[1,1000000 \right][1,1000000]中的整数来表示。rausen将它们从左到右排成一行,然后准备开始吃。

在吃之前,rausen想对蛋糕进行qq个操作。

某些时刻,rausen会把所有颜色为xx的蛋糕替换成颜色为yy的蛋糕。

另一些时刻,rausen会计算一段区间\left[x,y \right][x,y]内颜色的段数,所谓一段颜色,就是指极长的只有一种颜色的区间。例如1 4 4 1 1即为3段颜色。

然而,rausen发现,他并不会统计区间内的颜色段数,无助的rausen伤心地哭了起来(误)。而你为了安慰他,决定帮他解决这个问题。
输入描述
输入包含多组数据。第一行有一个整数TT,表示测试数据的组数,对于每组数据:

第一行输入2个整数nnqq分别表示蛋糕的数目和操作的数目。

接下来一行nn个正整数,第ii个正整数{a}_{i}ai表示第ii个蛋糕的颜色。

接下来qq行,每行3个整数op\left(1\leq op\leq 2 \right)op(1op2)xxyy,描述一个操作
对于op=1op=1,表示rausen进行替换操作,将颜色为xx的蛋糕替换成颜色为yy的蛋糕,xxyy满足\left(1\leq x,y\leq 1000000 \right)(1x,y1000000),请注意xx可能等于yy。

对于op=2op=2,表示rausen进行计数操作,此时你需要输出区间\left[x,y \right][x,y]内颜色的段数,xxyy满足\left(1\leq x\leq y\leq n \right)(1xyn)

\left(1\leq T\leq 5 \right)(1T5)\left(1\leq n\leq {10}^{5} \right)(1n105)\left(1\leq q\leq {10}^{5} \right)(1q105)
输出描述
对于每组测试数据的每一个计数操作,单独输出一行表示答案。
输入样例
1
5 3
1 4 4 10 1
2 1 5
1 4 10
2 3 5
输出样例
4

2

替换颜色的时候可以采用启发式合并,把小的往大的里面塞,然后记住每个颜色在当前序列中的颜色,然后维护一个树状数组,统计相邻位置的情况即可。

此题与梦幻布丁一样,不懂的话可以先去看 bzoj1483这道题。 这里讲的很好,可以点进去看看-->bzoj1483 (hnoi2009)梦幻布丁(链表+启发式合并) 

代码:参考_Occult_

#include<cmath>    
#include<queue>    
#include<cstdio>    
#include<cstring>    
#include<algorithm>    
using namespace std;  
#define ms(x,y) memset(x,y,sizeof(x))    
#define rep(i,j,k) for(int i=j;i<=k;i++)    
#define loop(i,j,k) for (int i=j;i!=-1;i=k[i])    
#define inone(x) scanf("%d",&x)    
#define intwo(x,y) scanf("%d%d",&x,&y)    
#define inthr(x,y,z) scanf("%d%d%d",&x,&y,&z)    
#define lson x<<1,l,mid    
#define rson x<<1|1,mid+1,r    
typedef long long LL;  
const int low(int x) { return x&-x; }  
const int mod = 1e9 + 7;  
const int N = 1e6 + 10;  
int T, n, m, x, y, z;  
int ft[N], nt[N], u[N], sz;  
int f[N], g[N], a[N], q[N];  
  
void add(int x, int y) { for (int i = x; i <= n; i += low(i)) q[i] += y; }  
  
int sum(int x) { int s = 0; for (int i = x; i; i -= low(i)) s += q[i]; return s; }  
  
int main()  
{  
    inone(T);  
    while (T--)  
    {  
        intwo(n, m);  
        ms(ft, -1); ms(f, 0); ms(g, 0); ms(q, 0);  
        sz = 0;  
        a[0] = a[n + 1] = -1;  
        rep(i, 1, n)  
        {  
            inone(a[i]); f[a[i]] = a[i]; g[a[i]]++;  
            u[sz] = i; nt[sz] = ft[a[i]]; ft[a[i]] = sz++;  
            if (a[i] != a[i - 1]) add(i, 1);  
        }  
        while (m--)  
        {  
            inthr(x, y, z);  
            if (x == 2) printf("%d\n", sum(z) - sum(y - 1) + (int)(a[y] == a[y - 1]));  
            else  
            {  
                if (y == z || !g[f[y]]) continue;  
                int s = y, t = z;  
                if (g[f[y]] > g[f[z]]) swap(y, z);  
                y = f[y]; z = f[z];  
                loop(i, ft[y], nt)  
                {  
                    if (a[u[i]] != a[u[i] - 1]) add(u[i], -1);  
                    if (a[u[i]] != a[u[i] + 1]) add(u[i] + 1, -1);  
                    a[u[i]] = z;  
                    if (a[u[i]] != a[u[i] - 1]) add(u[i], 1);  
                    if (a[u[i]] != a[u[i] + 1]) add(u[i] + 1, 1);  
                    if (nt[i] == -1)  
                    {  
                        nt[i] = ft[z]; ft[z] = ft[y]; break;  
                    }  
                }  
                g[z] += g[y]; g[y] = 0; f[t] = z; f[s] = 0;  
            }  
        }  
    }  
    return 0;  
}  






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值