Petrozavodsk Programming Camp, Winter 2020 部分题题解

题面这里有

Problem E. Contamination

题目大意:
平面上有(n)个两两相离的圆,给定它们的坐标和半径。
(q)次询问,每次给定两个点(p),(q),给出它们的坐标以及在y轴的可移动范围([ymin,ymax]),问两点能否互相到达。
(n,q) (leq) 1e6
题解:
可以想象,如果两点无法到达,一定是(p_x)(q_x)之间有若干个圆覆盖了([ymin,ymax])这一段。
由于有上下两个限制不太好做,考虑枚举第一个限制,用数据结构来维护第二个限制。
由此,我们将询问离线下来,维护一个从下到上的扫描线。
首先考虑一个圆c。当扫描线坐标(geq) (c_y)-(c_r)(leq) (c_y)+(c_r)时,它在(c_x)处将(x)轴一分为二。
所以我们可以以离散化后的x坐标为下标建立线段树。线段树的一个([l,r])区间表示的是(x)坐标在([l,r])之间的圆的(y)坐标最大值。
这样,只需要进行线段树的单点修改和区间查询了。
时间复杂度:O(nlogn)。
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;++x)
#define FOR(x,y,z) for(re x=y;x>=z;--x)
typedef long long ll;
#define I inline void
#define IN inline int
#define STS system("pause")
template<class D>I read(D &res){
	res=0;register D g=1;register char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')g=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		res=(res<<3)+(res<<1)+(ch^48);
		ch=getchar();
	}
	res*=g;
}
const int INF=2e9+7;
typedef pair<int,int>pii; 
struct C{
	int x,y,r;
}c[1010000];
struct Q{
	int x,y,a,b,mn,mx;
}q[1010000];
int n,m,cnt,s,tr[12020000];
bool ans[1010000];
pii pa[3030000];
#define all 1,1,s
#define lt k<<1,l,mid
#define rt k<<1|1,mid+1,r
I build(int k,int l,int r){
	tr[k]=-INF;if(l==r)return;
	re mid=(l+r)>>1;
	build(lt);build(rt);
}
I modi(int k,int l,int r,int x,int w){
	if(l==r)return tr[k]=max(tr[k],w),void();
	re mid=(l+r)>>1;
	if(x<=mid)modi(lt,x,w);else modi(rt,x,w);
	tr[k]=max(tr[k<<1],tr[k<<1|1]);
}
IN ques(int k,int l,int r,int x,int y){
	if(x>r||y<l)return -INF;
	if(x<=l&&r<=y)return tr[k];
	re mid=(l+r)>>1;
	return max(ques(lt,x,y),ques(rt,x,y));
}
int main(){
	read(n);read(m);cnt=0;
	F(i,1,n){read(c[i].x);read(c[i].y);read(c[i].r);pa[++cnt]=make_pair(c[i].x,i+m);}
	F(i,1,m){
		read(q[i].x);read(q[i].y);read(q[i].a);read(q[i].b);read(q[i].mn);read(q[i].mx);
		if(q[i].x>q[i].a)swap(q[i].x,q[i].a);
		pa[++cnt]=make_pair(q[i].x,i);pa[++cnt]=make_pair(q[i].a,-i);
	}
	sort(pa+1,pa+1+cnt);pa[0].first=pa[1].first-1;
	F(i,1,cnt){
		if(pa[i].first!=pa[i-1].first)++s;
		if(pa[i].second<0)q[-pa[i].second].a=s;
		else if(pa[i].second<=m)q[pa[i].second].x=s;
		else c[pa[i].second-m].x=s;
	}
	cnt=0;
	F(i,1,n)pa[++cnt]=make_pair(c[i].y-c[i].r,-i);
	F(i,1,m)pa[++cnt]=make_pair(q[i].mn,i);
	sort(pa+1,pa+1+cnt);build(all);
	F(i,1,cnt){
		if(pa[i].second<0)modi(all,c[-pa[i].second].x,c[-pa[i].second].y+c[-pa[i].second].r);
		else ans[pa[i].second]=ques(all,q[pa[i].second].x,q[pa[i].second].a)<q[pa[i].second].mx;
	}
	F(i,1,m)if(ans[i])printf("YES
");else printf("NO
");
	return 0;
}
/*
3 3
3 3 2
7 7 3
12 5 2
1 4 14 4 2 6
1 4 14 4 4 7
1 4 14 4 3 9
*/

Problem F. The Halfwitters

题目大意:
有一个1到(n)的排列。你需要将它还原为(p_i)=(i)的排列。
可以进行三种操作,分别需要(A),(B),(C)的花费。
(A):交换任意相邻的两个数字。
(B):翻转整个序列。
(C):随机排列这个序列。
求最小期望代价。
多组数据的方式为:给定(T)(n,A,B,C,D),每次给定(D)个排列。
(sum) (D) (leq) 1e5, (n) (leq) 16,(A),(B),(C) (leq) 1000,答案应以既约分数的形式给出。
题解:
先考虑如果只做A操作,会做多少次。因为目标序列的逆序对数为0,所以我们每做一次A操作,都应让逆序对数减1。
考虑最优解的操作序列应是什么形式。
首先,B操作最多一次。B操作的实质就是将逆序对数从k变为了(n*(n-1)/2-k)个。所以重复做B操作显然是无意义的。
其次再看看这个玄学的C操作。
首先,C操作之前肯定没有任何操作。next,不管做多少次C操作,最后的还原一定都是归到A操作和B操作上的。
所以操作序列大概就是:CCCC......C(B)AAAAA......A
可以理解为:我们不断打乱这个序列,直到它的逆序对数比较小或比较大,我们直接用A和B操作进行最后的还原。
由此,我们就有了一个O((n^4))的做法:
(dp_{i,j})表示逆序对数在([i,j])之间时,我们继续做C操作,这种方案的最小期望代价。
转移方程是:

[dp_{i,j} = frac{sum_{k=i}^{j} f_k}{n!} dp_{i,j} + C + frac{sum_{k=1}^{i-1} f_k*k*A}{n!} + frac{sum_{k=j+1}^{n} B+f_k*((n-1)*n)/2-k)*A}{n!} ]

