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
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")
templateI 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 pairpii; 
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>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)

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
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")
templateI 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=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\n", f[cnt]);
	    else printf("%lld/%lld\n", 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<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\n",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
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")
templateI 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<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\n",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
*/

你可能感兴趣的:(Petrozavodsk Programming Camp, Winter 2020 部分题题解)