IOI2020集训队作业-1 (CF549E, CF674G, ARC103F)

A - CF549E Sasha Circle

题意

平面内有两个点集 N , M N,M N,M,问能否在平面内画一个圆,使得 N N N M M M这两个点集恰好有一个点集所有点严格在圆内,另一个点集所有点严格在圆外(即两个点集都不能有点在圆上)。

坐标为 [ − 1 0 4 , 1 0 4 ] [-10^4,10^4] [104,104]的整数, ∣ N ∣ , ∣ M ∣ ≤ 1 0 4 |N|,|M|\le 10^4 N,M104

Sol

朴素算法

枚举哪个集合在圆内,假设在圆内的集合是 A A A,在圆外的集合是 B B B

枚举 A A A中的两个点 p , q p,q p,q,令圆过这两个点,则只需要再知道圆心就可以确定圆。考虑剩下的点,“限制某个点在圆内/圆外”可以转化成对圆心的限制,最后圆心可能取的位置一定是 p q pq pq中垂线上的一条线段。由于这道题要求点和圆不能交,所以取值范围的线段的端点不能够重合。容易发现,当取值范围的线段端点没有重合的时候,我们通过一定的调整就可以使得圆不经过 p , q p,q p,q

这样做的复杂度是 O ( ∣ A ∣ 2 ⋅ ( ∣ A ∣ + ∣ B ∣ ) ) O( |A|^2 \cdot (|A| + |B|)) O(A2(A+B))的。

优化

把这个问题放到三维坐标系中,原来的点在 x O y xOy xOy这个平面上。

考虑 x 2 + y 2 = z x^2 +y^2 = z x2+y2=z这个曲面,把 A , B A,B A,B这两个点集中的点垂直于 x O y xOy xOy地投影到这个曲面上得到点集 A ′ , B ′ A',B' A,B。考虑任意一个不与 x O y xOy xOy垂直的平面 a x + b y + z = c ax + by + z =c ax+by+z=c,与曲面的解析式联立可以得到 x 2 + a x + y 2 + b y = c x^2+ax+y^2+by=c x2+ax+y2+by=c,这是圆方程的形式。所以,曲面 z = x 2 + y 2 z = x^2 + y^2 z=x2+y2与任意一个不垂直于 x O y xOy xOy的平面的交集,垂直投影到平面 x O y xOy xOy上,得到的一定是一个圆;一个曲面上的点投影到 x O y xOy xOy之后在圆内,当且仅当在曲面上它在那个平面的下方。

所以,问题转化成判断能否找出一个不垂直于 x O y xOy xOy的平面, A ′ A' A所有点都在平面的下方, B ′ B' B所有点都在平面的上方。

显然只有 A ′ A' A的上凸壳的这些平面是有用的。

由于曲面 z = x 2 + y 2 z = x^2 + y^2 z=x2+y2是向下凸的,所以 A ′ A' A上凸壳顶点的点的投影一定是 A A A的凸包端点。

而上凸壳上的某一个平面,它与这个曲面的交在 x O y xOy xOy上的投影,实际上是平面在 x O y xOy xOy上的投影的外接圆。也就是说,上凸壳上的每一个多边形在 x O y xOy xOy的投影都满足:1)所有端点共圆;2)外接圆都包含了 A A A中的所有点。

如图所示(官方题解里的图):

IOI2020集训队作业-1 (CF549E, CF674G, ARC103F)_第1张图片

故而我们可以:将 A A A的凸包划分成若干个三角形,使得每一个三角形的外接圆都包含 A A A中的所有点,然后对三角形上的每一条边进行前面的朴素算法。

由于三维中的上凸壳是存在且唯一的,所以这样的划分方式一定存在,并且当 A A A没有四点共圆的时候是唯一的。

这种三角剖分称为Anti-Delaunay Triagulation。

