IOI2020集训队作业-7 (CF573E, CF704E, ARC103D)

A - CF573E Bear and Bowling

题意

定义一个长度为 m m m的序列 s 1 , s 2 ⋯ s m s_1,s_2\cdots s_m s1,s2sm的价值为 ∑ i = 1 m i ⋅ s i \sum_{i=1}^m i \cdot s_i i=1misi

有一个长度为 n n n的数列 a 1 , a 2 ⋯ a n a_1,a_2\cdots a_n a1,a2an。你需要选出这个序列的一个子序列(可以为空也可以全部选),使得子序列的价值最大,并输出最大价值。

n ≤ 1 0 5 , ∣ a i ∣ ≤ 1 0 7 n\le 10^5, |a_i| \le 10^7 n105,ai107

Sol

如果在一个子序列的基础上再加入一个元素,那么这个元素的贡献就是(这个元素的值 * (这个元素前面已经被加入子序列的元素个数+1) + 这个元素后面已经被加入子序列的元素的和)。

可以证明,每一次取出贡献最大的元素加入,将可以对于所有的 s ∈ [ 0 , n ] s\in [0,n] s[0,n]得到长度为 s s s的价值最大的子序列。

Lemma 1:如果 i < j i< j i<j a i > a j a_i> a_j ai>aj,那么我们一定不会先取出 a j a_j aj

proof:假设引理不成立,考虑整个过程中我们第一次违反这个引理的操作。如果 a i a_i ai a j a_j aj之间不存在已经取了的元素,那么由于 a i > a j a_i > a_j ai>aj i i i的贡献会比 j j j大;如果 a i a_i ai a j a_j aj之间存在已经取过的元素,假设这个元素是 x x x,由于这是我们第一次违反引理,所以有 a i ≤ a x a_i \le a_x aiax,所以 a j < a i ≤ a x a_j < a_i \le a_x aj<aiax x x x i i i的贡献的贡献是 a x a_x ax x x x j j j的贡献的贡献是 a j a_j aj,故而 i i i的贡献会比 j j j大。综上所述,引理成立。

下面证明贪心的算法是成立的。

假设不成立,则存在一个 { a 1 , a 2 ⋯ a n } \{ a_1,a_2 \cdots a_n\} {a1,a2an}的子集 A A A和一个 s s s,满足 A A A可以通过加入一些不在 A A A中的元素(设它们构成的集合为 B B B ∣ A ∣ + ∣ B ∣ = s |A|+|B|=s A+B=s)达到最优解,并且 A A A加入选择了 A A A之后贡献最大的元素 a j a_j aj之后得到的 A + a j A+a_j A+aj无法达到最优解。 B B B不能为空集。

B B B中存在元素在 a j a_j aj的左侧,假设这其中最靠右的为 a i a_i ai,那么由于加入 A A A中的元素后 a j a_j aj的贡献最大,所以 a i ≤ a j a_i \le a_j aiaj。又由于 B B B中不包含在 ( i , j ) (i,j) (i,j)的元素,所以将 B B B中的 i i i替换成 j j j不会使答案变差。

B B B中不存在元素 a j a_j aj的左侧,则一定存在元素在 a j a_j aj的右侧,假设其中最靠左的是 a i a_i ai。则选择将 a i a_i ai替换成 a j a_j aj,对 B B B中其它元素的贡献是没有影响的,而 a j a_j aj的贡献大于等于 a i a_i ai,所以将 a i a_i ai替换成 a j a_j aj将可以得到一个更优的解。

实际上只需要每次取出贡献最大的元素,直到贡献最大的元素的贡献小于 0 0 0就可以了。因为对于序列中所有的负数,由于取走它的时候它的左边一定不存在正数,所以序列中的正数的贡献一定是单调递增的,因此我们一定会把正数都取完。而考虑到所有正数都取完了之后,取走一个负数,由于剩下元素都是负数,所以它们的贡献一定会变小。也即是说,在一个时刻贡献最大的元素的贡献小于了 0 0 0,那么之后贡献最大的元素的贡献将不会再大于 0 0 0

