zoukankan      html  css  js  c++  java
  • 羽未「umi」

    前言

    首先这道题要把题读对,而且不能在考试还剩 10 分钟的时候才读对。

    老子看到二刺猿就烦,能不能下次不要搞这些莫名其妙的背景!!

    题目

    一开始有一个长度为 \(n\) 的序列,每个位置都有一个颜色 \(a_i\),你可以进行若干次操作:

    • 选择一种颜色,将这种颜色全部变为另外一种颜色。

    目标状态是相同的颜色是序列中连续的一段。

    但这还不够,所以有 \(q\) 次修改,每次修改会更改一个位置上的颜色。

    你需要对初始序列和每个修改之后都回答最小改变量,注意改变量不是操作次数,而是操作后的序列和原序列的不同元素个数。

    你的操作不会保留到下一次修改。

    \(1\le n,a_i\le 2\times 10^5;0\le q\le 2\times 10^5.\)

    讲解

    首先暴力怎么做?考虑有区间有交集的颜色一定会变成一种颜色,在这些颜色中选出最多的一个不变,其它的都变成这种颜色,线性扫可以做到 \(O(cq)\) (c是颜色数量)。

    我们如何加速这一过程?太阳神给出了一条从未设想也想都不敢想的道路:线段树!线段树真的无所不能!

    1. 维护每种颜色的区间,用set随便做。

    2. 一个转化,将颜色的数量放在颜色区间的左端点,将每段最多的颜色转化为区间最大值!

    3. 一个 trick,记 \(b_i\) 为有多少个区间包含了 \(i\)\(i+1\),问题转化为求 \(\{b_i\}\) 两个 \(0\) 之间的最大值之和。

    4. observation,\(b_i \ge 0\),所以问题再转化为 \(\{b_i\}\) 的最小值之间的最大值之和。

    现在已经可以做了,修改时只需要对 \(b_i\) 进行区间加减,对颜色数量进行单点修改,魔幻的是上述这些信息都可以正常合并!

    时间复杂度 \(O((n+q)\log_2n)\).

    代码

    米奇妙妙屋
    //12252024832524
    #include <bits/stdc++.h>
    #define TT template<typename T>
    using namespace std;
    
    typedef long long LL;
    const int MAXN = 200005;
    const int INF = 0x3f3f3f3f;
    int n,m;
    int a[MAXN];
    
    LL Read()
    {
    	LL x = 0,f = 1; char c = getchar();
    	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
    	while(c >= '0' && c <= '9'){x = (x*10)+(c^48);c = getchar();}
    	return x * f;
    }
    TT void Put1(T x)
    {
    	if(x > 9) Put1(x/10);
    	putchar(x%10^48);
    }
    TT void Put(T x,char c = -1)
    {
    	if(x < 0) putchar('-'),x = -x;
    	Put1(x); if(c >= 0) putchar(c);
    }
    TT T Max(T x,T y){return x > y ? x : y;}
    TT T Min(T x,T y){return x < y ? x : y;}
    TT T Abs(T x){return x < 0 ? -x : x;}
    
    set<int> s[MAXN];
    
    #define lc (x<<1)
    #define rc (x<<1|1)
    int val[MAXN];
    struct node
    {
    	int MIN,lz,s,lm,rm,MAX;
    }t[MAXN<<2];
    void cc(int x,int v){t[x].MIN += v; t[x].lz += v;}
    void up(int x)
    {
    	t[x].MIN = Min(t[lc].MIN,t[rc].MIN);
    	t[x].MAX = Max(t[lc].MAX,t[rc].MAX);
    	if(t[lc].MIN < t[rc].MIN)
    	{
    		t[x].lm = t[lc].lm; t[x].s = t[lc].s;
    		t[x].rm = Max(t[lc].rm,t[rc].MAX);
    	}
    	else if(t[lc].MIN > t[rc].MIN)
    	{
    		t[x].rm = t[rc].rm; t[x].s = t[rc].s;
    		t[x].lm = Max(t[rc].lm,t[lc].MAX);
    	}
    	else
    	{
    		t[x].rm = t[rc].rm; t[x].lm = t[lc].lm;
    		t[x].s = t[lc].s + t[rc].s + Max(t[lc].rm,t[rc].lm);
    	}
    }
    void down(int x){if(!t[x].lz) return;cc(lc,t[x].lz); cc(rc,t[x].lz); t[x].lz = 0;}
    void Add(int x,int l,int r,int ql,int qr,int v)
    {
    	if(ql > qr) return;
    	if(ql <= l && r <= qr){cc(x,v);return;}
    	int mid = (l+r) >> 1;
    	down(x);
    	if(ql <= mid) Add(lc,l,mid,ql,qr,v);
    	if(mid+1 <= qr) Add(rc,mid+1,r,ql,qr,v);
    	up(x); 
    }
    void Change(int x,int l,int r,int pos)
    {
    	if(l == r)
    	{
    		t[x].lm = t[x].rm = t[x].MAX = val[pos];
    		return;
    	}
    	int mid = (l+r) >> 1;
    	down(x);
    	if(pos <= mid) Change(lc,l,mid,pos);
    	else Change(rc,mid+1,r,pos);
    	up(x);
    }
    
    void clr(int x)
    {
    	if(s[x].empty()) return;
    	int l = *s[x].begin(),r = *s[x].rbegin()-1;
    	val[l] = 0; Add(1,0,n,l,r,-1);
    	Change(1,0,n,l);
    }
    void calc(int x)
    {
    	if(s[x].empty()) return;
    	int l = *s[x].begin(),r = *s[x].rbegin()-1;
    	val[l] = s[x].size(); Add(1,0,n,l,r,1);
    	Change(1,0,n,l);
    }
    
    int main()
    {
    	freopen("umi.in","r",stdin);
    	freopen("umi.out","w",stdout);
    	n = Read(); m = Read(); 
    	for(int i = 1;i <= n;++ i) a[i] = Read(),clr(a[i]),s[a[i]].insert(i),calc(a[i]);
    	Put(n-t[1].s,'\n');
    	while(m --> 0)
    	{
    		int pos = Read(),color = Read();
    		clr(a[pos]); s[a[pos]].erase(s[a[pos]].lower_bound(pos)); calc(a[pos]);
    		clr(color); s[a[pos] = color].insert(pos); calc(color);
    		Put(n-t[1].s,'\n');
    	}
    	return 0;
    }
    

    总结

    区间问题大胆猜想用线段树,说不定真的能做!

    这道题学到了将序列分隔之后对所有区间求值的 trick.

  • 相关阅读:
    006_tcpdump专题
    002_阿里监控平台的“打怪升级”之路
    001_谈阿里核心业务监控平台SunFire的技术架构
    001_前端面试集合
    007_zkCli.sh
    007_Chrome的Waterfall详解
    021_supervise进行管理利器
    028_shell脚本递归求值
    MySQL -- 在磁盘爆满后复制的状态
    MySQL -- 内存使用监控详解
  • 原文地址:https://www.cnblogs.com/PPLPPL/p/15570576.html
Copyright © 2011-2022 走看看