求出三角剖分之后计算答案的复杂度是 O ( C 2 3 ( ∣ N ∣ + ∣ M ∣ ) ) O (C^{2\over 3}(|N|+|M|)) O(C32(N+M))(其中 C C C是坐标绝对值大小, C 2 3 C^{2\over 3} C32是凸包点数的最大值),可以接受。

现在还剩下的问题是如何在 O ( ( C 2 3 ) 2 ) O((C^{2\over 3})^2) O((C32)2)以下的时间复杂度内求出三角剖分。

求一个凸多边形的Anti-Delaunay Triagulation

首先随便选择多边形的一条边 p 1 p 2 p_1p_2 p1p2,然后找出多边形上的另一个端点 p k p_k pk使得 p 1 , p 2 , p k p_1,p_2,p_k p1,p2,pk确定圆的半径最大。由于凸多边形的三角剖分一定存在,所以这个外接圆一定包含凸多边形的所有端点;又因为当没有四点共圆的情况的时候三角剖分唯一,所以这个多边形的三角剖分中一定有 △ p 1 p 2 p k \triangle p_1p_2p_k p1p2pk。这样我们就将原问题转化成了由 p 1 p k p_1p_k p1pk p 2 p k p_2p_k p2pk划开的两个规模更小的子问题,递归求解即可。

设多边形的端点数为 n n n,则复杂度 O ( n 2 ) O(n^2) O(n2)

实现细节

  • 在进行那个朴素算法的时候,首先没有必要去算圆心 o o o,因为我们只需要确定从 o p ⃗ \vec{op} op o q ⃗ \vec{oq} oq 的角的大小就可以了。而这个角的大小等于圆上的第三个点与 p , q p,q p,q的夹角的二分之一。所以我们只需要考虑 A , B A,B A,B中的其它点与 p , q p,q p,q的夹角带来的限制就可以了。而比较角的大小这里用比较余切值的大小比较方便,只需要特殊考虑点与 p q pq pq贡献的情况。
    假设我们现在在检查的点是 u u u ∠ p u q = α , ∠ p o q = β \angle puq = \alpha,\angle poq=\beta puq=α,poq=β,注意这里角的大小是带符号的。

    • 要求 u u u在圆内:

      • p u ⃗ \vec{pu} pu p q ⃗ \vec{pq} pq 的逆时针方向:那么则要求 cot ⁡ β > cot ⁡ α \cot \beta > \cot \alpha cotβ>cotα
      • p u ⃗ \vec{pu} pu p q ⃗ \vec{pq} pq 的顺时针方向:那么则要求 cot ⁡ β < cot ⁡ α \cot \beta < \cot \alpha cotβ<cotα
    • 要求 u u u在圆外:

      • p u ⃗ \vec{pu} pu p q ⃗ \vec{pq} pq 的逆时针方向:那么则要求 cot ⁡ β < cot ⁡ α \cot \beta < \cot \alpha cotβ<cotα
      • p u ⃗ \vec{pu} pu p q ⃗ \vec{pq} pq 的顺时针方向:那么则要求 cot ⁡ β > cot ⁡ α \cot \beta > \cot \alpha cotβ>cotα
    • 此外还要特殊考虑 u u u p q pq pq共线的情况。

  • 求两个向量的夹角的余切值可以用点积除以叉积实现。

Code


#include 
#include 
#include 
#include 
#include 
#define db long double
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}

void FUCK() { printf("YES"); exit(0); }
const int N=1e4+10;
const db eps=1e-12;
int sgn(db x) { return x>-eps?(x>eps):-1; }
struct Point {
	db x,y;
	Point(db x=0,db y=0): x(x),y(y) {}
	friend Point operator +(Point A,Point B) { return Point(A.x+B.x,A.y+B.y); }
	friend Point operator -(Point A,Point B) { return Point(A.x-B.x,A.y-B.y); }
	friend Point operator *(Point A,db B) { return Point(A.x*B,A.y*B); }
	friend bool operator <(Point A,Point B) { return sgn(A.x-B.x)==0?A.y<B.y:A.x<B.x; }
};
db Dot(Point A,Point B) { return A.x*B.x+A.y*B.y; }
db Cross(Point A,Point B) { return A.x*B.y-A.y*B.x; }

