IOI2020集训队作业-15 (CF626G, CF666D, ARC102F)

A - CF626G Raffles

Sol

假设已经在第 i i i个奖池中放了 m m m张彩票,那么往第 i i i个奖池再放入一张的贡献是

p i ( m + 1 l i + m + 1 − m l i + m ) = p i ⋅ l i ( l i + m ) ( l i + m + 1 ) p_i({m+1\over l_i+m+1} - {m\over l_i+m})=p_i\cdot {l_i\over (l_i+m)(l_i+m+1)} pi(li+m+1m+1li+mm)=pi(li+m)(li+m+1)li

观察发现这个贡献是随着 m m m增加单调递减的,所以可以贪心地选择贡献最大的奖池放彩票。

此外还有一个性质,就是修改前后的最优策略,最多只会改变一张彩票投放的奖池。

以增加第 i i i个奖池中属于其他人的彩票数为例,修改前第 i i i个奖池放第 m , m + 1 m,m+1 m,m+1张彩票的贡献分别是 x 1 = p i l i ( l i + m ) ( l i + m − 1 ) , x 2 = p i ⋅ l i ( l i + m + 1 ) ( l i + m ) x_1=p_i{l_i\over (l_i+m)(l_i+m-1)},x_2=p_i\cdot {l_i\over (l_i+m+1)(l_i+m)} x1=pi(li+m)(li+m1)lix2=pi(li+m+1)(li+m)li,此时投放的贡献最小的彩票的贡献是 l i m 1 lim_1 lim1,修改后在第 i i i个奖池投放第 m , m + 1 m,m+1 m,m+1张彩票的贡献是 y 1 = p i l i + 1 ( l i + m + 1 ) ( l i + m ) , y 2 = p i ⋅ l i + 1 ( l i + m + 2 ) ( l i + m + 1 ) y_1=p_i{l_i+1\over (l_i+m+1)(l_i+m)},y_2=p_i\cdot {l_i+1\over (l_i+m+2)(l_i+m+1)} y1=pi(li+m+1)(li+m)li+1,y2=pi(li+m+2)(li+m+1)li+1,投放的贡献最小的彩票的贡献是 l i m 2 lim_2 lim2,显然有 x 1 > x 2 , y 1 > y 2 , y 1 > x 2 , l i m 2 < l i m 1 x_1> x_2,y_1> y_2,y_1>x_2,lim_2< lim_1 x1>x2,y1>y2,y1>x2,lim2<lim1。如果修改之前的最优策略是选了 m m m m + 1 m+1 m+1的,修改之后没有选 m , m + 1 m,m+1 m,m+1,那么就意味着 x 2 > l i m 1 , y 1 < l i m 2 x_2 >lim_1,y_1< lim_2 x2>lim1,y1<lim2,也就是说 y 1 < l i m 2 < l i m 1 < x 2 y_1 < lim_2 < lim_1 < x_2 y1<lim2<lim1<x2,推出矛盾。

有了这个性质,直接用集合维护每个奖池往其中放入/拿走一张彩票的贡献就可以了。时间复杂度 O ( q log ⁡ n ) O(q\log n) O(qlogn)

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <set>
#define ll long long
#define db long double
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=2e5+10;
const db eps=1e-10;
int dcmp(db x) { return x<-eps?-1:(x>eps); }
struct item {
	int id; db v;
	item(int u,db x): id(u),v(x) {}
	friend bool operator <(item A,item B) {
		if(dcmp(A.v-B.v)) return A.v<B.v;
		return A.id<B.id; 
	}
};
set<item> A,B;
int rb[N],p[N],c[N];
int n,t,q;
db ans;
inline db getA(int x) { return (db)p[x]*rb[x]/(rb[x]+c[x])/(rb[x]+c[x]+1); }
inline db getB(int x) { return (db)p[x]*rb[x]/(rb[x]+c[x])/(rb[x]+c[x]-1); }
int cnt;
void Ins(int u) {
	if(c[u]<rb[u]) A.insert(item(u,getA(u)));
	if(c[u]) B.insert(item(u,getB(u)));
}
void Del(int u) {
	if(c[u]<rb[u]) A.erase(item(u,getA(u)));
	if(c[u]) B.erase(item(u,getB(u)));
}
void ins(int u) {
	Del(u);
	ans+=getA(u);
	c[u]++,t--;
	Ins(u);
}
void del(int u) {
	Del(u);
	ans-=getB(u);
	c[u]--,t++;
	Ins(u);
}
int main() {
	rd(n),rd(t),rd(q);
	for(int i=1;i<=n;++i) rd(p[i]);
	for(int i=1;i<=n;++i) rd(rb[i]),Ins(i);
	while(t&&!A.empty()) ins(A.rbegin()->id);
	while(q--) {
		int ty,x; rd(ty),rd(x);
		
		if(c[x]==rb[x]&&ty==2) del(x);
		
		Del(x),ans-=(db)p[x]*c[x]/(rb[x]+c[x]);
		if(ty==1) rb[x]++; else rb[x]--;
		Ins(x),ans+=(db)p[x]*c[x]/(rb[x]+c[x]);
		
		if(t>0&&!A.empty()) ins(A.rbegin()->id);
		
		if(!A.empty()&&!B.empty()&&dcmp((A.rbegin()->v)-(B.begin()->v))>0) {
			int x=B.begin()->id,y=A.rbegin()->id;
			del(x),ins(y);
		}
		printf("%.10Lf\n",ans);
	}
	return 0;
}

