2021-7-16 个人赛 补题

No Time to Dry

题意:
给你2e5个数,和2e5组询问。
序列代表的是最后涂成的油漆,大的数只能涂在小的数上面,而小的数不能涂在大的数上面。每次询问一个区间,求最小的操作数使得涂成上面需要的序列。
思路:
线段树 + 单调栈
涂油漆必备套路,单调栈,能预处理处最初的涂色操作数。
这道题的核心在于 离线(想到了,可惜没写),这个数据量在线是不可能的。
先把处理出next数组,就能跳同一个数的下一个位置,
那么我们就把询问离线,按照l为第一关键字,r为第二关键字排序,当区间的左边界移动时,用next数组修改下一个数为1即可,区间答案就用线段树查询就好了。

int n,m;
struct node{
	int l,r,id;
}z[200060];
int ans[200060];
int s[200060];
bool cmp(node a,node b){
	if(a.l!=b.l)return a.l<b.l;
	return a.r<b.r;
}
map<int,int>mp;
int nex[200060];
int tree[800070];
int sss[200060];
void build(int p,int l,int r){
	if(l==r){
		tree[p]=sss[l];
		return;
	}
	int mid=l+r>>1;
	build(2*p,l,mid);
	build(2*p+1,mid+1,r);
	tree[p]=tree[2*p]+tree[2*p+1];
}
void update(int p,int l,int r,int x,int y,int w){
	if(l==r){
		tree[p]=w;
		return;
	}
	int mid=l+r>>1;
	if(x<=mid)update(2*p,l,mid,x,y,w);
	if(mid<y)update(2*p+1,mid+1,r,x,y,w);
	tree[p]=tree[2*p]+tree[2*p+1];
}
int query(int p,int l,int r,int x,int y){
	if(x<=l&&r<=y){
		return tree[p];
	}
	int mid=l+r>>1,ans=0;
	if(x<=mid)ans+=query(2*p,l,mid,x,y);
	if(mid<y)ans+=query(2*p+1,mid+1,r,x,y);
	return ans;
}
stack<int>ss;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)s[i]=qread();
	for(int i=1;i<=m;i++){
		z[i].l=qread(),z[i].r=qread();
		z[i].id=i;
	}
	sort(z+1,z+1+m,cmp);
	for(int i=1;i<=n;i++){
		if(mp[s[i]]==0)mp[s[i]]=i;
		else {
			nex[mp[s[i]]]=i;
			mp[s[i]]=i;
		}
		nex[i]=n+1;
	}
	s[0]=-1;
	ss.push(0);
	for(int i=1;i<=n;i++)
	{
		while(!ss.empty()&&s[ss.top()]>s[i]){
			ss.pop();
		}
		if(s[ss.top()]!=s[i]){
			sss[i]=1;
		}
		ss.push(i);
	}
	build(1,1,n);
	int l=1;
	for(int i=1;i<=m;i++){
		while(l<z[i].l){
			if(nex[l]!=n+1)update(1,1,n,nex[l],nex[l],1);
			l++;
		}
		ans[z[i].id]=query(1,1,n,z[i].l,z[i].r);
	}
	for(int i=1;i<=m;i++){
		printf("%d\n",ans[i]);
	}
	return 0;
}

Just Green Enough

题意:
给你一个 N*N 的矩阵(N<=500),要求求出最小值刚好是100的矩阵的个数。
思路:
这道题N^4,可以很简单的解决,那么我们考虑优化到N³ ;
核心想法:将每个 大于等于100 的数看成1,将小于100的数看成0,计算出全1·矩阵数;
考虑化简问题,最小值恰好等于100的矩阵数==最小值大于等于100的矩阵数 - 最小值大于100的矩阵数。
然后 N² 处理出 next 数组,表示下一个 0 的位置,然后暴力枚举矩阵的左上角,用 next 数组得到边长,然后遍历之后的每行,边长取 min 得到长为此时的宽,加到答案里即可。
太秒啦~

int n,m;
int mp[505][505];
int nex[505][505];
int s[505][505];
void init1(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(mp[i][j]>=100)s[i][j]=1;
			else s[i][j]=0;
		}
	}
	for(int i=1;i<=n;i++){
		if(s[i][n]==1)nex[i][n]=n+1;
		else nex[i][n]=n;
		for(int j=n-1;j>=1;j--){
			if(s[i][j]==1)nex[i][j]=nex[i][j+1];
			else nex[i][j]=j;
		}
	}
}
void init2(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(mp[i][j]>100)s[i][j]=1;
			else s[i][j]=0;
		}
	}
	for(int i=1;i<=n;i++){
		if(s[i][n]==1)nex[i][n]=n+1;
		else nex[i][n]=n;
		for(int j=n-1;j>=1;j--){
			if(s[i][j]==1)nex[i][j]=nex[i][j+1];
			else nex[i][j]=j;
		}
	}
}
ll solve(){
	ll res=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(s[i][j]==0)continue;
			int t=nex[i][j];
			res+=t-j;
			for(int k=i+1;k<=n;k++){
				t=min(t,nex[k][j]);
				res+=t-j;
			}
		}
	}
	return res;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			mp[i][j]=qread();
		}
	}
	ll ans=0;
	init1();
	ans=solve();
	init2();
	ans=ans-solve();
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值