namespace Calculate_Convex_Hull {
	db ag[N]; int rnk[N];
	Point tp[N];
	bool cmp(int x,int y) { return sgn(ag[x]-ag[y])==0?tp[x]<tp[y]:ag[x]<ag[y]; }
	void Convex_Hull(Point *p,int n,Point *q,int &m) {
		for(int i=1;i<n;++i) if(sgn(p[i].x-p[0].x)<0||sgn(p[i].x-p[0].x)==0&&sgn(p[i].y-p[0].y)<0) swap(p[i],p[0]);
		for(int i=1;i<n;++i) ag[i]=atan2(p[i].y-p[0].y,p[i].x-p[0].x),rnk[i]=i,tp[i]=p[i];
		sort(rnk+1,rnk+n,cmp);
		q[m=0]=p[0];
		for(int i=1;i<n;++i) {
			Point cur=p[rnk[i]];
			while(m&&sgn(Cross(q[m]-q[m-1],cur-q[m-1]))<=0) m--;
			q[++m]=cur;
		}
		m++;
	}
}
using Calculate_Convex_Hull::Convex_Hull;

void upd(Point p,Point q,Point *A,int n,Point *B,int m) { // A->inner  B->exterior
	db l=-1e100,r=1e100;
	for(int i=0;i<n;++i)
		if(sgn(Cross(p-A[i],q-A[i]))) {
			if(sgn(Cross(A[i]-p,q-p))<0) l=max(l,Dot(p-A[i],q-A[i])/Cross(p-A[i],q-A[i]));
			else r=min(r,Dot(p-A[i],q-A[i])/Cross(p-A[i],q-A[i]));
		}
	for(int i=0;i<m;++i) {
		if(sgn(Cross(p-B[i],q-B[i]))==0) {
			if(sgn(min(p.x,q.x)-B[i].x)>0||sgn(B[i].x-max(p.x,q.x))>0) continue;
			if(sgn(min(p.y,q.y)-B[i].y)>0||sgn(B[i].y-max(p.y,q.y))>0) continue;
			return;
		}
		if(sgn(Cross(B[i]-p,q-p))<0) r=min(r,Dot(p-B[i],q-B[i])/Cross(p-B[i],q-B[i]));
		else l=max(l,Dot(p-B[i],q-B[i])/Cross(p-B[i],q-B[i]));
		if(l>=r-eps) return;
	}
	FUCK();
}

Point p[N],A[N],B[N];
int s1,s2,n;
void sol(int l,int r) {
	if((l+1)%n==r) { upd(p[l],p[r],p,n,B,s2); return; }
	int x; db mx=1e100;
	for(int i=(l+1)%n;i!=r;i=(i+1)%n) {
		db tmp=Dot(p[i]-p[l],p[i]-p[r])/Cross(p[i]-p[l],p[i]-p[r]);
		if(tmp<mx) mx=tmp,x=i;
	}
	upd(p[x],p[l],p,n,B,s2);
	upd(p[x],p[r],p,n,B,s2);
	sol(l,x),sol(x,r);
}
void work() {
	Convex_Hull(A,s1,p,n);
	upd(p[0],p[n-1],p,n,B,s2);
	sol(0,n-1);
}
int main() {
	rd(s1),rd(s2);
	for(int i=0;i<s1;++i) scanf("%Lf%Lf",&A[i].x,&A[i].y);
	for(int i=0;i<s2;++i) scanf("%Lf%Lf",&B[i].x,&B[i].y);
	work();
	for(int i=0;i<max(s1,s2);++i) swap(A[i],B[i]); swap(s1,s2);
	work();
	printf("NO");
	return 0;
}

B - CF674G Choosing Ads

题意

有一个长度为 n n n的序列,初始序列的第 i i i个元素是 a i a_i ai。有一个给定的常量 p ( 20 ≤ p ≤ 100 ) , p ∈ N p (20 \le p \le 100),p\in N p(20p100),pN