B - CF666D Chain Reaction

Sol

首先枚举每个点是上下走还是左右走。

拿出每个点可能在的位置构成的直线,对这些直线进行去重。

如果去重后有两条竖线,两条横线,那么这四个点必须是那四个交点。注意判断四个交点是否构成正方形。

如果是一条竖线,两条横线,那么正方形的边长已经确定了,枚举另一条竖线在现在有的竖线的哪一侧就可以求出四个点的坐标。

如果只有两条平行的横线,我们相当于只知道正方形的边长。

设给出的四个点的横坐标为 x 0 , x 1 , x 2 , x 3 x_0,x_1,x_2,x_3 x0,x1,x2,x3,其中 x 0 , x 1 x_0,x_1 x0,x1在同一条横线上, x 2 , x 3 x_2,x_3 x2,x3在同一条横线上,且 x 0 < x 1 , x 2 < x 3 x_0< x_1, x_2< x_3 x0<x1,x2<x3。设正方形边长为 l l l

则我们要最小化的就是
max ⁡ { ∣ x 0 − p ∣ , ∣ x 1 − p − l ∣ , ∣ x 2 − p ∣ , ∣ x 3 − p − l ∣ } \max\{ |x_0-p|,|x_1-p-l|,|x_2-p|,|x_3-p-l|\} max{x0p,x1pl,x2p,x3pl}

u 0 = x 0 , u 1 = x 1 − l , u 2 = x 2 , u 3 = x 3 − l u_0=x_0,u_1=x_1-l,u_2=x_2,u_3=x_3-l u0=x0,u1=x1l,u2=x2,u3=x3l,在要最小化的就是数轴上 p p p u 0 , u 1 , u 2 , u 3 u_0,u_1,u_2,u_3 u0,u1,u2,u3的距离的最大值。取最大的数和最小的数的和的一半作为 p p p就可以了。

确定了四个点的坐标之后,再枚举 4 4 4的排列,找出最优的方案。

时间复杂度 O ( 2 4 ⋅ 4 ! ) O(2^4\cdot 4!) O(244!)

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define PB push_back
#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;
}
inline int Abs(int x) { return x>0?x:-x; }
struct item {
	int p,x;
	item(int p=0,int x=0): p(p),x(x) {}
	friend bool operator <(item A,item B) {
		if(A.p==B.p) return A.x<B.x;
		return A.p<B.p;
	}
};
vector<item> X,Y;
int xi[4],yi[4];
int ax[4],ay[4];
int ansx[4],ansy[4];
int ans;
void checkans() {
	int p[4]={0,1,2,3};
	do {
		int tot=0,flg=1;
		for(int i=0;i<4;++i) {
			if(xi[p[i]]!=ax[i]&&yi[p[i]]!=ay[i]) { flg=0; break; }
			tot=max(tot,Abs(xi[p[i]]-ax[i])+Abs(yi[p[i]]-ay[i]));
		}
		if(flg&&tot<ans) {
			ans=tot;
			for(int i=0;i<4;++i) ansx[i]=xi[p[i]],ansy[i]=yi[p[i]];
		}
	} while(next_permutation(p,p+4));
}
void sol(int s) {
	X.clear(),Y.clear();
	for(int i=0;i<4;++i)
		if((s>>i)&1) X.PB(item(ax[i],ay[i]));
		else Y.PB(item(ay[i],ax[i]));
	sort(X.begin(),X.end()),sort(Y.begin(),Y.end());
	
	int totx=X.size(),toty=Y.size();
	for(int i=0;i+1<X.size();++i) totx-=X[i].p==X[i+1].p;
	for(int i=0;i+1<Y.size();++i) toty-=Y[i].p==Y[i+1].p;
	if(totx>2||toty>2) return;
	if(totx==1&&toty==1) return;
	if(totx==2&&toty==2) {
		int lx=X[0].p,rx=X.back().p;
		int ly=Y[0].p,ry=Y.back().p;
		if(rx-lx!=ry-ly) return;
		xi[0]=xi[2]=lx,xi[1]=xi[3]=rx;
		yi[0]=yi[1]=ly,yi[2]=yi[3]=ry;
		checkans();
	}
	else if(toty==2) {
		int ly=Y[0].p,ry=Y.back().p,l=ry-ly;
		if(totx==0) {
			int xx[4]={Y[0].x,Y[1].x-l,Y[2].x,Y[3].x-l};
			sort(xx,xx+4);
			int q=xx[0]+xx[3]>>1;
			xi[0]=xi[2]=q,xi[1]=xi[3]=q+l;
			yi[0]=yi[1]=ly,yi[2]=yi[3]=ry;
			checkans();
		}
		else {
			int q=X[0].p;
			xi[0]=xi[2]=q,xi[1]=xi[3]=q+l;
			yi[0]=yi[1]=ly,yi[2]=yi[3]=ry;
			checkans();
			xi[0]=xi[2]=q,xi[1]=xi[3]=q-l;
			yi[0]=yi[1]=ly,yi[2]=yi[3]=ry;
			checkans();
		}
	}
	else if(totx==2) {
		int lx=X[0].p,rx=X.back().p,l=rx-lx;
		if(toty==0) {
			int yy[4]={X[0].x,X[1].x-l,X[2].x,X[3].x-l};
			sort(yy,yy+4);
			int q=yy[0]+yy[3]>>1;
			yi[0]=yi[2]=q,yi[1]=yi[3]=q+l;
			xi[0]=xi[1]=lx,xi[2]=xi[3]=rx;
			checkans();
		}
		else {
			int q=Y[0].p;
			yi[0]=yi[2]=q,yi[1]=yi[3]=q+l;
			xi[0]=xi[1]=lx,xi[2]=xi[3]=rx;
			checkans();
			yi[0]=yi[2]=q,yi[1]=yi[3]=q-l;
			xi[0]=xi[1]=lx,xi[2]=xi[3]=rx;
			checkans();
		}
	}
}
int main() {
	int T; rd(T);
	while(T--) {
		for(int i=0;i<4;++i) rd(ax[i]),rd(ay[i]);
		ans=1e9;
		for(int s=0;s<(1<<4);++s) sol(s);
		if(ans==1e9) printf("-1\n");
		else {
			printf("%d\n",ans);
			for(int i=0;i<4;++i) printf("%d %d\n",ansx[i],ansy[i]);
		}
	}
	return 0;
}

