[绍兴集训2019]test2-18

[绍兴集训2019]test2-18

吐槽

我原本以为,上次打了两个正解总共得分50,输出调试语句爆0已经非常惨了,为没想到我还可以没建子文件夹全场爆0。
没建子文件夹+T3暴力 输入用Lf爆炸。
然鹅就算我什么智障错误也没犯都不到三位数,rank1 250,我还有什么话可说呢。
可能sy的dalao们都在想这个两场考试总分50的魔芋到底是何方神圣,这年头这种水平的选手都可以冲省选了,真是世界之大无奇不有。

题解

T1 超越一切

菜如我只会 n 2 n^2 n2暴力dp.

题解:
考虑把每个矩形的贡献放在左下角,转为计算每个点的贡献。
一个点(x,y)的贡献即为x这一行和y这一列都被切之后还切了多少次+1。

那么点A(x,y)的贡献就等于 C ( h + w − 2 , k − 2 ) C ( h + w , k ) \frac{C(h+w-2,k-2)}{C(h+w,k)} C(h+w,k)C(h+w2,k2)(选k条边切选到了x行和y列的概率)* f ( A ) f(A) f(A)

f ( A ) = ∑ i = 1 k g i ∗ ( k − i + 1 ) f(A)=\sum_{i=1}^k g_i*(k-i+1) f(A)=i=1kgi(ki+1)

g i g_i gi : A A A点在第i次切出的概率

f ( A ) = ∑ i = 1 k h i f(A)=\sum_{i=1}^k h_i f(A)=i=1khi

h i h_i hi: A A A点在第i次之前被切出的概率

f ( A ) = ∑ i = 1 k C ( i , 2 ) C ( k , 2 ) f(A)=\sum_{i=1}^k \frac{C(i,2)}{C(k,2)} f(A)=i=1kC(k,2)C(i,2)

这样的点共有 h ∗ w h*w hw个。

a n s 1 = C ( h + w − 2 , k − 2 ) C ( h + w , k ) ∗ ∑ i = 1 k C ( i , 2 ) C ( k , 2 ) ∗ h ∗ w ans_1=\frac{C(h+w-2,k-2)}{C(h+w,k)}*\sum_{i=1}^k \frac{C(i,2)}{C(k,2)}*h*w ans1=C(h+w,k)C(h+w2,k2)i=1kC(k,2)C(i,2)hw

除此之外还有 h + w + 1 h+w+1 h+w+1个边缘点,同理可得他们的贡献。

a n s 2 = C ( h + w − 1 , k − 1 ) C ( h + w , k ) ∗ ∑ i = 1 k i k ∗ ( h + w ) + k ans2=\frac{C(h+w-1,k-1)}{C(h+w,k)}*\sum_{i=1}^k \frac{i}{k} *(h+w) +k ans2=C(h+w,k)C(h+w1,k1)i=1kki(h+w)+k

大力展开一波即可。

#include<bits/stdc++.h>
#define For(i,a,b) for(register int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(register int i=(a);i>=(b);i--)
#define Formylove return 0;
const int p=1e9+7;
typedef long long LL;
typedef double db;
using namespace std;
LL h,w,k;

template<typename T>void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

LL ksm(LL a,LL b) {
	LL rs=1,bs=a%p;
	while(b) {
		if(b&1) rs=rs*bs%p;
		bs=bs*bs%p;
		b>>=1;
	}
	return rs;
}

int main() {
	freopen("ak.in","r",stdin);
	freopen("ak.out","w",stdout);
	read(h); read(w); read(k);
	h%=p; w%=p; k%=p;
	LL ans=k*(k+1)%p*((k*2-2+p)%p)%p*ksm(6,p-2)%p*ksm(h+w-1,p-2)%p*ksm(h+w,p-2)%p*h%p*w%p;
	ans=(ans+(k+1)*k%p*ksm(2,p-2)%p+k)%p;
	printf("%lld\n",ans);
	Formylove;
}

T2 不再胆怯

数据结构好啊,我YY出了这题正解,然鹅码力捉急,只打了60且只调出了40且只得到了30,so sad.
题解:
观察一下这个两重循环,它的意义大致是每个点有个限制最大值lim和增加值d,给定一个区间,把从区间每个点开始到区间右端点进行一段变换(从每个值开始,每次加经过点的d并且和lim取min)后的值放进一个集合(集合每次做前清空),问关于集合的一些问题。这段是废话。
f ( l , r ) f(l,r) f(l,r)表示从l到r进行变换得到的值, x l x_l xl表示开始变换时的初值,sum为d的前缀和。
f ( l , r ) = m i n ( x l + s u m r − s u m l , M i n i = l + 1 r ( l i m i + s u m r − s u m i ) ) f(l,r)=min(x_l+sum_r-sum_l,Min_{i=l+1}^r (lim_i+sum_r-sum_i)) f(l,r)=min(xl+sumrsuml,Mini=l+1r(limi+sumrsumi))
看询问