q q q次操作,操作有两种:1)将一个区间内的所有元素全部赋值为某个值。2)对一个区间进行询问,你需要输出至多 ⌊ 100 p ⌋ \lfloor {100 \over p} \rfloor p100个数字(这些数字可以重复),并且你输出的数字必须包含所有在这个区间内出现次数大于了 ⌈ ( r − l + 1 ) ⋅ p 100 ⌉ \lceil {(r-l+1)\cdot {p\over 100}}\rceil (rl+1)100p的数字。

n , q ≤ 150000 n,q\le 150000 n,q150000,元素取值是不超过 150000 150000 150000的正整数。

我的思路

如果一个元素在两个区间的并中的占据的位置超过了这两个区间并的大小的 p % p \% p%,那么这个元素应该在这两个区间中的至少一个区间里占了这个区间的至少 p % p\% p%的位置。所以可以在线段树上维护每个区间内占据位置超过 p % p\% p%的元素,用另外一个数据结构维护区间内某个颜色的出现次数(可以对每种颜色开一个动态开点线段树,最后用一个线段树维护是否区间内全部同色,修改的时候递归到区间内全部同色的时候在对应的颜色的动态开点线段树上修改就可以了,修改的复杂度 O ( log ⁡ 2 n ) O(\log^ 2 n) O(log2n),查询的复杂度 O ( log ⁡ n ) O(\log n) O(logn)),区间合并的时候枚举一下两个区间内占据位置超过 p % p \% p%的元素,查询是否在两个区间的并中也占据了 p % p \% p%的位置。这样的复杂度是 O ( n log ⁡ 2 n ⋅ ⌊ 100 p ⌋ ) O( n \log ^ 2 n \cdot \lfloor {100 \over p} \rfloor) O(nlog2np100)的,由于常数也比较大,无法通过。

Sol

如果是求占据了至少 51 % 51\% 51%的位置的元素,有这样的一种 O ( n ) O(n) O(n)算法:依次扫描整个区间中的元素,维护一个元素值 x x x x x x的“权重” v v v。首先将 x x x赋为第一个元素,将 v v v置为 1 1 1。如果接下来扫到的元素不等于 x x x,则 v − − v-- v;如果接下来扫到的元素等于 x x x,则 v + + v++ v++。如果某个时刻 v = 0 v=0 v=0,则让 x x x成为下一个扫到的元素。

观察发现:1)如果存在一个数 a a a至少占据了 51 % 51\% 51%的位置,那么它成为最后的 x x x。2)占据至少 51 % 51\% 51%的位置的元素至多只有一个。所以,这个算法求出来的就是那个“唯一一个可能占据至少 51 % 51\% 51%的位置”的元素。

这个算法还可以扩展到求占据了至少 ⌈ 1 k ⌉ \lceil {1\over k} \rceil k1的位置的元素。维护一个集合,集合中的元素是二元组 ( x , v ) (x,v) (x,v)。假设当前扫到的元素是 a a a:1)如果 a a a在集合中作为 x x x出现过,则让对应的 v + + v++ v++;2)如果集合的大小不到 k k k,加入 ( a , 1 ) (a,1) (a,1);2)否则,让集合中所有元素的 v − − v-- v。如果有元素的 v v v变成 0 0 0则把它从集合中删除。

v v v一定大于等于( x x x的出现次数 - 不是 x x x元素数量 * 1 k 1 \over k k1),因为至少要加入 k k k个不是 x x x的元素才能让 v v v 1 1 1。也就是说,出现次数大于等于总数的 1 k + 1 1\over k+1 k+11的元素一定能在最后保持权值非负。又因为不可能同时出现 k k k个占据 1 k + 1 1\over k+1 k+11的元素和 1 1 1占据 1 k 1\over k k1的元素(因为 k k + 1 + 1 k > 1 {k \over k+1}+ {1\over k } > 1 k+1k+k1>1),所以所有的占据位置超过 1 k 1\over k k1一定会出现在最终的集合中。