C - ARC102F Revenge of BBuBBBlesort!

Sol

1)不可能有两个相邻的数都作为过操作中的 i i i。推论是,不可能有连续的三个数,这三个数都不满足 p i = i p_i=i pi=i
2)这也就意味着我们可以将 p p p划分成若干段,使得每一段以一个 p i ≠ i p_i\not=i pi=i的元素开始,以一个 p i ≠ i p_i\not=i pi=i的元素结束,中间是 p i = i p_i=i pi=i的元素和 p i ≠ i p_i\not =i pi=i的元素交替,或者是一个 p i = i p_i=i pi=i的元素单独为一段。每一段的元素集合和这一段的下标集合是一样的,因为不可能改变属于两个不同段的元素的相对顺序。
3)考虑每一段中的 p i ≠ i p_i\not=i pi=i的元素,由于有那些 p i = i p_i=i pi=i的元素存在,所以对于每一个元素来说它始终只能够向一个方向移动(也就是说第一步向左的元素会一直向左,第一步向右的元素会一直向右)。所以一个段可以变成恒等排列的充要条件是这一段中向左走的元素随下标单调,向右走的元素随下标单调。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define PB push_back
#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 FAIL() { printf("No"); exit(0); }
const int N=3e5+10;
int n;
int p[N];
void sol(int l,int r) {
	for(int i=l;i<=r;++i) if(p[i]<l||p[i]>r) FAIL();
	vector<int> a,b;
	for(int i=l;i<=r;i+=2)
		if(p[i]>i) a.PB(p[i]);
		else b.PB(p[i]);
	for(int i=0;i+1<a.size();++i) if(a[i+1]<a[i]) FAIL();
	for(int i=0;i+1<b.size();++i) if(b[i+1]<b[i]) FAIL();
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) rd(p[i]);
	for(int i=1;i+2<=n;++i)
		if(p[i]!=i&&p[i+1]!=i+1&&p[i+2]!=i+2) FAIL();
	int l=1;
	while(1) {
		while(l<=n&&p[l]==l) l++;
		if(l>n) break;
		int r=l;
		while(r+2<=n&&p[r+1]==r+1&&p[r+2]!=r+2) r+=2;
		sol(l,r);
		l=r+1;
	}
	printf("Yes");
	return 0;
}
rd(n);
	for(int i=1;i<=n;++i) rd(p[i]);
	for(int i=1;i+2<=n;++i)
		if(p[i]!=i&&p[i+1]!=i+1&&p[i+2]!=i+2) FAIL();
	int l=1;
	while(1) {
		while(l<=n&&p[l]==l) l++;
		if(l>n) break;
		int r=l;
		while(r+2<=n&&p[r+1]==r+1&&p[r+2]!=r+2) r+=2;
		sol(l,r);
		l=r+1;
	}
	printf("Yes");
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值