Codeforces Round #569 (Div. 2) (D/构造+E/线段树)

思路来源

https://codeforces.com/blog/entry/67891(官方题解)

其实有时候死磕官方题解没什么不好的

D.Tolik and His Uncle

题目

有一个n*m(1<=n*m<=1e6)的网格图,初始小人位于(1,1),

下一跳可以跳到任意还没跳到的位置,例如跳到(x,y),则差向量为(x-1,y-1),

需在每一跳的差向量(dx,dy)不同下,访问所有位置,输出下一跳的序列

题解

最后向量不同的要求,就是自己在画图的时候,没有两个箭头是平行且长度相同的

考虑一维1*m的情形,应该是(1,1)跳到(1,m)跳回(1,2)跳到(1,m-1)诸如此类,由于每次长度减一,所以最终不同

那两行的也是类似的,从(1,1)跳到(2,m)跳回(1,2)跳到(2,m-1),再重复这个过程

n行的情形,从(1,1)跳到(n,m)跳回(1,2)跳到(n,m-1),由于对称性,最终落在(n,1),

再向(2,1)跳,就变成子问题n-2行了;

特别地,n是奇数的话,中间一行采用一维跳法即可

心得

构造题总有一种智商惨遭碾压的感觉……

代码

#include <bits/stdc++.h>
using namespace std;
int n,m,mid;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n/2;++i)
	{
		for(int j=1;j<=m;++j)
		{
			printf("%d %d\n",i,j);
			printf("%d %d\n",n+1-i,m+1-j);
		}
	}
	if(n&1)
	{
		mid=n-n/2;//中间那一行 
		for(int j=1;j<=m/2;++j)
		{
			printf("%d %d\n",mid,j);
			printf("%d %d\n",mid,m+1-j);
		}
		if(m&1)printf("%d %d\n",mid,m-m/2);
	}
	return 0;
}

 

E.Serge and Dining Room

题目

n(n<=3e5)个菜价,第i个菜价值ai(1<=ai<=1e6)

m(m<=3e5)个小学生,第j个人有bj块钱(1<=bj<=1e6)

每次小学生会挑自己能选的最贵的菜,挑完之后让Serge挑,

但是有q(q<=3e5)个修改,第k次修改包括op[k],pos[k],x[k],op[k]={1,2}

①1 p x 代表把a[p]的值改为x

②2 p x 代表把b[p]的值改为x

修改完之后,Serge有无限钱,会挑没被小学生选过的最贵的,

问他挑到的菜的价钱,如果没菜可挑,输出-1

题解

递减的考虑这个答案的价钱x,

一定会在某一时刻,出现大于等于价钱x的菜数比大于等于x的小学生钱数的个数多的情况,满足这个条件的最大的x就是答案

 

而且由于前面的x都不满足这一条件,所以小学生人数所拥有的钱始终可以买的了菜,

只需贪心地用递减序中第一个出现的小学生钱数买第一个出现的菜,第二个买第二个,以此类推,即可证明

 

离散化所有读入的菜价、小学生钱、修改的值,维护3*3e5的权值线段树

每个点维护大于等于这个点所代表的值的个数,即后缀和,

pushup上去左右子的最大值,最大值>0说明必有至少一子满足条件

修改时,把单点修改,变成改前面的数的后缀和的区间修改

查询时,先判整棵树有没有答案,有的话优先右子,然后左子

心得

做过cf393的E的线段树,也是维护单点后缀和,把单点修改变成改前缀和,最后询问最右的大于0的位置

可以说是代码一模一样,然而把思路转到和那个题一样就感觉很费劲啊……

不然,如果赛中能做出一道2400的E,可以说直接上紫了…… 

代码

#include<bits/stdc++.h>
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r 
using namespace std;
const int N=3e5+10;
const int V=3*N;//有多少不同的值 
int n,m,q;
int a[N],b[N],op[N],pos[N],x[N];
int c[V],cnt;//用于离线读入修改之后 将a[]b[]x[]离散化 
int suf[4*V],cov[4*V];//线段树上每个点维护后缀和 单点pos修改会对[1,pos]的后缀和都有影响 
//pushup维护后缀和的最大值 便于二分查找最后位置时先右后左 只要max>0说明左右子必有>0的 
void pushdown(int p,int l,int r)
{
	if(!cov[p])return;
	suf[p<<1]+=cov[p];
	cov[p<<1]+=cov[p];
	suf[p<<1|1]+=cov[p];
	cov[p<<1|1]+=cov[p];
	cov[p]=0;
}
void update(int p,int l,int r,int ql,int qr,int v)
{
	if(ql<=l&&r<=qr)
	{
		suf[p]+=v;
		cov[p]+=v;
		return; 
	} 
	int mid=l+r>>1;
	pushdown(p,l,r);
	if(ql<=mid)update(lson,ql,qr,v);
	if(qr>mid)update(rson,ql,qr,v);
	suf[p]=max(suf[p<<1],suf[p<<1|1]);
}
int ask(int p,int l,int r)
{
	if(l==r)return l;//按c数组建线段树 返回位置pos后c[pos]即对应位置的值
	int mid=l+r>>1;
	pushdown(p,l,r);
	if(suf[p<<1|1]>0)return ask(rson);
	else return ask(lson);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&a[i]);
		c[++cnt]=a[i];
	}
	for(int i=1;i<=m;++i)
	{
		scanf("%d",&b[i]);
		c[++cnt]=b[i];
	}
	scanf("%d",&q);
	for(int i=1;i<=q;++i)
	{
		scanf("%d%d%d",&op[i],&pos[i],&x[i]);
		c[++cnt]=x[i];
	}
	sort(c+1,c+cnt+1);
	cnt=unique(c+1,c+cnt+1)-(c+1);
	for(int i=1;i<=n;++i)
	{
		a[i]=lower_bound(c+1,c+cnt+1,a[i])-c;
		update(1,1,cnt,1,a[i],1);
	}
	for(int i=1;i<=m;++i)
	{
		b[i]=lower_bound(c+1,c+cnt+1,b[i])-c;
		update(1,1,cnt,1,b[i],-1);
	}
	for(int i=1;i<=q;++i)
	x[i]=lower_bound(c+1,c+cnt+1,x[i])-c;
	//a[]菜+1 b[]人-1 找到最后位置的后缀和>0的位置 即菜多人少的位置 
	for(int i=1;i<=q;++i)
	{
		if(op[i]==1)
		{
			update(1,1,cnt,1,a[pos[i]],-1);//删原值 
			a[pos[i]]=x[i];
			update(1,1,cnt,1,a[pos[i]],1);//加新值 
		}
		else if(op[i]==2)
		{
			update(1,1,cnt,1,b[pos[i]],1);//删原值 
			b[pos[i]]=x[i];
			update(1,1,cnt,1,b[pos[i]],-1);//加新值 
		}
		if(suf[1]<=0)puts("-1");
		else printf("%d\n",c[ask(1,1,cnt)]);
	} 
	return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值