对于这道题,我们需要考虑如何合并两个区间的集合。枚举一个集合中的二元组 ( x , v ) (x,v) (x,v),把它加入另一个集合:1)如果 x x x出现过,那么直接增加相应的 v v v就可以了。2)如果另一个集合的大小不到 k k k,则直接加入这个二元组。3)否则,如果 v v v小于集合中所有元素的权值,则让集合中所有元素的权值都减 v v v。4)否则找出集合中 v v v最小的元素删掉,把当前这个二元组加入集合,然后把集合中的所有元素的权值都减去删掉的那个元素的权值。

容易发现:1)这样子仍然可以保持“每个元素的权值大于等于( x x x的出现次数 - 不是 x x x元素数量 * 1 k 1 \over k k1)的性质。2)把权值最小的元素剔除也是唯一一个保持集合中元素权值非负的方案。故而在两个区间的并中占据位置的数量超过 1 k 1\over k k1的元素一定会出现在最终的集合中。

Code

我的TLE的做法

#include 
#include 
#include 
#include 
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1.5e5+10;
int n;
namespace tr1 {
	const int M=8*18*N;
	int sum[M],tg[M],ls[M],rs[M];
	int ncnt,stk[M],top;
	int rt[N];
	void rec(int c) { if(top<M) stk[top++]=c; }
	int new_node() {
		int u=top?stk[--top]:++ncnt;
		ls[u]=rs[u]=sum[u]=tg[u]=0; return u;
	}
	void push_up(int c,int len) {
		sum[c]=sum[ls[c]]+sum[rs[c]]+tg[c]*len;
	}
	int ql,qr,qt;
	void upd(int l,int r,int &c) {
		if(!c) c=new_node();
		if(ql<=l&&qr>=r) {
			tg[c]+=qt;
			sum[c]+=qt*(r-l+1);
			if(!sum[c]&&!tg[c]&&!ls[c]&&!rs[c]) rec(c),c=0;
			return;
		}
		int mid=l+r>>1;
		if(ql<=mid) upd(l,mid,ls[c]);
		if(qr>mid) upd(mid+1,r,rs[c]);
		push_up(c,r-l+1);
	}
	void upd(int RT,int l,int r,int t) { ql=l,qr=r,qt=t,upd(1,n,rt[RT]); }
	int query(int l,int r,int c,int t) {
		if(!c||(ql<=l&&qr>=r)) return sum[c]+t*(min(qr,r)-max(ql,l)+1);
		int mid=l+r>>1,ans=0; t+=tg[c];
		if(ql<=mid) ans+=query(l,mid,ls[c],t);
		if(qr>mid) ans+=query(mid+1,r,rs[c],t);
		return ans;
	}
	int query(int RT,int l,int r) { ql=l,qr=r; return query(1,n,rt[RT],0); }
}