用分块实现序列贡献最大元素的查询,复杂度 O ( n log ⁡ n + n n ) O(n\log n+ n\sqrt n) O(nlogn+nn )

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#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 M=510;
const int B=500;
struct item {
	int p; ll v;
	item(int p=0,ll v=0): p(p),v(v) {}
	friend bool operator <(item A,item B) {
		if(A.p==-1) return B.p!=-1;
		if(B.p==-1) return 0;
		return A.v<B.v;
	}
};
ll __a[M];
bool cmp(int x,int y) { return __a[x]<__a[y]; }
struct BLOCK {
	ll a[M],b[M],tg,l;
	int pos[M],m,hd,tl;
	int vis[M],que[M];
	int s;
	long double slope(int x,int y) { return (b[y]-b[x])/(long double)(a[x]-a[y]); }
	int dcmp(long double x) { return x<-1e-12?-1:(x>1e-12); }
	void rebuild() {
		hd=1,tl=0;
		for(int u=0;u<m;++u) {
			int i=pos[u];
			if(vis[i]) continue;
			if(hd<=tl&&a[i]==a[que[tl]]) {
				if(b[i]>b[que[tl]]) {
					tl--;
					while(hd<tl&&dcmp(slope(que[tl-1],que[tl])-slope(que[tl],i))>=0) tl--;
					que[++tl]=i;
				}
				continue;
			}
			while(hd<tl&&dcmp(slope(que[tl-1],que[tl])-slope(que[tl],i))>=0) tl--;
			que[++tl]=i;
		}
	}
	void build(int _m,int _s) {
		m=_m,s=_s;
		for(int i=0;i<m;++i) b[i]=a[i]=__a[i],pos[i]=i;
		sort(pos,pos+m,cmp);
		l=tg=0;
		rebuild();
	}
	void add(int t) { tg+=t; }
	void mul() { l++; }
	void push_down() {
		if(l||tg) {
			for(int i=0;i<m;++i)
				b[i]+=a[i]*l+tg;
			l=tg=0;
		}
	}
	void del(int x) {
		push_down();
		for(int i=0;i<x;++i) b[i]+=a[x];
		for(int i=x+1;i<m;++i) b[i]+=a[i];
		vis[x]=1;
		rebuild();
	}
	item query() {
//		item ans=item(-1,-1);
//		for(int i=0;i<m;++i) if(!vis[i]) ans=max(ans,item(i+s,a[i]*l+b[i]+tg));
//		return ans;
		
		while(hd<tl&&dcmp(l-slope(que[hd],que[hd+1]))>=0) hd++;
		if(hd<=tl) return item(s+que[hd],a[que[hd]]*l+b[que[hd]]+tg);
		return item(-1,-1);
	}
}T[210];
const int N=1e5+10;
int a[N],n,tot;
int bl[N];
void D(int x) {
	for(int i=0;i<bl[x];++i)
		T[i].add(a[x]);
	T[bl[x]].del(x-bl[x]*B);
	for(int i=bl[x]+1;i<tot;++i)
		T[i].mul();
}
item Q() {
	item ans=item(-1,-1);
	for(int i=0;i<tot;++i) ans=max(ans,T[i].query());
	return ans;
}
int main() {
	rd(n);
	for(int i=0;i<n;++i) rd(a[i]);
	for(int i=0;i<n;++i) bl[i]=i/B;
	tot=(n-1)/B+1;
	for(int i=0;i<n;i+=B) {
		for(int j=0;j<min(B,n-i);++j) __a[j]=a[i+j];
		T[bl[i]].build(min(B,n-i),i);
	}
	item tmp,lim=item(0,0); ll ans=0;
	while(!((tmp=Q())<lim))
		ans+=tmp.v,D(tmp.p);
	printf("%lld",ans);
	return 0;
}

B - CF704E Iron Man

题意

有一棵包含 n n n个节点的树,边长都是 1 1 1

m m m个人,第 i i i个人在 t i t_i ti x i x_i xi点出发向 y i y_i yi点走,速度为每秒钟 c i c_i ci条边,经过节点不需要时间。出发前或到达后这个人都不存在。

问第一次两个人相遇发生的时间,精确到 1 0 − 6 10^{-6} 106。如果永远没有人相遇,输出 − 1 -1 1