1. x l = l i m l x_l=lim_l xl=liml时,求集合 k k k大值和集合元素的数目。

此时 f ( l , r ) = M i n i = l r ( l i m i + s u m r − s u m i ) ) f(l,r)=Min_{i=l}^r (lim_i+sum_r-sum_i)) f(l,r)=Mini=lr(limi+sumrsumi))

= M i n i = l r ( l i m i − s u m l ) ) + s u m r =Min_{i=l}^r (lim_i-sum_l))+sum_r =Mini=lr(limisuml))+sumr

线段树维护 l i m − s u m lim-sum limsum的值,求 f ( a , b ) f(a,b) f(a,b)直接查区间最值即可。

发现 f ( l , r ) f(l,r) f(l,r)是关于 ( l , r ) (l,r) (l,r)的后缀的最小值,那么 f ( l , r ) , f ( l + 1 , r ) , f(l,r),f(l+1,r), f(l,r),f(l+1,r),…, f ( r , r ) f(r,r) f(r,r)的值一定是单调不减的。

(1) k k k大值,即 f ( r − k + 1 , r ) f(r-k+1,r) f(rk+1,r).线段树区间查询,一个log。

(2)集合元素数目,从右往左看是遇到一个比当前最值更小的值就++,从左往右就是问单调增的单调队列中元素的个数,即线段树维护单调队列经典做法,每个点维护左子区间在右子区间最小值加入在最后情况下的单调队列。两个log。

2. x i = x 0 , x 0 x_i=x_0,x_0 xi=x0,x0为输入值,求集合最大值
f ( l , r ) = m i n ( x 0 + s u m r − s u m l , M i n i = l + 1 r ( l i m i + s u m r − s u m i ) ) f(l,r)=min(x_0+sum_r-sum_l,Min_{i=l+1}^r (lim_i+sum_r-sum_i)) f(l,r)=min(x0+sumrsuml,Mini=l+1r(limi+sumrsumi))
把两段分开看, x 0 + s u m r − s u m l x_0+sum_r-sum_l x0+sumrsuml是单调不增的, M i n i = l + 1 r ( l i m i + s u m r − s u m i ) Min_{i=l+1}^r (lim_i+sum_r-sum_i) Mini=l+1r(limi+sumrsumi)是单调不减的, f f f的图形为两个函数的最低点,即一个交叉的x型的下半部分,显然可以二分找到最高点。
直接二分两个log,然而这个询问数目非常大,需要线段树上二分做到一个log才能过鹅。

3.修改lim的操作,线段树单点修改。因为需维护单调队列,是双log的。

d ≤ 2000 d \leq 2000 d2000只需要Int即可,LL会TLE诶(也许是我自带巨无霸常数)。

最终代码可以说是很短了,我还是太不熟悉数据结构了,每步都写挂调了好久。

#include<bits/stdc++.h>
#define For(i,a,b) for(register int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(register int i=(a);i>=(b);i--)
#define Formylove return 0;
const int N=5e5+7;
typedef long long LL;
typedef double db;
using namespace std;
int d[N],lim[N],x[N],sum[N];
int n,m;

template<typename T>void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

#define inf 1e9+7
#define lc (x<<1)
#define rc ((x<<1)|1)
#define mid ((l+r)>>1)

int sg[N<<2],V;
int ct[N<<2];
inline int Q(int x,int l,int r,int ql,int qr) {
	if(l>r||ql>qr) return 0;
	if(l>=ql&&r<=qr) {
		int rs=0;
		if(l==r) rs=sg[x]<V;
		else if(V>=sg[rc]) rs=ct[x]+Q(rc,mid+1,r,ql,qr);
		else rs=Q(lc,l,mid,ql,qr);
		V=min(V,sg[x]); return rs;
	}
	if(qr<=mid) return Q(lc,l,mid,ql,qr);
	if(ql>mid) return Q(rc,mid+1,r,ql,qr);
	int rs=Q(rc,mid+1,r,ql,qr);
	return rs+Q(lc,l,mid,ql,qr);
}

void upd(int x,int l,int r,int pos,int v) {
	if(l==r) { sg[x]=v; return; }
	if(pos<=mid) upd(lc,l,mid,pos,v);
	else upd(rc,mid+1,r,pos,v);
	sg[x]=min(sg[lc],sg[rc]);
	V=sg[rc]; ct[x]=Q(lc,l,mid,l,mid);
}