int _col[N];
namespace tr2 {
	#define ls (c<<1)
	#define rs (c<<1|1)
	const int M=N*4;
	struct item {
		int flg,c;
		item(int c=0): flg(1),c(c) {}
		friend item operator +(item A,item B) {
			A.flg&=B.flg&&(A.c==B.c);
			return A;
		}
		
	}sum[M];
	int tg[M];
	void cov(int x,int c) {
		sum[x]=item(c);
		tg[x]=1;
	}
	void push_down(int c) {
		if(tg[c]) {
			cov(ls,sum[c].c);
			cov(rs,sum[c].c);
			tg[c]=0;
		}
	}
	void push_up(int c) { sum[c]=sum[ls]+sum[rs]; }
	void build(int l,int r,int c) {
		if(l==r) {
			sum[c]=item(_col[l]);
			tr1::upd(_col[l],l,l,1);
			return;
		}
		int mid=l+r>>1;
		build(l,mid,c<<1),build(mid+1,r,c<<1|1);
		push_up(c);
	}
	int ql,qr,qt;
	void upd(int l,int r,int c) {
		if(ql<=l&&qr>=r&&sum[c].flg) {
			tr1::upd(sum[c].c,l,r,-1);
			tr1::upd(qt,l,r,1);
			cov(c,qt);
			return;
		}
		int mid=l+r>>1; push_down(c);
		if(ql<=mid) upd(l,mid,c<<1);
		if(qr>mid) upd(mid+1,r,c<<1|1);
		push_up(c);
	}
	void UPD(int l,int r,int t) { ql=l,qr=r,qt=t; upd(1,n,1); }
}
int P;
namespace tr3 {
	#define ls (c<<1)
	const int M=N*4;
	struct item {
		int a[10],cnt;
		item() { cnt=0; }
		void add(int x) { for(int i=0;i<cnt;++i) if(a[i]==x) return; a[cnt++]=x; }
	}sum[M];
	int tg[M];
	void add(int x,int c) {
		sum[x].cnt=1,sum[x].a[0]=c;
		tg[x]=1;
	}
	void push_down(int c) {
		if(tg[c]) {
			add(ls,sum[c].a[0]);
			add(rs,sum[c].a[0]);
			tg[c]=0;
		}
	}
	item un(item A,item B,int l,int r) {
		item C;
		for(int i=0;i<A.cnt;++i) if(tr1::query(A.a[i],l,r)>=((P*(r-l+1)+99)/100)) C.add(A.a[i]);
		for(int i=0;i<B.cnt;++i) if(tr1::query(B.a[i],l,r)>=((P*(r-l+1)+99)/100)) C.add(B.a[i]);
		return C;
	}
	void push_up(int c,int l,int r) { sum[c]=un(sum[ls],sum[rs],l,r); }
	void build(int l,int r,int c) {
		if(l==r) return (void)(sum[c].add(_col[l]));
		int mid=l+r>>1;
		build(l,mid,c<<1),build(mid+1,r,c<<1|1);
		push_up(c,l,r);
	}
	int ql,qr,qt;
	void upd(int l,int r,int c) {
		if(ql<=l&&qr>=r) return add(c,qt);
		int mid=l+r>>1; push_down(c);
		if(ql<=mid) upd(l,mid,c<<1);
		if(qr>mid) upd(mid+1,r,c<<1|1);
		push_up(c,l,r);
	}
	void UPD(int l,int r,int c) { ql=l,qr=r,qt=c; upd(1,n,1); }
	item qans;
	void query(int l,int r,int c) {
		if(ql<=l&&qr>=r) return (void)(qans=un(qans,sum[c],ql,qr));
		int mid=l+r>>1; push_down(c);
		if(ql<=mid) query(l,mid,c<<1);
		if(qr>mid) query(mid+1,r,c<<1|1);
	}
	void query(int l,int r) {
		qans.cnt=0; ql=l,qr=r;
		query(1,n,1);
		printf("%d ",qans.cnt);
		for(int i=0;i<qans.cnt;++i) printf("%d ",qans.a[i]); puts("");
	}
}
int main() {
	int q;
	rd(n),rd(q),rd(P);
	for(int i=1;i<=n;++i) rd(_col[i]);
	tr2::build(1,n,1);
	tr3::build(1,n,1);
	while(q--) {
		int op,l,r; rd(op),rd(l),rd(r);
		if(op==1) {
			int x; rd(x);
			tr2::UPD(l,r,x);
			tr3::UPD(l,r,x);
		}
		else tr3::query(l,r);
	}
	return 0;
}

官方题解做法

