[考试反思]0429省选模拟83:差错
$spj$日常爆炸送了我个绿框。
$T3$又是有部分分的且满足:只要分数不为$0$那就给满分。。。
本来期望得分也就十几分来着。。。
然后我不知道出题人怎么想的,$T2$明明出部分分了为啥不在数据范围里说一声啊。。。?
结果我想到$O(n^2)$的了然后以为没有暴力分结果就没打。。。
$T1$是大神的组合数学,然而我又一次完全想偏(想了半天怎么优化$dp$最后也就是$O(nms^2)$的出题人没想到所以我还是低档暴力分)
$T3spj$救我一命,不然这场肯定完全没法看。
这告诉我们:不管说不说暴力分,先写上再说吧。。。反正不少时间也啥都想不出来也是闲着。。。
T1:table
大意:网格图从$(1,1)$走到$(n,m)$只能往右或下走,哪个格子数大就往哪边走,如果一边是边界那就走另一边。
每个格子的数$\in [0,s]$。求有多少个网格图满足在上述决策下走出的路径上数字之和为$s$。要求$w_{1,1}=0$。$n,m,s \le 10^6$
首先考虑一个暴力$dp$,发现其实路径在哪里拐弯我们不是很在意,我们在意的是:
最后是贴着右边界还是贴着下边界走的,有几步是贴着边界走的。(这样就足以确定除了边界之外横着竖着各走了几步)
所以上述两个条件相同的路径,它们的贡献就都是一样的。(不沿着边界走每一步会确定$2$个位置的数,否则会确定一个,其余未确定位置都是随便填)
我们只要考虑一种特殊情况最后方案乘上组合数就行了。
以贴着下边界为例,设最开始贴上下边界的点是$(n,i)$。那么路径可以分成三部分部分:$(1,1) \rightarrow (n-1,i) \rightarrow (n,i) \rightarrow (n,m)$
因为我们强制在$(n,i)$是第一次贴到边界所以需要强制$(n-1,i)$。
只需要考虑第一段和最后一段,需要考虑在这两部分各分配了多少权值。
首先,未确定位置有$nm-(m-i)-2 \times ((i-1)+(m-1))$个,所以贡献是$(s+1)^{nm-(m-i)-2 \times ((i-1)+(m-1))}$
然后对于第一段,我们枚举所有横着走的步数分配了$x$的权值,竖着走的步数分配了$y$权值,那么贴边走的步数就分配了$s-x-y$权值。
对于横着走的部分,一共走了$i-1$步,每一步中,右边的格子要大于等于下边的,设这些格子序列是$a_i$那么就需要满足:
$\forall a_i \ge 0,\sum\limits_{l=1}^{i-1} a_l = x$。方案数就是$\sum\limits_{a} \prod \limits_{i=1}^{i-1} (a_i+1)$
这样不好考虑,设$b_i =a_i +1$那么条件变为$\forall b_i \ge 1,\sum\limits_{l=1}^{i-1} b_l = x+i-1$。方案数就是$\sum\limits_{a} \prod \limits_{i=1}^{i-1} b_i$
这样的话,就是要把$x+i-1$划分成$i-1$段,然后再在每一段里选择一个特殊点。方案数就是$\binom{(x+i-1)+(i-2)}{(i-1)+(i-2)}$
同理,竖着走的也要分配然而要求的是小于而不是小于等于所以上面直接用$a$就可以。方案数$\binom{y+(n-2)}{(n-1)+(n-2)}$
然后对于贴边走的部分就不需要考虑这么多,直接插板分配权值就行,方案数$\binom{(s-x-y)+(m-i-1)}{m-i-1}$
所以说对于先贴到下边界的总贡献就是$\sum\limits_{i=1}^{m-1} \binom{i+n-3}{i-1} (s+1)^{nm-(m-i)-2 \times ((i-1)+(m-1))} \sum\limits_{x=0}^{s} \binom{x+2i-3}{2i-3} \sum\limits_{y=0}^{s-x} \binom{y+n-2}{2n-3} \binom{s-x-y+m-i-1}{m-i-1}$
同理贴到右边界的是$\sum\limits_{j=1}^{n-1} \binom{j+m-3}{j-1} (s+1)^{nm-(n-j)-2 \times ((j-1)+(n-1))} \sum\limits_{x=0}^{s} \binom{x+j-2}{2j-3} \sum\limits_{y=0}^{s-x} \binom{y+2m-3}{2m-3} \binom{s-x-y+n-j-1}{n-j-1}$
然后暴力算肯定是不可行的。这一大堆组合数肯定可以搞一点事情。
可以发现这些组合数被选数的和是与$x,y$无关的。然后根据某某恒等式:
$\sum\limits_{i} \binom{a-i}{b} \binom{c+i}{d} = \binom{a+c+1}{b+d+1}$
含义理解的话,就是大小为$a+c$的集合选出$b+d$个关键点的方案数,其中每个方案作出的贡献是第$b$到第$b+1$个关键点之间的距离
于是我们枚举一个点作为第$b$个与第$b+1$个球之间的普通球,也就是枚举分界线,一共有$a+c+1$个可选点,选出$b+d+1$个其中第$b+1$个就是分解点。
有了这个式子。稍加拓展就是$\sum\limits_{a,b,c} [a+b+c=s] \binom{a}{d} \binom{b}{e} \binom{c}{f} = \binom{a+b+c+2}{d+e+f+2}$
代入上面两个大式子就行,最后的答案就是$\sum\limits_{i=1}^{m-1} (s+1)^{nm-2n-m-i+3} \binom{n+m+s+i-4}{2n+m+i-5} \binom{i+n+3}{i-1}$
再加上$\sum\limits_{j=1}^{n-1} (s+1)^{nm-2m-n-j+3} \binom{n+2m+s-4}{2m+n+j-5} \binom{j+n+3}{i-1}$
递推幂,复杂度$O(n)$
T2:remove
大意:给定$n$个形如$(x_1,0)(x_2,0)(x_3,1)(x_4,1)$的四边形,每次操作可以删除一个四边形集合满足集合中所有四边形两两相交,求最少操作次数。$n \le 10^5$
上来是一个结论:问题等价于选出尽量多的四边形使之互不重叠。
类似于当时学$LIS$时那个极长$LDS$个数=最长$LIS$长度
然后所谓不相交就是$x_{i,2}<x_{j,1},x_{i,3}<x_{j,4}$。
对$x_1$排序,按$x_2$建堆,用$x_3$维护树状数组,用$x_4$查询。
$O(nlogn)$
T3:road
大意:提交答案。平面上$n$点,有$m$个人要从$x_i$走到$y_i$,你可以在两个点之间修路,路长是欧几里德距离。最多修$k$条。构造方案最小化所有人路程和。
首先$spj$大概是炸了,貌似只要有分就是满分。
本来打算骗分写了个最小生成树结果就$A$了。
可能会有点卡常(一分都拿不到?)。写了个轻工业生成代码跑十个测试点。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 char in[666],out[666]; double x[66666],y[66666]; 4 int s[666666],t[666666],ec,f[66666],c[66666],deg[66666]; 5 struct E{int a,b;double len;friend bool operator<(E x,E y){return x.len<y.len;}}e[20000005]; 6 int find(int k){return f[k]==k?k:f[k]=find(f[k]);} 7 vector<int>v[66666]; 8 int main(){ 9 for(int _=1;_<=10;++_){ 10 sprintf(in,"road%d.in",_);freopen(in,"r",stdin); 11 sprintf(out,"road%d.out",_);freopen(out,"w",stdout); 12 int n,m,k;cin>>n>>m>>k; 13 for(int i=1;i<=n;++i)scanf("%lf%lf",&x[i],&y[i]),deg[i]=0; 14 for(int i=1;i<=m;++i)scanf("%d%d",&s[i],&t[i]),deg[s[i]]++,deg[t[i]]++; 15 if(_==2){ 16 for(int i=1;i<=m;++i)printf("%d %d\n",s[i],t[i]); 17 continue; 18 } 19 if(k>=n-1){ec=0; 20 for(int i=1;i<=n;++i)for(int j=i+1;j<=n;++j)e[++ec]=(E){i,j,(0.4+1.*deg[j]/m)*(0.4+1.*deg[i]/m)*sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]))}; 21 sort(e+1,e+1+ec); 22 for(int i=1;i<=n;++i)f[i]=i; 23 for(int i=1;i<=ec;++i){ 24 int a=e[i].a,b=e[i].b; 25 if(find(a)!=find(b))f[f[a]]=f[b],printf("%d %d\n",a,b); 26 } 27 for(int i=n;i<=k;++i){ 28 int x=rand()%n+1,y=rand()%n+1; 29 while(x==y)x=rand()%n+1,y=rand()%n+1; 30 printf("%d %d\n",x,y); 31 } 32 continue; 33 } 34 for(int i=1;i<=n;++i)f[i]=i; 35 for(int i=1;i<=m;++i)if(find(s[i])!=find(t[i]))f[f[s[i]]]=f[t[i]]; 36 for(int i=1;i<=n;++i)v[find(i)].push_back(i); 37 for(int i=1;i<=n;++i)if(v[i].size()){ 38 ec=0; 39 for(int a=0;a<v[i].size();++a)for(int b=a+1;b<v[i].size();++b){ 40 int q=v[i][a],w=v[i][b]; 41 e[++ec]=(E){q,w,(1+1.*deg[q]/m)*(1+1.*deg[w]/m)*(x[q]-x[w])*(x[q]-x[w])+(y[q]-y[w])*(y[q]-y[w])}; 42 } 43 sort(e+1,e+1+ec); 44 for(int i=1;i<=n;++i)f[i]=i; 45 for(int i=1;i<=ec;++i){ 46 int a=e[i].a,b=e[i].b; 47 if(find(a)!=find(b))f[f[a]]=f[b],printf("%d %d\n",a,b); 48 } 49 } 50 } 51 }
$sub1:n\le 8,k\le 15$:搜。
$sub2:m=k$:两点之间直线最短。
$sub3:$所有点是一条直线,直接构造链。(最小生成树当然也是最优的)
$sub4:$询问是$10$个联通块,每个联通块都是一条直线,直接跑$10$个链就行($10$棵生成树)
$sub5,6:k=n$有$10$条直线。搜索所有如下方案:讲直线环排列然后顺次在交点附近连边其余继续造链。(最优性不明,可能跑不过生成树?)
$sub7,8:k=n-1$有$10$条直线。还是搜索 ,只不过不连成环了。
$sub9,10:n<k<n+25$有$10$条直线。还是搜索,只不过搜一下除了环还连哪些边。
其实最难的地方在于看出一堆浮点数的点,它们都在一条直线上???
反正大概理论上讲生成树分不会低。