其中,(f_i) 表示长度为(n)的,逆序对数为(i)的排列个数。这个可以直接预处理出来。
但是如果每次都O((n^4))预处理,会被(T)=1e5的极限数据轻松卡掉。
考虑优化。设(h_i)为逆序对数为(i)时的答案,那么(h)序列一定由三段组成:
前面是一段i*A,中间是一段包含C操作的代价,后面就是B+若干个A。
中间这段的代价一定是一个定值。所以我们只需要求出这个定值(W)就好了。
考虑将所有的(i)以min{ (i*A) , ((n*(n-1)/2-i)) *A+B}为关键字进行排序。
然后只需要从小到大枚举,算一下最小的(W)就好了。
时间复杂度:O((T) * (n^2) + (D) * (n^2) )
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
typedef long long  ll;
#define I inline void
#define IN inline int
#define Cl(x,y) memset(x,y,sizeof(x))
#define STS system("pause")
template<class D>I read(D &res){
	res=0;register D g=1;register char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')g=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		res=(res<<3)+(res<<1)+(ch^48);
		ch=getchar();
	}
	res*=g;
}
typedef __int128 LL;
const ll INF=1e18+9;
typedef long double db;
inline ll gcd(ll x,ll y){return !y?x:gcd(y,x%y);}
/*struct frac{
	ll a,b;
	frac(ll _a=0,ll _b=1){a=_a;b=_b;ll c=gcd(a,b);a/=c;b/=c;}
	friend bool operator < (frac x,frac y){
		return (LL)x.a*y.b<(LL)y.a*x.b;
//		static db p,q;
//		p=1.0*x.a/x.b;q=1.0*y.a/y.b;
//		return p<q;
	}
}f[440][440],g[440][440],h[440];
inline frac min(frac x,frac y){
	return x<y?x:y;
}*/
int n,m,s,T,a,b,c,d,p[20];
ll dp[20][440],id[440],f[440],sum[440],pre[440],suf[440],fac[20];
I init() {
  dp[0][0]=1;
  F(i,1,16)F(j,0,i*(i-1)/2)F(k,0,min(i-1,j))dp[i][j]+=dp[i-1][j-k];
  fac[0]=1;F(i,1,16)fac[i]=fac[i-1]*i;
}
inline bool bbb(int x,int y){return f[x]<f[y];}
I solve(){
 	read(n);read(a);read(b);read(c);read(d);
    int s=n*(n-1)/2;
    F(i,0,s)f[i]=min(a*i,b+a*(s-i)),id[i]=i;
	sort(id+1,id+1+s,bbb);
	ll sum=0,tot=fac[n],aa=0,bb=0;
	db vv=INF;
	F(i,0,s-1){
	    re x=id[i];
	    sum+=f[x]*dp[n][x];
	    tot-=dp[n][x];
	    ll fir=sum+tot*c,sec=fac[n]-tot;
	    if(!sec)continue;
	    db v=(db)fir/sec;
	    if (f[x]<=v+c&& f[id[i+1]]>=v+c) {
	      fir+=sec*c;
	      ll g=__gcd(fir, sec);
	      aa=fir/g;bb=sec/g;vv=v;
	      break;
	    }
	}
	while(d--){
	    F(i,1,n)read(p[i]);
	    re cnt = 0;
	    F(i,1,n-1)F(j,i+1,n)cnt+=(p[i]>p[j]);
	    if(f[cnt]<=vv+c)printf("%lld/1
", f[cnt]);
	    else printf("%lld/%lld
", aa, bb);
	}
}
/*I solve(){
	c=1;dp[1][0]=1;s=n*(n-1)/2;
	F(i,2,n){
		c^=1;Cl(dp[c],0);
		F(k,0,((i-1)*(i-2))>>1)F(j,0,i-1)dp[c][j+k]+=dp[c^1][k];
	}
//	F(i,0,s)cout<<dp[c][i]<<" ";
//	cout<<endl;
	sum[0]=dp[c][0];F(i,1,s)sum[i]=sum[i-1]+dp[c][i];//cout<<sum[s]<<" "<<fac[n]<<endl;
	pre[0]=0;F(i,1,s)pre[i]=pre[i-1]+dp[c][i]*i*A;
	suf[s+1]=0;FOR(i,s,0)suf[i]=suf[i+1]+dp[c][i]*((s-i)*A+B);
	F(j,0,s-1)f[0][j]=frac((ll)fac[n]*C+suf[j+1],fac[n]-sum[j]);f[0][s]=frac(INF);g[0][0]=f[0][0];F(j,1,s)g[0][j]=min(g[0][j-1],f[0][j]);
	F(i,1,s){
		F(j,i,s)f[i][j]=frac((ll)fac[n]*C+pre[i-1]+suf[j+1],fac[n]-sum[j]+sum[i-1]);
		g[i][i]=f[i][i];F(j,i+1,s)g[i][j]=min(g[i][j-1],f[i][j]);
	}
	h[0]=g[0][s];F(i,1,s)h[i]=min(h[i-1],g[i][s]);
	F(i,0,s)h[i]=min(h[i],min(frac(A*i),frac(B+(s-i)*A)));
}*/
IN count(){
	re res=0;
	F(i,1,n-1)F(j,i+1,n)if(p[i]>p[j])res++;
	return res;
}
int main(){
	read(T);init();
	while(T--){
		solve();
//		read(n);read(A);read(B);read(C);read(D);solve();
//		while(D--){F(i,1,n)read(p[i]);m=count();printf("%lld/%lld
",h[m].a,h[m].b);}
	}
	return 0;
}
/*
1
6 1 1 1 3
1 2 3 4 5 6
5 4 3 2 1 6
6 4 2 1 3 5
*/

Problem H. Lighthouses

题目大意:有(n)个点围成一个凸包,由(m)条双向边连接。
求一条最长路径,使得这条折线不自交。
(n) (leq) 300,(m) (leq) (frac{n*(n-1)}{2})
题解:
考虑走了(k)条路径后,整个凸包被若干条直线划成了(k+1)个部分。
(k+1)条直线只能选择其两边的一个部分进入,直到无法继续操作。
发现每一次转移到的区间都是原区间的子集,因此我们考虑区间DP。
(f_{i,j})表示当前还能走([i,j])这段区间,且当前在点(i)上的最长路径。
(g_{i,j})表示当前还能走([i,j])这段区间,且当前在点(j)上的最长路径。注意(i)不一定大于(j)
枚举(i),(j),以及中间点(k),两个数组互相转移一下就好了。
当然,以区间大小从小到大DP也是可以的。我的代码采用了从小到大的DP思想。
时间复杂度:O((n^3))
代码:

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define F(x,y,z) for(re x=y;x<=z;x++)
#define FOR(x,y,z) for(re x=y;x>=z;x--)
typedef long long ll;
#define I inline void
#define IN inline int
#define C(x,y) memset(x,y,sizeof(x))
#define STS system("pause")
template<class D>I read(D &res){
	res=0;register D g=1;register char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')g=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		res=(res<<3)+(res<<1)+(ch^48);
		ch=getchar();
	}
	res*=g;
}
typedef double db;
const db INF=1e18;
int n,m,T,X,Y,x[330],y[330],a[330][330];
db f[330][330][2],dis[330][330],ans;
inline db calc(int a,int b){
	return sqrt(1.0*(x[a]-x[b])*(x[a]-x[b])+1.0*(y[a]-y[b])*(y[a]-y[b]));
}
int main(){
	read(T);
	//cout<<fixed;
	while(T--){
		read(n);ans=0.0;
		F(i,1,n)read(x[i]),read(y[i]);
		F(i,1,n)F(j,1,n)dis[i][j]=0.0,a[i][j]=0;
		read(m);
		F(i,1,m)read(X),read(Y),a[X][Y]=a[Y][X]=1,dis[X][Y]=dis[Y][X]=calc(X,Y);
		F(i,1,n)F(j,1,n)f[i][j][0]=f[i][j][1]=-INF;
		F(i,1,n)f[i][i][0]=f[i][i][1]=0.0;
		F(k,0,n-1)F(l,1,n){
			re r=l+k;if(r>n)r-=n;
			for(re p=r+1;p^l;p++){
				if(p>n)p=1;if(p==l)break;
				if(a[l][p])f[l][p][1]=max(f[l][p][1],f[l][r][0]+dis[l][p]),f[p][r][0]=max(f[p][r][0],f[l][r][0]+dis[l][p]);
				if(a[p][r])f[l][p][1]=max(f[l][p][1],f[l][r][1]+dis[p][r]),f[p][r][0]=max(f[p][r][0],f[l][r][1]+dis[p][r]);
			}
			ans=max(ans,f[l][r][0]);ans=max(ans,f[l][r][1]);
		}
		printf("%.12lf
",ans);
	}
	return 0;
}
/*
2
4
0 0
1 0
1 1
0 1
3
1 3
2 4
3 4
4
0 0
1 0
1 1
0 1
4
1 4
4 3
3 2
2 1
*/
原文地址:https://www.cnblogs.com/Purple-wzy/p/13209206.html