#include 
#include 
#include 
#include 
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
#define ls (c<<1)
#define rs (c<<1|1)
const int N=1.5e5+10;
int _col[N],lim,n;
struct node {
	int c,v;
	node(int c=1,int v=0): c(c),v(v) {}
	friend bool operator >(node A,node B) { return A.v==B.v?A.c>B.c:A.v>B.v; }
}tmp[10];
struct item {
	node a[5];
	int s;
	item() { s=0; }
	void init(int c,int cnt) {
		a[0]=node(c,cnt),s=1;
	}
	friend item operator +(item A,item B) {
		item C; int tot=0;
		for(int i=0;i<A.s;++i) {
			int flg=0;
			for(int j=0;j<tot;++j)
				if(tmp[j].c==A.a[i].c) { tmp[j].v+=A.a[i].v,flg=1; break; }
			if(!flg) tmp[tot++]=A.a[i];
		}
		for(int i=0;i<B.s;++i) {
			int flg=0;
			for(int j=0;j<tot;++j)
				if(tmp[j].c==B.a[i].c) { tmp[j].v+=B.a[i].v,flg=1; break; }
			if(!flg) tmp[tot++]=B.a[i];
		}
		for(int i=0;i<tot;++i) {
			int flg=0;
			for(int j=0;j<C.s;++j)
				if(C.a[j].c==tmp[i].c) { C.a[j].v+=tmp[i].v; flg=1; break; }
			if(flg) continue;
			
			if(C.s<lim) {
				C.a[C.s++]=tmp[i];
				continue;
			}
			
			int p=0;
			for(int j=1;j<C.s;++j) if(C.a[j].v<C.a[p].v) p=j;
			if(C.a[p].v>tmp[i].v) for(int j=0;j<C.s;++j) C.a[j].v-=tmp[i].v;
			else {
				int d=C.a[p].v; C.a[p]=tmp[i];
				for(int j=0;j<C.s;++j) C.a[j].v-=d;
			}
		}
		return C;
        
//		int __=0;
//		for(int i=lim;i
//		for(int i=0;i
//			tmp[i].v-=__;
//			if(tmp[i].v<=0) break;
//			C.a[C.s++]=tmp[i];
//		}
//		return C;
	}
}sum[N*4];
int tg[N*4],tc[N*4];
void add(int c,int t,int len) {
	sum[c].init(t,len);
	tc[c]=t,tg[c]=1;
}
void push_down(int c,int l,int r) {
	if(tg[c]) {
		int mid=l+r>>1;
		add(ls,tc[c],mid-l+1);
		add(rs,tc[c],r-mid);
		tg[c]=0;
	}
}
void push_up(int c) { sum[c]=sum[ls]+sum[rs]; }
void build(int l,int r,int c) {
	if(l==r) return sum[c].init(_col[l],1);
	int mid=l+r>>1;
	build(l,mid,c<<1),build(mid+1,r,c<<1|1);
	push_up(c);
}
int ql,qr,qt;
item qans;
void update(int l,int r,int c) {
	if(ql<=l&&qr>=r) return add(c,qt,r-l+1);
	int mid=l+r>>1; push_down(c,l,r);
	if(ql<=mid) update(l,mid,c<<1);
	if(qr>mid) update(mid+1,r,c<<1|1);
	push_up(c);
}
void query(int l,int r,int c) {
	if(ql<=l&&qr>=r) return (void)(qans=qans+sum[c]);
	int mid=l+r>>1; push_down(c,l,r);
	if(ql<=mid) query(l,mid,c<<1);
	if(qr>mid) query(mid+1,r,c<<1|1);
}
int main() {
	int q,p; rd(n),rd(q),rd(p);
	lim=100/p;
	for(int i=1;i<=n;++i) rd(_col[i]);
	build(1,n,1);
	while(q--) {
		int op; rd(op),rd(ql),rd(qr);
		if(op==1) {	
			rd(qt);
			update(1,n,1);
		}
		else {
			qans.s=0;
			query(1,n,1);
			printf("%d ",qans.s);
			for(int i=0;i<qans.s;++i) printf("%d ",qans.a[i].c);
			puts("");
		}
	}
	return 0;
}

C - ARC103F Distance Sums

题意

有一个长度为 N N N的序列 D 1 , D 2 ⋯ D N D_1,D_2 \cdots D_N D1,D2DN,其中的元素两两不同。你需要构造一棵树,树的节点编号为 1 1 1 N N N,满足树上第 i i i个点到其它所有点的距离的和是 D i D_i Di。无解输出 − 1 -1 1。这里两个点之间的距离定义为最短路径上的边数。 N ≤ 1 0 5 , D i ≤ 1 0 12 N \le 10^5,D_i \le 10^{12} N105,Di1012