inline int qry(int x,int l,int r,int ql,int qr) {
	if(l>r||ql>qr) return 0;
	if(l>=ql&&r<=qr) return sg[x];
	if(qr<=mid) return qry(lc,l,mid,ql,qr);
	if(ql>mid) return qry(rc,mid+1,r,ql,qr);
	return min(qry(lc,l,mid,ql,qr),qry(rc,mid+1,r,ql,qr));
}

void build(int x,int l,int r) {
	if(l==r) { sg[x]=lim[l-1]-sum[l-1]; return ; }
	build(lc,l,mid); build(rc,mid+1,r);
	sg[x]=min(sg[lc],sg[rc]);
	V=sg[rc]; ct[x]=Q(lc,l,mid,l,mid);
}

int ans2,xo;
int sqry(int x,int l,int r,int ql,int qr) {
	if(l>=ql&&r<=qr) {
		if(l==r) {
			int mi1=min(V,sg[x]);
			int mi2=xo-sum[l-2];
			ans2=max(ans2,min(mi1,mi2));
			if(mi1<mi2) return 1;
			else { V=min(V,sg[x]); return 0; }
		}
		int mi1=min(V,sg[rc]);
		int mi2=xo-sum[mid-1];
		ans2=max(ans2,min(mi1,mi2));
		if(mi1<mi2) { sqry(rc,mid+1,r,ql,qr); return 1; }
		else { V=min(V,sg[rc]); return sqry(lc,l,mid,ql,qr); }
	}
	if(qr<=mid) return sqry(lc,l,mid,ql,qr);
	if(ql>mid) return sqry(rc,mid+1,r,ql,qr);
	if(sqry(rc,mid+1,r,ql,qr)) return 1;
	else return sqry(lc,l,mid,ql,qr);
}

int main() {
	freopen("str.in","r",stdin);
	freopen("str.out","w",stdout);
	read(n); read(m);
	For(i,1,n) { read(d[i]); sum[i]=sum[i-1]+d[i]; }
	For(i,0,n) read(lim[i]);
	build(1,1,n+1);
	For(cs,1,m) {
		int o;
		read(o);
		if(o==0) {
			int i; 
			int v; 
			read(i); read(v);
			lim[i]=v;
			upd(1,1,n+1,i+1,lim[i]-sum[i]);
		}
		if(o==1) {
			int l,r,k;
			read(l); read(r); read(k);
			int i=r-k+1;
			x[i-1]=lim[i-1];
			int ans=qry(1,1,n+1,i,r+1)+sum[r];
			printf("%d\n",ans);
		}
		if(o==2) {
			int l,r,rs,R;
			read(l); read(r); read(xo);
			ans2=-inf; V=inf;
			sqry(1,1,n+1,l+1,r+1);
			printf("%d\n",ans2+sum[r]);
		}
		if(o==3) {
			int l,r;
			read(l); read(r);
			int mi=lim[r];
			int ans=0;
			V=min(lim[r-1]-sum[r-1],lim[r]-sum[r]);
			ans=Q(1,1,n+1,l,r-1)+1;
			printf("%d\n",ans);
		}
	}
	Formylove;
}

T3 不需要你们的理解

我觉得好神仙啊。
最优解的向量显然在B,C为焦点2a=ans的椭圆的边界上,其他所有向量都在这个椭圆内部。
若椭圆上某点是个答案,过该点做切线,做这条切线的法向量,把所有n个向量投影到这个法向量上,选最大且为正的m个即为一组合法解。
那么如果我们枚举所有向量当作这个法向量,取投影后最大的部分向量来更新答案,就可以找到正确答案了。
如何枚举,我们需要所有的向量投影的大小关系和正负关系。
枚举两两向量之差的法向量,经过这个法向量时这两个向量的投影的大小关系就会改变。
枚举每个向量本身的法向量,经过这个法向量时这个向量投影的正负就会改变。
这样就可以枚举到所有有用的向量了。
把这些向量极角排序,跨越大小关系改变的向量时通过向前交换来排序。
(这个题解我真的不知道如何描述,只可意会不可言传,代码+感性理解一下(希望以后看到这里不会想打自己))。

然后就是这题卡精度,要到1e12,搬题人提供了一种很高精度的整数开根方法。
x开根后分为整数a和小数b两个部分。
x = ( a + b ) 2 x = a 2 + 2 ∗ a ∗ b + b 2 x − a 2 = a ∗ b + b ∗ ( a + b ) x − a 2 = a ∗ b + b ∗ s q r t ( x ) b = x − a ∗ a a + s q r t ( x ) x=(a+b)^2 \\ \\x=a^2+2*a*b+b^2 \\ \\x-a^2=a*b+b*(a+b) \\ \\x-a^2=a*b+b*sqrt(x) \\ \\b=\frac{x-a*a}{a+sqrt(x)} x=(a+b)2x=a2+2ab+b2xa2=ab+b(a+b)xa2=ab+bsqrt(x)b=a+sqrt(x)xaa
sqrt(x)直接开根即可。

