[考试反思]0922csp-s模拟测试50:谜朦
这辈子第5个rank1,是在长期状态低迷再度偶遇傻逼出题人然后总算在下午而不是晚上考了一场试
然后莫名其妙选对了头铁的题把其它暴力打满之后发现sdfz没有参加之后竞争减弱的综合结果。
说是在的其实这套题不很是我的类型,我的分数里面也有一些水分。
T1正解是单调栈但是打飞了,我用大模拟实现了单调的过程,不知道为什么相较于正解,时间快内存小代码复杂度也低。
但是就是有几个细节,吸取之前考试的教训打了个对拍慢慢调。
没看时间,结果花了太多时间(2个小时),看对拍过了就匆忙进T2T3。
T2暴力很好打,轻松拿下了。想到了莫队但是不会证复杂度没有打。
实际上莫队非常优秀。但是面对4维偏序还是犹豫了。
知识迁移。不要怕,要尝试把学过的知识点用到新的题上。
16分钟解决T2暴力之后,看T3,和之前《影子》那道题有关系。
但是其实想的不够好,它基本就是《消防》那题的裸题稍改编,但是没有看出来。
我运用了《影子》那道题的结论(其实应该更早就知道的)然后也码了一个暴力。
惊奇的发现它有60分的暴力分。但是十分不好调,怎么弄怎么弄反正最后终于过了拿下60分。
剩的一点时间里想到一个非正解优化,感觉没什么效果就没有打(估计打不完),就白扔了10分。
然而实际上十分好打。。。3分钟就弄出来了。
能优化一定要优化!!!不要浪费最后的时间!!
这次最好的一点就是没有意外失分。
然而其实有些运气成分。yxm和skyh都压注在T3上了,而我是肝在T1上了,T3的暴力分比T1多,所以我就rank1了。。。
越说越没水准的样子。
但其实我也的确分析了分值分布才这么抉择的,但是我并不知道那两个人没有肝T1。
对分值要有规划。合理分配有限的时间。最浅显的道理:分越多越好。
是状态有所回升吗?
不知道。也许能把之前的坑填上一点。。。
离第一机房线还是有一段不小的距离。。。
不管怎么说,求稳吧,稳中求进。
步调不要乱。继续保持。
DeepinC,加油!
T1:施工
阶梯状的位置不会被拔高,只有平底的坑才会被填。
记录每一个大坑的左右端点,填1单位高度的代价,填1单位高度的代价增长率。
然后扫每一个坑,只要代价还小于等于收益就不断填就好,运用上面的数组可以O(1)计算要填几单位的深度。
填完一个坑之后检查一下看有没有填满,填满的话尝试扩展这个区间看能不能形成一个新的更大的平底坑。
把这个新的平底区间两个端点打上标记,方便其它的坑扩展的时候O(1)吞并这个坑。
大模拟。。。
因为每次填坑至少让坑的大小+1,或者弃坑然后以后再也不碰这个坑,那么就是每次循环会至少让1个位置作废。
所以复杂度是O(n)的。
1 #include<cstdio> 2 #include<cmath> 3 #include<iostream> 4 using namespace std; 5 int n,h[1000005],l[2000005],r[2000005],top,Rc[1000005],Lc[1000005];long long a[2000005],w[2000005],c,ans; 6 int main(){//freopen("t1.in","r",stdin);freopen("t1.out","w",stdout); 7 scanf("%d%lld",&n,&c); 8 for(int i=1;i<=n;++i)scanf("%d",&h[i]); 9 for(int i=2;i<=n;++i)ans+=abs(h[i]-h[i-1])*c; 10 h[0]=h[n+1]=100000001; 11 for(int i=1;i<=n;++i){ 12 int j=i; 13 for(;h[j]==h[i];++j);j--;//printf("%d %d %d\n",h[i-1],h[i],h[j+1]); 14 if(h[i-1]>h[i]&&h[i]<h[j+1]){ 15 ++top; 16 l[top]=i;r[top]=j;a[top]=j-i+1<<1;w[top]=j-i+1; 17 } 18 i=j; 19 }//printf("%lld\n",ans); 20 for(int q=1;q<=top;++q){//printf("%d %d %lld %lld\n",l[q],r[q],a[q],w[q]); 21 long long e=c<<1;if(l[q]==1)e-=c;if(r[q]==n)e-=c; 22 if(e<w[q])continue; 23 int t=(e-w[q])/a[q]+1; 24 t=min(t,min(h[r[q]+1],h[l[q]-1])-h[l[q]]); 25 h[l[q]]+=t; 26 if(l[q]!=r[q])h[r[q]]+=t; 27 ans-=e*t-(w[q]+w[q]+(t-1)*a[q])*t/2;//printf("%d %lld\n",t,ans); 28 w[q]+=a[q]*t; 29 while(h[l[q]]==h[l[q]-1]&&!Rc[l[q]-1])l[q]--,w[q]++,a[q]+=2; 30 if(h[l[q]]==h[l[q]-1])w[q]+=w[Rc[l[q]-1]],a[q]+=a[Rc[l[q]-1]],l[q]=l[Rc[l[q]-1]]; 31 while(h[r[q]]==h[r[q]+1]&&!Lc[r[q]+1])r[q]++,w[q]++,a[q]+=2; 32 if(h[r[q]]==h[r[q]+1])w[q]+=w[Lc[r[q]+1]],a[q]+=a[Lc[r[q]+1]],r[q]=r[Lc[r[q]+1]]; 33 Lc[l[q]]=q;Rc[r[q]]=q;//printf("%d %d %lld %lld\n",l[q],r[q],a[q],w[q]); 34 if(h[l[q]-1]>h[l[q]]&&h[r[q]]<h[r[q]+1]){q--;continue;} 35 } 36 printf("%lld\n",ans); 37 }
思路积累:
- 论模拟的重要性。
- 复杂度分析。
T2:蔬菜
正解(?)看大脸,三维容斥树状数组?
不要怂,裸的莫队,只不过是4维罢了,而且每次更新也是O(n)的。
但是就是不会T!!!
就算我把块的大小弄成了1它也不会T。。。(也就是只是排序了一下而已)
开一个桶,随着指针移动改变当前答案和桶里的值就行。
因为是平方,所以每次加入一个元素当前答案的变化量是2x+1或2x-1。
真的很暴力。。。
1 #include<cstdio> 2 #include<map> 3 #include<algorithm> 4 using namespace std; 5 #define P ; 6 map<int,int>M; 7 void read(int &p,register char ch=getchar()){p=0; 8 while(ch<'0'||ch>'9')ch=getchar(); 9 while(ch>='0'&&ch<='9')p=(p<<3)+(p<<1)+ch-48,ch=getchar(); 10 } 11 struct qs{ 12 int ord,ans,x1,x2,y1,y2; 13 #define S 13 14 friend bool operator<(qs a,qs b){ 15 if(a.x1/S!=b.x1/S)return a.x1/S<b.x1/S; 16 if(a.y1/S!=b.y1/S)return a.y1/S<b.y1/S; 17 if(a.x2/S!=b.x2/S)return a.x2/S<b.x2/S; 18 return a.y2/S<b.y2/S; 19 } 20 }q[100005]; 21 bool com(qs a,qs b){return a.ord<b.ord;} 22 int n,m,Q,buc[40001],x[205][205],nw,cnt,px1,px2,py1,py2; 23 int main(){//freopen("t2.in","r",stdin);//freopen("t2.out","w",stdout); 24 read(n);read(m);read(Q); 25 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)read(x[i][j]); 26 for(int i=1;i<=Q;++i)read(q[i].x1),read(q[i].y1),read(q[i].x2),read(q[i].y2),q[i].ord=i; 27 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(M.find(x[i][j])==M.end())M[x[i][j]]=++cnt; 28 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)x[i][j]=M[x[i][j]]; 29 sort(q+1,q+1+Q); 30 buc[x[1][1]]=px1=px2=py1=py2=nw=1; 31 for(int k=1;k<=Q;++k){//printf("Q:%d %d %d %d\n",q[k].x1,q[k].y1,q[k].x2,q[k].y2); 32 while(px2<q[k].x2){ 33 px2++; 34 for(int i=py1;i<=py2;++i)nw+=2*buc[x[px2][i]]+1,buc[x[px2][i]]++; 35 } 36 while(py2<q[k].y2){ 37 py2++; 38 for(int i=px1;i<=px2;++i)nw+=2*buc[x[i][py2]]+1,buc[x[i][py2]]++; 39 } 40 while(px1>q[k].x1){ 41 px1--; 42 for(int i=py1;i<=py2;++i)nw+=2*buc[x[px1][i]]+1,buc[x[px1][i]]++; 43 }P 44 while(py1>q[k].y1){ 45 py1--; 46 for(int i=px1;i<=px2;++i)nw+=2*buc[x[i][py1]]+1,buc[x[i][py1]]++; 47 }P 48 while(px2>q[k].x2){ 49 for(int i=py1;i<=py2;++i)nw-=2*buc[x[px2][i]]-1,buc[x[px2][i]]--; 50 px2--; 51 }P 52 53 while(py2>q[k].y2){ 54 for(int i=px1;i<=px2;++i)nw-=2*buc[x[i][py2]]-1,buc[x[i][py2]]--; 55 py2--; 56 }P 57 while(px1<q[k].x1){ 58 for(int i=py1;i<=py2;++i)nw-=2*buc[x[px1][i]]-1,buc[x[px1][i]]--; 59 px1++; 60 } 61 while(py1<q[k].y1){ 62 for(int i=px1;i<=px2;++i)nw-=2*buc[x[i][py1]]-1,buc[x[i][py1]]--; 63 py1++; 64 } 65 q[k].ans=nw;//puts(""); 66 } 67 sort(q+1,q+1+Q,com); 68 for(int i=1;i<=Q;++i)printf("%d\n",q[i].ans); 69 }
- 平方性质的运用。
- 数据结构扩展到高维。
T3:联盟
yxm发现了这题的数据里直径唯一。。。
所以我打的其实不是正解(但是差的也不多了,犯懒了而已)
先假定直径唯一。那就简单了。
我们先求出直径,那么我们砍断的边一定是直径上的边,否则直径仍然存在最长距离没有变。
那么我们就看直径这条链就好了。
然后就好象是《消防》裸题了(《算法竞赛进阶指南》P367《树网的核》)。
从链上正反各跑一遍找到子树最长支链(就是与直径无重边的链),然后就可以求出正反向的子树直径了。
然后就是一个dp了?枚举砍直径上的那一条边,然后看它两个端点的子树直径。
最优决策是把两个直径的中点相连,答案是((1+d1)/2+(1+d2)/2+1)
1 #include<cstdio> 2 #include<vector> 3 #include<algorithm> 4 using namespace std; 5 vector<int>num; 6 int cnt=1,fir[300005],l[600005],to[600005],ban[600005],sta[300005]; 7 int mxdp,P,al[300005],ansP1,ansP2,n,ans=300001,ind[300005]; 8 int dp1[300005],dp2[300005],far[300005],ltnx[300005]; 9 void link(int a,int b){l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b;} 10 void dfs(int p,int dep){//printf("dfs:%d\n",p); 11 al[p]=1;if(dep>=mxdp)mxdp=dep,P=p; 12 for(int i=fir[p];i;i=l[i])if(!ban[i]&&!al[to[i]])dfs(to[i],dep+1); 13 } 14 int DFS(int p,int dep){//printf("DFS:%d\n",p); 15 int flag=0; 16 al[p]=2;if(dep>=mxdp)mxdp=dep,P=p,flag=1; 17 for(int i=fir[p];i;i=l[i])if(!ban[i]&&al[to[i]]!=2)if(DFS(to[i],dep+1))flag=1; 18 if(flag)sta[dep+1]=p; 19 return flag; 20 } 21 void Dfs(int p,int fa,int dep,int bel){ 22 far[bel]=max(far[bel],dep); 23 for(int i=fir[p];i;i=l[i])if(to[i]!=fa&&!ind[to[i]])Dfs(to[i],p,dep+1,bel); 24 } 25 int main(){ 26 scanf("%d",&n); 27 for(int i=1,x,y;i<n;++i)scanf("%d%d",&x,&y),link(x,y),link(y,x); 28 dfs(1,0);mxdp=0;DFS(P,0); 29 for(int i=1;i<=n;++i)ind[sta[i]]=1,al[i]=0; 30 for(int i=1;i<=mxdp+1;++i)Dfs(sta[i],0,0,sta[i]);//,printf("%d ",sta[i]);puts(""); 31 for(int i=1;i<=mxdp+1;++i)dp1[sta[i]]=max(dp1[sta[i-1]],far[sta[i]]+i-1);//,printf("%d ",dp1[sta[i]]);puts(""); 32 for(int i=mxdp+1;i>=1;--i)dp2[sta[i]]=max(dp2[sta[i+1]],far[sta[i]]+mxdp+1-i);//,printf("%d ",dp2[sta[i]]);puts(""); 33 for(int i=1;i<=mxdp;++i)for(int j=fir[sta[i]];j;j=l[j])if(to[j]==sta[i+1])ltnx[i]=j>>1; 34 for(int i=1;i<=mxdp;++i){ 35 int an=max((dp1[sta[i]]+1>>1)+(dp2[sta[i+1]]+1>>1)+1,max(dp1[sta[i]],dp2[sta[i+1]]));//printf("%d\n",an); 36 if(an<ans)ans=an,num.clear(),num.push_back(ltnx[i]); 37 else if(an==ans)num.push_back(ltnx[i]); 38 } 39 sort(num.begin(),num.end()); 40 for(int i=1;i<=n;++i)al[i]=0; 41 ban[num[0]<<1]=ban[num[0]<<1|1]=1; 42 mxdp=0;dfs(1,0);mxdp=0;DFS(P,0);ansP1=sta[mxdp+2>>1];//for(int i=1;i<=mxdp+1;++i)printf("%d ",sta[i]);puts(""); 43 mxdp=0; 44 for(int i=1;i<=n;++i)if(!al[i])dfs(i,0);mxdp=0;DFS(P,0);ansP2=sta[mxdp+2>>1]; 45 printf("%d\n",ans); 46 printf("%d ",num.size());for(int i=0;i<num.size();++i)printf("%d ",num[i]);puts(""); 47 printf("%d %d %d %d\n",to[num[0]<<1],to[num[0]<<1|1],ansP1,ansP2); 48 }
思路积累:
- 树的直径的性质。
- 直径上双向枚举子树的dp,复杂度为O(2n)