Sol

把这棵树想成有根树,树根是重心。则每个点的 D i D_i Di将大于父亲的 D i D_i Di(除非是双重心,会出现两个相邻的重心 D i D_i Di相等)。

D i D_i Di最大的点一定是叶子。对于某个叶子 x x x,它的父亲的 D i D_i Di应该等于 D x − ( n − 1 ) + 1 D_x - (n-1) + 1 Dx(n1)+1,又因为 D i D_i Di是互不相同的,所以 D i D_i Di最大的那个点的父亲可以确定。

推广一下,对于还没有确定父亲的节点,记 s z [ u ] sz[u] sz[u]表示 u u u子树内已经确定了父亲的节点的点数。每一次取出 D i D_i Di最大的 u u u,此时它一定不可能再成为其它未确定父亲的节点的父亲了(因为儿子的 D i D_i Di大于父亲的 D i D_i Di),而它的父亲的 D i D_i Di就应该是 D u − ( n − s z [ u ] ) + s z [ u ] D_u - (n-sz[u]) + sz[u] Du(nsz[u])+sz[u],是可以唯一确定的。在 u u u u u u的父亲之间连边,并更新 u u u的父亲的 s z sz sz。重复以上过程即可。

由于构造的过程只考虑了相邻的点的 D i D_i Di的差要满足条件,所以最后还要检查一下根节点的 D i D_i Di是否与构造出来的树一样,

时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)

Code

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define PB push_back
#define MP make_pair
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1e5+10;
struct node {
	int id; ll d;
	node(int id=0,ll d=0): id(id),d(d) {}
	friend bool operator <(node A,node B) { return A.d<B.d; }
};
priority_queue<node> que;
map<ll,int> mp;
int n,sz[N];
ll d[N];
vector< pair<int,int> > E;
vector<int> son[N];
ll dfs(int u) {
	ll tot=0;
	for(int i=0;i<son[u].size();++i) tot+=dfs(son[u][i])+sz[son[u][i]];
	return tot;
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) {
		rd(d[i]),mp[d[i]]=i;
		que.push(node(i,d[i]));
		sz[i]=1;
	}
	while(que.size()>1) {
		int u=que.top().id; que.pop();
		mp.erase(d[u]);
		ll t=d[u]-(n-sz[u]*2ll);
		if(!mp.count(t)) {
			printf("-1");
			return 0;
		}
		int f=mp[t];
		E.PB(MP(f,u));
		son[f].PB(u);
		sz[f]+=sz[u];
	}
	int rt=que.top().id;
	if(dfs(rt)!=d[rt]) {
		printf("-1");
		return 0;
	}
	for(int i=0;i<E.size();++i) printf("%d %d\n",E[i].first,E[i].second);
	return 0;
}
	for(int i=0;i<son[u].size();++i) tot+=dfs(son[u][i])+sz[son[u][i]];
	return tot;
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) {
		rd(d[i]),mp[d[i]]=i;
		que.push(node(i,d[i]));
		sz[i]=1;
	}
	while(que.size()>1) {
		int u=que.top().id; que.pop();
		mp.erase(d[u]);
		ll t=d[u]-(n-sz[u]*2ll);
		if(!mp.count(t)) {
			printf("-1");
			return 0;
		}
		int f=mp[t];
		E.PB(MP(f,u));
		son[f].PB(u);
		sz[f]+=sz[u];
	}
	int rt=que.top().id;
	if(dfs(rt)!=d[rt]) {
		printf("-1");
		return 0;
	}
	for(int i=0;i<E.size();++i) printf("%d %d\n",E[i].first,E[i].second);
	return 0;
}

你可能感兴趣的:(IOI2020集训队作业-1 (CF549E, CF674G, ARC103F))