CF1444D Rectangular Polyline[题解]

Booksnow

2021-02-20 20:48:47

题解

构造

题目大意

给定 h 条长度分别为 l_1,l_2,……,l_h 的水平线段以及 v 条长度分别为 p_1,p_2,…….p_v 的竖直线段,将这些线段水平竖直交替首尾相连组成一个多边形,满足任意两条线段不相交或交点为各自的端点。可能无解

题意分析

是否有解

首先考虑有解的必要条件。

由于题目要求线段要交替摆放,所以线段的数量肯定满足 h=v ,否则无解。

其次,由于最后要首尾相连,设集合 X 为水平线段的集合,集合 Y 为竖直线段的集合,再设 X+ \subsetneqq XX- \subsetneqq XY+ \subsetneqq YY- \subsetneqq Y

假设我们从 (0,0) 开始摆放线段,则线段的摆放是有方向的,设 X+ 表示水平线段摆放的方向为正, X- 表示, Y+Y- 同理。

X+ 中线段的长度和为 sumx+X- 中线段的数量为 sumx- 。则要做到首尾相连,则必有 sumx+=sumx- 。且设 Y+ 中线段的长度和为 sumy+Y- 中线段的数量为 sumy- 。则要做到首尾相连,则必有 sumy+=sumy-

证明易得,下不赘述。

那么,我们的问题在于,这些条件是否充分?即满足这些条件,是否一定有解。

那么答案就是它确实是充分的,下给出证明。

如何放置

假设 |X+|\leq |Y+| ,则我们的构造可以分成三段,第一段是 X+Y+ ,第二段是 X-Y+ ,第三段是 X-Y-

则我们最后得到的图像大致如下图中的黑色三角形,且我们希望这三段的线段被框在红色线段与黑色三角形的边框形成的黄色范围内:

对于第一个子问题,我们想让形成的图像尽可能靠右下,则我们可以让 X+ 从大到小排序,让 Y- 从小到大排序。

这样做为什么正确呢?

如图,第一阶段我们行走的路径将类似于绿色线段,设到达点的坐标为 (sumx1,sumy1) ,设我们现在走到第一阶段中的第 k 步,且一共有 n 步,由于我们取的 x 是前 k 大,则我们目前的 sumxnow \ge \frac {k}{n}sumx1 ,同理,由于我们取的 y 是前 k 小,则 sumynow \leq \frac {k}{n}sumy1 ,所以 \frac {sumynow}{sumxnow} \leq \frac {sumy1}{sumx1} ,即当前的斜率至少比红线的斜率小,所以是一定处于红线下方的。

而对于另外的两段我们可以按照同样的方法进行构造

如下图,我们最终可以发现,X+X- 都应该从大到小排序, Y+Y- 都应该从小到大排序,这样就能够满足不相交的性质。

注意我们这样做的前提, |X+|\leq|Y+| ,如果情况恰好相反,则我们可以调换 XY ,到最后输出的时候再反转输出即可。

X+,X-,Y+,Y- 如何求得?

我们可以使用 bitset ,建立数组 dp[i] ,表示前 i 条线段中取任意线段长度相加的所有情况。