#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0;
#define pi acos(-1)
const int N=1507;
typedef unsigned long long ULL;
typedef long long LL;
typedef double db;
using namespace std;
int n,m,cnt,pos[N];

template<typename T>void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct Num {
	LL a; db b;
	Num(){}
	Num(LL a,db b):a(a),b(b){}
	friend bool operator <(const Num&A,const Num&B) { 
		return A.a<B.a||(A.a==B.a&&A.b<B.b);
	}
}ans;
Num operator +(const Num&A,const Num&B) { 
	Num rs=Num(A.a+B.a,A.b+B.b);
	return Num(rs.a+(int)rs.b,rs.b-(int)rs.b); 
}

Num get_sqrt(ULL x) {
	if(x==0) return Num(0,0);
	ULL a=(LL)sqrt(x);
	while((a+1)*(a+1)<=x) a++;
	return Num(a,(x-a*a)/(sqrt(x)+a));
}

void print(Num A) {
	static char s[N];
	sprintf(s,"%.15lf",A.b),
	printf("%lld%s",A.a,s+1);
}

struct pt {
	LL x,y; int id;
	pt(){}
	pt(LL x,LL y,int id=0):x(x),y(y){}
	void read() { scanf("%lld%lld",&x,&y); }
}p[N],A,B,C,sum[N],bs,np[N];

struct vc {
	db slop;
	int x,y;
	friend bool operator <(const vc&A,const vc&B) {
		return A.slop<B.slop||(A.slop==B.slop&&A.x<B.x);
	}
}d[N*N];

pt operator +(const pt&A,const pt&B) { return pt(A.x+B.x,A.y+B.y); }
pt operator -(const pt&A,const pt&B) { return pt(A.x-B.x,A.y-B.y); }
LL dot(pt A,pt B) { return A.x*B.x+A.y*B.y; }
Num lenth(pt A) { 
	ULL a=A.x>0?A.x:-A.x,b=A.y>0?A.y:-A.y;
	return get_sqrt(a*a+b*b); 
}

bool cmp(const pt &A,const pt &B) {
	return dot(A,bs)>dot(B,bs);
}

void calc() {
	int l=1,r=min(m,n),rs=0;
	while(l<=r) {
		int mid=((l+r)>>1);
		if(dot(np[mid],bs)>=0) rs=mid,l=mid+1;
		else r=mid-1;
	}
	pt t=A+sum[rs];
	ans=max(ans,lenth(t-B)+lenth(t-C));
}

void move(int x) {
	while(x>1&&dot(np[x],bs)>=dot(np[x-1],bs)) {
		swap(pos[np[x].id],pos[np[x-1].id]);
		swap(np[x],np[x-1]);
		sum[x-1]=sum[x-2]+np[x-1];
		sum[x]=sum[x-1]+np[x];
		x--;
	}
}

int main() {
	freopen("tr.in","r",stdin);
	freopen("tr.out","w",stdout);
	read(n); read(m);
	ans=Num(0,0);
	A.read(); B.read(); C.read();
	p[n+1]=pt(0,0);
	For(i,1,n) {
		p[i].read();
		p[i].id=i;
		np[i]=p[i];
		pt v=p[i]-p[n+1];
		d[++cnt]=(vc){atan2(v.x,-v.y),i,n+1};
		v=p[n+1]-p[i];
		d[++cnt]=(vc){atan2(v.x,-v.y),n+1,i};
		For(j,1,i-1) if(p[i].x!=p[j].x||p[i].y!=p[j].y) {
			v=p[i]-p[j];
			d[++cnt]=(vc){atan2(v.x,-v.y),i,j};
			v=p[j]-p[i];
			d[++cnt]=(vc){atan2(v.x,-v.y),j,i};
		}
	}
	sort(d+1,d+cnt+1);
	pt tp=p[d[1].x]-p[d[1].y];
	bs=pt(-tp.y,tp.x);
	sort(np+1,np+n+1,cmp);
	For(i,1,n) {
		sum[i]=sum[i-1]+np[i];
		pos[np[i].id]=i;
	}
	calc();
	For(i,2,cnt) {
		pt tp=p[d[i].x]-p[d[i].y];
		bs=pt(-tp.y,tp.x);
		calc();
		if(d[i].x<=n&&d[i].y<=n) move(pos[d[i].y]);
	}
	ans=ans+lenth(B-C);
	print(ans);
	Formylove;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值