Codeforces Round 882 (Div. 2) D. Professor Higashikata

传送门:CF

前题提要:作为一个DS选手,在赛时没有解出这道题感觉还是有一点遗憾的.其实感觉在比赛的时候,想到的trick已经和正解很接近了,但是由于一些经验问题,导致脑子里想的不是很清楚.所以最后还是没做出来.


首先对于这道题.我们先来简单分析一下T(S)如何取得最大的字典序.因为我们的字符串只有0/1.所以显然应该尽量的让1放前面才行.我们再来仔细考虑一下这个.我们会发现假设我们的T(s)里面存在两个字符,这两个字符是由S中的同一个位置而来的,那么后面字符的0/1将会由前面的那一个字符来决定.也就是当前面的那一个字符变成1之后,后面的那个字符也会跟着变成1.所以后面的那一个字符我们就可以不用管了.

考虑记录S中每一个字符在T(S)中第一个出现第一个出现的位置.然后按照出现位置的顺序来排序.举一个例子:
假设我们的T(S)是6562131(其中数字代表该字符在S中的位置从1开始),那么我们记录的就是65213,也就是计算出T(S)中第一个出现的字符在S中的位置,第二个,第三个等等.
那么显然的,假设我们想让字典序最大,我们需要尽量的将这些数字从前往后变成1,也就是先满足6为1,再满足5为1,等等,简单来说就是先算出每一个位置变1的优先级,那么我们此时的花费,就是尽量将其变为1的花费.
那么这个花费是多少呢,我们可以算出S中所有1的个数记为K,然后算出T(S)去重后中前K位1的个数,记为K2,那么我们的花费就是K-K2.(当然此时有特例,比如去重后的字符的总个数可能小于K,这个简单特判一下即可,此处不展开).


本题的trick到这里就基本结束了

接下来考虑如何来维护上述需要的所有信息.

首先是如何计算出每一个位置变1的优先级呢.这个可以用很多数据结构来维护,在网上找题解,你会发现大多数人会告诉你这个维护方式很.(别问为什么我知道,问就是我查过).但是对于本人来说,我致力于讲清楚每一点.(因为说典不如不发表题解)

考虑到网上各大博客题解绝大部分都是并查集,但是并查集维护本题的思想又比较难懂.所以我准备讲讲简单的线段树做法.对于每一个给出的点,我们先维护这个点第一次出现的区间位置.这个维护方式有大致两种,我们可以正着来维护,也可以倒着来维护.正着来维护的话,我们需要维护min值,但是倒着来维护的话,我们只要区间覆盖即可(因为倒着来枚举区间显然后来覆盖的是最优的).我这里选择倒着来维护.这个维护十分简单,如果没有看懂可以看看具体实现的代码,相信你会很快明白的(这个是真简单,不同于并查集做法)

然后对于两个点,假设他们处于不同区间,显然按照区间顺序来排序.假设他们处于同一区间,我们按照点在S中的位置顺序来排序.,此时我们就维护好了每一个点的优先级.


对于上述中的K,我们其实很好维护.因为每一个询问是单点修改,我们直接修改即可.
然后考虑如何维护前缀1的个数.在这里,我们直接使用树状数组来维护单点修改即可.(线段树当然也行,但是码量较大).对于刚开始存在的1,我们先加入树状数组中.然后对于每一次修改,我们在树状数组中进行相应的修改即可.

本题感觉刚开始想还是比较绕的,建议结合代码食用.


下面是具体的代码部分;

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
struct Segment_tree{
	int l,r,first_pos,lazy;
}tree[maxn<<2];
void build(int l,int r,int rt) {
	tree[rt].l=l;tree[rt].r=r;tree[rt].first_pos=int_INF;
	if(l==r) {
		return ;
	}
	int mid=(l+r)>>1;
	build(lson);build(rson);
}
void change(int rt,int val) {
	tree[rt].first_pos=val;tree[rt].lazy=val;
}
void pushdown(int rt) {
	change(ls,tree[rt].lazy);change(rs,tree[rt].lazy);
	tree[rt].lazy=0;
}
void update(int l,int r,int rt,int val) {
	if(tree[rt].l==l&&tree[rt].r==r) {
		change(rt,val);
		return ;
	}
	if(tree[rt].lazy) pushdown(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(r<=mid) update(l,r,ls,val);
	else if(l>mid) update(l,r,rs,val);
	else update(l,mid,ls,val),update(mid+1,r,rs,val);
}
int query(int pos,int rt) {
	if(tree[rt].l==pos&&tree[rt].r==pos) {
		return tree[rt].first_pos;
	}
	if(tree[rt].lazy) pushdown(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(pos<=mid) return query(pos,ls);
	else return query(pos,rs);
}
int l[maxn],r[maxn];
struct Node{
	int id,pos;
}node[maxn];
bool cmp(Node a,Node b) {
	return a.pos==b.pos?a.id<b.id:a.pos<b.pos;
}
int Bit_tree[maxn];int n,m,q;
int lowbit(int x) {
	return x&(~x+1);
}
int len;
void Add(int pos,int val) {
	while(pos<=len) {
		Bit_tree[pos]+=val;
		pos+=lowbit(pos);
	}
}
int Query(int pos) {
	int ans=0;
	while(pos) {
		ans+=Bit_tree[pos];
		pos-=lowbit(pos);
	}
	return ans;
}
int main() {
	n=read();m=read();q=read();
	string s;cin>>s;
	int sum=0;
	for(int i=0;i<s.length();i++) sum+=s[i]=='1';
	for(int i=1;i<=m;i++) {
		l[i]=read();r[i]=read();
	}
	build(root);
	for(int i=m;i>=1;i--) {
		update(l[i],r[i],1,i);
	}
	for(int i=1;i<=n;i++) {
		node[i].id=i;node[i].pos=query(i,1);
	}
	sort(node+1,node+n+1,cmp);
	len=n;
	while(node[len].pos==int_INF) len--;
	map<int,int>mp;
	for(int i=1;i<=len;i++) {
		Add(i,s[node[i].id-1]=='1');
		mp[node[i].id]=i;
	}
	for(int i=1;i<=q;i++) {
		int x=read();
		if(s[x-1]=='0') {
			s[x-1]='1';sum++;
			if(mp[x]!=0) Add(mp[x],1);
			cout<<min(sum,len)-Query(min(sum,len))<<endl;
		}
		else {
			s[x-1]='0';sum--;
			if(mp[x]!=0) Add(mp[x],-1);
			cout<<min(sum,len)-Query(min(sum,len))<<endl;
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值