如何转移? $dp[i]=dp[i-1]|(dp[i-1]<<a[i])$ ,解读这个式子其实就是将 $dp[i-1]$ 中每个 $1$ 向右移动 $a[i]$ 位,其实含义是加上了 $a[i]$ ,在和原来的状态或运算,即可得到现在的状态。 具体代码实现如下: ```cpp inline void initial() { flag=true; //先解决x数组 int s1=sum1/2,s2=sum2/2; for(register int i=1;i<=n;i++) dp[i]=dp[i-1]|(dp[i-1]<<a[i]); if(dp[n][s1]){ //有可能到达sum1/2 for(register int i=n;i>=1;i--){ //该判断的含义是看若取了这个数,上一个状态是否能够皆这取到剩下需要取的长度,若能,才可以取 if(s1>=a[i]&&dp[i-1][s1-a[i]]) s1-=a[i],x[1][++totx[1]]=a[i]; else x[0][++totx[0]]=a[i]; } } else { flag=false; return; } //再解决y数组 for(register int i=1;i<=m;i++) dp[i]=dp[i-1]|(dp[i-1]<<b[i]); if(dp[m][s2]){ //有可能到达sum1/2 for(register int i=m;i>=1;i--){ if(s2>=b[i]&&dp[i-1][s2-b[i]]) s2-=b[i],y[1][++toty[1]]=b[i]; else y[0][++toty[0]]=b[i]; } } else { flag=false; return; } } ``` ### 结语 到这儿这道题就结束了,个人认为这是一道非常不错的好题,没有任何高深的算法,但是还是非常难,~~几乎和这道题死磕了半天~~。 ## CODE ```cpp #include <bits/stdc++.h> using namespace std; const int N=1e3+10; struct node{ int x,y; }; int T,n,m,sum1,sum2; int a[N],b[N]; //x[0][i]表示水平直线i被分在x-组中,记录直线i的长度 //x[1][i]表示水平直线i被分在x+组中,记录直线i的长度 //y[0][i]表示竖直直线i被分在y-组中,记录直线i的长度 //y[1][i]表示竖直直线i被分在y+组中,记录直线i的长度 int totx[2],toty[2]; int x[2][N],y[2][N]; int tot; node ans[N]; inline int read() { int s=0,w=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();} while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar(); return s*w; } bitset<N*N> dp[N]; bool flag=true; inline void initial() { flag=true; //先解决x数组 int s1=sum1/2,s2=sum2/2; for(register int i=1;i<=n;i++) dp[i]=dp[i-1]|(dp[i-1]<<a[i]); if(dp[n][s1]){ //有可能到达sum1/2 for(register int i=n;i>=1;i--){ if(s1>=a[i]&&dp[i-1][s1-a[i]]) s1-=a[i],x[1][++totx[1]]=a[i]; else x[0][++totx[0]]=a[i]; } } else { flag=false; return; } //再解决y数组 for(register int i=1;i<=m;i++) dp[i]=dp[i-1]|(dp[i-1]<<b[i]); if(dp[m][s2]){ //有可能到达sum1/2 for(register int i=m;i>=1;i--){ if(s2>=b[i]&&dp[i-1][s2-b[i]]) s2-=b[i],y[1][++toty[1]]=b[i]; else y[0][++toty[0]]=b[i]; } } else { flag=false; return; } } inline bool cmp(int p,int q) { return p>q; } inline void sol() { bool Judge=false; if(totx[1]<=toty[1]){ //x从大到小,y从小到大排序 sort(y[1]+1,y[1]+toty[1]+1); sort(y[0]+1,y[0]+toty[0]+1); sort(x[1]+1,x[1]+totx[1]+1,cmp); sort(x[0]+1,x[0]+totx[0]+1,cmp); node now={0,0}; int i[2]={1,1},j[2]={1,1}; while(n--){ if(i[1]<=totx[1]) now.x+=x[1][i[1]++],ans[++tot]=now; else now.x-=x[0][i[0]++],ans[++tot]=now; if(j[1]<=toty[1]) now.y+=y[1][j[1]++],ans[++tot]=now; else now.y-=y[0][j[0]++],ans[++tot]=now; } } else{ //反过来,y从大到小,x从小到大排序 sort(x[1]+1,x[1]+totx[1]+1); sort(x[0]+1,x[0]+totx[0]+1); sort(y[1]+1,y[1]+toty[1]+1,cmp); sort(y[0]+1,y[0]+toty[0]+1,cmp); node now={0,0}; int i[2]={1,1},j[2]={1,1}; while(n--){ if(j[1]<=toty[1]) now.y+=y[1][j[1]++],ans[++tot]=now; else now.y-=y[0][j[0]++],ans[++tot]=now; if(i[1]<=totx[1]) now.x+=x[1][i[1]++],ans[++tot]=now; else now.x-=x[0][i[0]++],ans[++tot]=now; } } } int main() { T=read(); dp[0][0]=1; while(T--){ totx[1]=totx[0]=toty[1]=toty[0]=0; tot=0; sum1=0,sum2=0; n=read(); for(register int i=1;i<=n;i++) a[i]=read(),sum1+=a[i]; m=read(); for(register int i=1;i<=m;i++) b[i]=read(),sum2+=b[i]; if(n!=m||sum1&1||sum2&1) { printf("No\n"); continue; } initial(); //预处理出x,y数组 if(!flag) { printf("No\n"); continue; } sol(); printf("Yes\n"); for(register int i=1;i<=tot;i++) printf("%d %d\n",ans[i].x,ans[i].y); //输出方案 } return 0; } ```