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\n");else printf("NO\n");
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操作,这种方案的最小期望代价。
转移方程是:
其中,\(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\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<<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\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<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\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
*/