1 ≤ n , m ≤ 100000 , 1 ≤ t i , c i ≤ 10000 1\le n,m\le 100000,1\le t_i,c_i\le 10000 1n,m100000,1ti,ci10000

Sol

首先链剖一下,问题转化成:有若干条不与 x x x轴垂直的线段(人在树上的深度关于时间的函数),求出这些线段两两的交点的横坐标的最小值。

x x x坐标,维护 x x x坐标在一个区间内的时候线段的“高低顺序”。用“高低顺序”中相邻的线段的交点的坐标更新答案。

考虑下一个加入线段/删除线段的时间:

  • 如果这个时间大于了我们之前已经得到的答案,那么直接退出。
  • 否则,这意味着之前我们维护的那些线段到现在都还没有交点。加入/删除这条线段,并检查与这个线段在高低顺序中相邻的线段即可。

时间复杂度 O ( n log ⁡ 2 n ) O(n\log ^ 2 n) O(nlog2n)

具体细节见代码。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <set>
#define PB push_back
#define ll long long
#define db long double
#define inf 1e19
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 db eps=1e-8;
int dcmp(db x) { return x>-eps?(x>eps):-1; }
namespace Sol {
	db curt;
}
struct Line {
	db k,b,l,r;
	Line(db k=0,db b=0,db l=-inf,db r=inf): k(k),b(b),r(r) {}
	db cal(db x) { return k*x+b; }
	friend bool operator <(Line A,Line B) {
		if(dcmp(A.cal(Sol::curt)-B.cal(Sol::curt))) return dcmp(A.cal(Sol::curt)-B.cal(Sol::curt))<0;
		db tl=max(A.l,B.l);
		if(dcmp(A.cal(tl)-B.cal(tl))) return dcmp(A.cal(tl)-B.cal(tl))<0;
		if(dcmp(A.k-B.k)) return dcmp(A.k-B.k)<0;
		if(dcmp(A.b-B.b)) return dcmp(A.b-B.b)<0;
		if(dcmp(A.l-B.l)) return dcmp(A.l-B.l)<0;
		return dcmp(A.r-B.r)<0;
	}
};
struct Event {
	Line l;
	db p; int ty;
	Event(Line l,db p=0,int ty=0): l(l),p(p),ty(ty) {}
	friend bool operator ==(Line A,Line B) {
		return dcmp(A.k-B.k)==0&&dcmp(A.b-B.b)==0;
	}
};

