zoukankan      html  css  js  c++  java
  • 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
    */
    
  • 相关阅读:
    AD设计过程中的注意事项
    ds18b20再硬件设计部分的注意事项
    时序设计的注意事项
    【HackerRank】 Game Of Thrones
    【HackerRank】Halloween party
    【HackerRank】The Love-Letter Mystery
    【HackerRank】Gem Stones
    【HackerRank】Service Lane
    【HackerRank】Utopian tree
    【leetcode刷题笔记】Word Ladder II
  • 原文地址:https://www.cnblogs.com/Purple-wzy/p/13209206.html
Copyright © 2011-2022 走看看