namespace Sol {
	multiset<Line> S;
	bool cmpt(Event A,Event B) {
		if(dcmp(A.p-B.p)==0) {
			if(A.ty==B.ty) return A.l<B.l; 
			return A.ty>B.ty;
		} 
		return A.p<B.p;
	}
	db ans;
	void UPD(Line A,Line B,db t) {
		db L=max(A.l,B.l),R=min(A.r,B.r);
		if(dcmp(L-R)>0) return;
		if(dcmp(A.k-B.k)==0) {
			if(dcmp(A.b-B.b)==0) ans=min(ans,L);
			return;
		}
		db tmp=(A.b-B.b)/(B.k-A.k);
		if(dcmp(tmp-L)>=0&&dcmp(tmp-R)<=0)
			ans=min(ans,tmp);
	}
	db sol(vector<Event> &A) {
		S.clear();
		sort(A.begin(),A.end(),cmpt);
//		for(int i=0;i<A.size();++i) {
//			printf("%Lf: (%Lf,%Lf,%Lf,%Lf) %d\n",A[i].p,A[i].l.k,A[i].l.b,A[i].l.l,A[i].l.r,A[i].ty);
//		}
//		puts("");
		curt=-inf;
		ans=inf;
		for(int i=0;i<A.size();++i) {
			if(dcmp(A[i].p-ans)>0) break;
			multiset<Line>::iterator it,it2;
			curt=A[i].p;
			if(A[i].ty==1) {
				it=S.upper_bound(A[i].l);
				if(it!=S.end()) UPD(*it,A[i].l,A[i].p);
				if(it!=S.begin()) UPD(*(--it),A[i].l,A[i].p);
				S.insert(A[i].l);
			}
			else {
				S.erase(S.lower_bound(A[i].l));
				it=S.upper_bound(A[i].l);
				if(it!=S.end()&&it!=S.begin()) {
					it2=it; it2--;
					UPD(*it,*it2,A[i].p);
				}
			}
		}
		return ans;
	}
}
const int N=1e5+10;
int head[N],ecnt,n,m;
struct ed { int to,next; }e[N<<1];
void ad(int x,int y) {
	e[++ecnt]=(ed){y,head[x]}; head[x]=ecnt;
	e[++ecnt]=(ed){x,head[y]}; head[y]=ecnt;
}
int dep[N],sz[N],son[N],fa[N],top[N];
void dfs1(int u,int last) {
	sz[u]=1,dep[u]=dep[fa[u]=last]+1;
	for(int k=head[u];k;k=e[k].next) {
		int v=e[k].to; if(v==last) continue;
		dfs1(v,u); sz[u]+=sz[v];
		if(sz[v]>sz[son[u]]) son[u]=v;
	}
}
void dfs2(int u,int tp) {
	top[u]=tp;
	if(son[u]) dfs2(son[u],tp);
	for(int k=head[u];k;k=e[k].next) {
		int v=e[k].to; if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}
int LCA(int x,int y) {
	while(top[x]!=top[y]) dep[fa[top[x]]]>dep[fa[top[y]]]?x=fa[top[x]]:y=fa[top[y]];
	return dep[x]>dep[y]?y:x;
}
vector<Event> W[N];
void add(int x,Line L,db l,db r) {
	if(l>r) swap(l,r);
//	printf("add: %d (%Lf,%Lf) [%Lf,%Lf]\n",x,L.k,L.b,l,r);
	L.r=r,L.l=l;
	W[x].PB(Event(L,l,1));
	W[x].PB(Event(L,r,-1));
}
int ff;
void upd(int x,int f,db s,db c,Line L) {
	while(top[x]!=top[f]) {
		db s1=s-(dep[x]-dep[top[x]]+1)/c;
		add(top[x],L,s,s1);
		s=s1;
		x=fa[top[x]];
	}
	if(x==f) return;
	else ff=1;
	db s1=s-(dep[x]-dep[f])/c;
	add(top[x],L,s,s1);
}
int main() {
	rd(n),rd(m);
	for(int i=1,x,y;i<n;++i) rd(x),rd(y),ad(x,y);
	dfs1(1,0),dfs2(1,1);
	for(int i=1;i<=m;++i) {
		int t,_c,x,y; rd(t),rd(_c),rd(x),rd(y);
		db c=_c;
		int L=LCA(x,y);
		db ts=t+(dep[x]+dep[y]-2*dep[L])/c;
		ff=0;
		if(x!=L) upd(x,L,t,-c,Line(-c,dep[x]+c*t,-inf,inf));
		if(y!=L) upd(y,L,ts,c,Line(c,dep[y]-c*ts,-inf,inf));
		if(!ff) {
			db tmp=t+(dep[x]-dep[L])/c;
			add(top[L],Line(c,dep[y]-c*ts,-inf,inf),tmp,tmp);
		}
	}
	db ans=inf;
	for(int i=n;i>=1;--i) if(top[i]==i) ans=min(ans,Sol::sol(W[i]));
	if(ans>1e18) printf("-1");
	else printf("%.7Lf\n",ans);
	return 0;
}

C - ARC103D Robot Arms

题意

平面上有 n n n个点 ( x 1 , y 1 ) , ( x 2 , y 2 ) ⋯ (x_1,y_1),(x_2,y_2)\cdots (x1,y1),(x2,y2)

你需要构造一个长度不超过 40 40 40的非负整数序列 d 1 , d 2 ⋯ d m d_1,d_2\cdots d_m d1,d2dm,使得对于任意一个 i ∈ [ 1 , n ] i\in [1,n] i[1,n],都存在一种定向方案。其中定向方案是指,一个长度为 m m m的由 ( 1 , 0 ) , ( 0 , 1 ) , ( − 1 , 0 ) , ( 0 , − 1 ) (1,0),(0,1),(-1,0),(0,-1) (1,0),(0,1),(1,0),(0,1)中的元素构成的序列 L L L,并满足 ∑ j = 1 m d j ⋅ L j = ( x i , y i ) \sum_{j=1}^m d_j \cdot L_j = (x_i,y_i) j=1mdjLj=(xi,yi)

无解输出 − 1 -1 1

n ≤ 1000 n\le 1000 n1000 ∣ x i ∣ , ∣ y i ∣ ≤ 1 0 9 |x_i|,|y_i| \le 10^9 xi,yi109

Sol

首先,由于 1 ≡ − 1 ( m o d 2 ) 1\equiv -1 \pmod 2 11(mod2),所以如果这 n n n个点的 x i + y i ( m o d 2 ) x_i + y_i \pmod 2 xi+yi(mod2)不都相同,那么无解。

结论:对于 d = { 1 , 2 , ⋯ 2 k } d=\{1,2,\cdots 2^k\} d={1,2,2k},我们可以走到 ( − ( 2 k + 1 − 1 ) , 0 ) , ( ( 2 k + 1 − 1 ) , 0 ) , ( 0 , − ( 2 k + 1 − 1 ) ) , ( 0 , ( 2 k + 1 − 1 ) ) (-(2^{k+1}-1),0),((2^{k+1}-1),0),(0,-(2^{k+1}-1)),(0,(2^{k+1}-1)) ((2k+11),0),((2k+11),0),(0,(2k+11)),(0,(2k+11))为顶点构成的正方形内所有满足 x i + y i ≡ 1 ( m o d 2 ) x_i + y_i \equiv 1 \pmod 2 xi+yi1(mod2)的点。

考虑归纳地证明这个结论。对于 k = 0 k=0 k=0显然成立。而对于 k > 0 k>0 k>0,我们可以考虑先向上/下/左/右走 2 k 2^k 2k,接下来我们可以通过用 { 1 , 2 , ⋯ 2 k − 1 } \{ 1,2,\cdots 2^{k-1}\} {1,2,2k1}可以到达的点的并将填满整个正方形,如图:

image.png

而如果 x i + y i ≡ 0 ( m o d 2 ) x_i + y_i \equiv 0 \pmod 2 xi+yi0(mod2),则在 d d d中多加一个数 1 1 1就可以了。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define PB push_back
#define pii pair<int,int>
#define fir first
#define sec second
#define ll long long
#define MP make_pair
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=1010;
int sgn(int x) { return x>0?1:-1; }
int Abs(int x) { return x>0?x:-x; }
ll Xi[N],Yi[N];
int n;
vector<char> s[N];
char dy(int x) { return x>0?'U':'D'; }
char dx(int x) { return x>0?'R':'L'; }
int main() {
	rd(n);
	for(int i=1;i<=n;++i) rd(Xi[i]),rd(Yi[i]);
	int ty=((Xi[1]+Yi[1])%2+2)%2;
	for(int i=2;i<=n;++i) if(((Xi[i]+Yi[i])%2+2)%2!=ty) {
		printf("-1");
		return 0;
	}
	if(ty==0) {
		for(int i=1;i<=n;++i) {
			if(Abs(Xi[i])>Abs(Yi[i])) {
				s[i].PB(dx(Xi[i]));
				Xi[i]-=sgn(Xi[i]);
			}
			else {
				s[i].PB(dy(Yi[i]));
				Yi[i]-=sgn(Yi[i]);
			}
		}
	}
	for(int j=30;j>=0;--j)
		for(int i=1;i<=n;++i)
			if(Abs(Xi[i])>Abs(Yi[i])) {
				s[i].PB(dx(Xi[i]));
				Xi[i]-=sgn(Xi[i])*(1ll<<j);
			}
			else {
				s[i].PB(dy(Yi[i]));
				Yi[i]-=sgn(Yi[i])*(1ll<<j);
			}
	printf("%d\n",31+(!ty));
	if(!ty) printf("1 ");
	for(int j=30;j>=0;--j) printf("%d ",(1<<j));
	puts("");
	for(int i=1;i<=n;++i) {
		for(int j=0;j<s[i].size();++j) printf("%c",s[i][j]);
		puts("");
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值