[考试反思]0909csp-s模拟测试41:反典
T1:夜莺与玫瑰
T2:影子
T3:玫瑰花精
说在前面:我是反面典型!!!不要学我!!!
说在前面:向rank1某脸学习,不管是什么题都在考试反思后面稍微写一下题解。
这次是真的真的运气好。。。
这次知识点上还可以,但是答题策略出了问题。。。
幸亏平时攒的rp生效了(前一天比较耐心的教不少人题来着,与某脸形成对比rp++)(然而可能也并不耐心)
考试过程:
考前,大脸说他可能不是第一个AK csp-s赛制的人,但是应该是第一个AC的。
于是我的目标就变成了抢第一个AC:我要快速切掉最简单的题!!!
考前flag必倒。。。
于是反正第一步就是先找到最简单的题吧。。。
过了一边三道题:T1显然需要找规律并不是很快能切掉,T2数据结构去死吧不会
等等,T3这是啥?大力模拟?STL set可以水?
发现自己hack不掉,然后就开始码。
好写好调。(其实都没怎么调,过编译就过样例)
刚好这题没有大一点的样例,我就直接交了。
然而心急了。细节打错了!
不要给自己凭空施压!
从时间上看的确是第一个,但不是第一个AC。。。
考后5分钟乱改就A了。
然后相较于T2当然我宁愿滚去推式子于是T2暴力都没有打。
然后就去推式子了。然后就差不多死了。(详见下方题解)
暴力很好弄啊。。。但是用了一个小时
但是我的暴力始终过不了小样例里的某一个询问,其实是我手抄样例时抄错了。
我还以为我打错了,心态略崩。抱着骗分的想法继续了。
然后发现会MLE,优化,又用了一个小时。。。
这还是比某孩子tdcp直接MLE0好一些的。
不要忘了还有MLE这种错。
于是我还有20分钟,不抛弃不放弃去T2打暴力。
14分钟完成,过不了样例结果发现和我手模结果一致。。。我手模都不对。。。?!
又看错题了!!!
做题之前不管时间多么紧迫一定要好好看题再手模一个样例!!!不要白费时间!!!
然后就结束了,平时一定会炸一道题,我就交了两个如果炸掉一个的话就只剩100分/80分了
但是rp爆发没有爆炸,拿到了100+空白+80=180。
然后教练说:吴迪又是一个反面示范。
我错了了了了了了了。。。。。
一定要打暴力!!!!
题解区:
T1:夜莺与玫瑰
我的式子是$ \sum\limits_{i=1}^{n-1} \sum\limits_{j=1}^{m-1} [gcd(i,j)==1] \times (min(i,n-i) \times (m-j) + min(j,m-j) \times (n-i) – min(i,n-i) \times min(j,m-j)) $
很长,很丑,但是有具体含义,我给Alpaca解释过了但是实在太复杂,对实际含义感兴趣的可以来找我。
现在的问题是化简。
我们可以发现最不好搞的两项就是min的项,但是只要分类讨论i和n-i的关系以及j同理,那么就可求了。
当i和j分别小于n/2和m/2时,式子的值可以化简为$ \sum\limits_{i=1}^{n/2} \sum\limits_{j=1}^{m/2} [gcd(i,j)==1] n*j+m*i-i*j*3 $
在其他情况下,同理可以化简为只含有ij,im,jn,ij的项
那么我们可以预处理ij,i,j,1在gcd(i,j)==1时的前缀和,4个二维数组干上再O(1)计算即可
发现会MLE。
那么我们就可以离线所有询问,使询问的n递增,我们一次只处理二位数组的一行就行了。
1 //for(int i=1;i<=n-1;++i)for(int j=1;j<=m-1;++j)if(gcd(i,j)==1) 2 // ans+=min(n-i,i)*(m-j)+(n-i)*min(m-j,j)-min(n-i,i)*min(m-j,j); 3 #include<cstdio> 4 #include<algorithm> 5 using namespace std; 6 const long long mod=1<<30; 7 long long sig_1[4005],sig_i[4005],sig_j[4005],sig_ij[4005]; 8 long long si_1[4005],si_i[4005],si_j[4005],si_ij[4005]; 9 long long s_1,s_i,s_j,s_ij; 10 int gcd(int a,int b){return b?gcd(b,a%b):a;} 11 struct ps{int m,n,ord,ans;}p[10005]; 12 bool com_n(ps a,ps b){return a.n<b.n;} 13 bool com_ord(ps a,ps b){return a.ord<b.ord;} 14 void Mod(long long &p){if(p>=mod)p-=mod;} 15 int main(){ 16 int T,p1=0,p2=0; 17 scanf("%d",&T); 18 for(int i=1;i<=T;++i)scanf("%d%d",&p[i].n,&p[i].m),p[i].ord=i; 19 sort(p+1,p+1+T,com_n); 20 for(int i=1;i<=T;++i){ 21 int n=p[i].n,m=p[i].m; 22 while(p1<n/2){ 23 p1++; 24 for(int j=1;j<=2000;++j){ 25 if(gcd(p1,j)==1)s_1++,Mod(s_i+=p1),Mod(s_j+=j),(s_ij+=p1*j)%=mod; 26 Mod(sig_1[j]+=s_1),Mod(sig_i[j]+=s_i), 27 Mod(sig_j[j]+=s_j),Mod(sig_ij[j]+=s_ij); 28 } 29 s_1=s_i=s_j=s_ij=0; 30 } 31 while(p2<n-1){ 32 p2++; 33 for(int j=1;j<=4000;++j){ 34 if(gcd(p2,j)==1)s_1++,Mod(s_i+=p2),Mod(s_j+=j),(s_ij+=p2*j)%=mod;//if(j<=2)printf("--%lld\n",s_1); 35 Mod(si_1[j]+=s_1),Mod(si_i[j]+=s_i), 36 Mod(si_j[j]+=s_j),Mod(si_ij[j]+=s_ij);//if(j<=2)printf("%lld\n",si_1[j]); 37 } 38 s_1=s_i=s_j=s_ij=0; 39 } 40 p[i].ans=(mod+( 41 (-n*m*sig_1[m/2]-4*sig_ij[m/2]+2*sig_i[m/2]*m+2*sig_j[m/2]*n) 42 + 43 (n*m*si_1[m-1]+si_ij[m-1]-si_i[m-1]*m-si_j[m-1]*n) 44 )%mod)%mod;//printf("%lld %lld %lld %lld\n",si_1[m-1],si_ij[m-1],si_i[m-1],si_j[m-1]); 45 } 46 sort(p+1,p+1+T,com_ord); 47 for(int i=1;i<=T;++i)printf("%lld\n",(p[i].ans*2+p[i].n+p[i].m)%mod); 48 }
View Code
记录思想(包括其它大神用的):
- 离线
- 记忆化gcd(去掉log)
- 反演(带根号回答询问)
- 拆式子,去min,max,保留不同的0,1,2次项求前缀和
- 卡空间,运行内存实际很小,2×4000×4000在128M下可过
- 询问排序递增,单调指针硬扫
T2:影子
考场上没打,不多说
前置知识:树的直径。
树上任意一点开始dfs,距离最远的点一定是直径的两个端点之一。
接下来按照点权排序,从大到小解锁每一个点。
然后我们用并查集维护当前树的直径以及端点,每次合并AB时分别讨论:
1.直径就是A里的直径
2.就是B里的
3-6.是从A的端点里选一个,走到目前枚举的边,再走到B的一个端点
取最优决策,用直径×点权更新ans。
因为前两种情况里直径一定被更大或相等的点权枚举过,所以用较小的点不会使ans变大,故决策最优。
预处理,倍增求LCA,树上前缀和,实现O(log)查询两点距离。
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 #define int long long 6 int t,n,fir[100005],l[200005],to[200005],cnt,f[100005],al[100005],p1[100005],p2[100005]; 7 int ans,v[200005],d[100005],F[20][100005],dep[100005],dt[100005]; 8 struct ps{int p,v;friend bool operator<(ps a,ps b){return a.v>b.v;}}p[100005]; 9 void link(int a,int b,int vv){l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b;v[cnt]=vv;} 10 int find(int k){return f[k]==k?k:f[k]=find(f[k]);} 11 void dfs(int p,int fa){ 12 F[0][p]=fa;dep[p]=dep[fa]+1; 13 for(int i=1;i<=19;++i)F[i][p]=F[i-1][F[i-1][p]]; 14 for(int i=fir[p];i;i=l[i])if(to[i]!=fa)dt[to[i]]=dt[p]+v[i],dfs(to[i],p); 15 } 16 int DT(int a,int b){//printf("DT:%lld %lld\n",a,b); 17 int A=a,B=b,subdep=dep[a]-dep[b]; 18 if(subdep<0)subdep=-subdep,a^=b^=a^=b,A^=B^=A^=B; 19 for(int i=0;i<=19;++i)if(subdep&1<<i)a=F[i][a]; 20 if(a==b)return dt[A]-dt[B]; 21 for(int i=19;~i;--i)if(F[i][a]!=F[i][b])a=F[i][a],b=F[i][b]; 22 a=F[0][a]; 23 return dt[A]+dt[B]-dt[a]-dt[a]; 24 } 25 main(){//freopen("b.in","r",stdin); 26 //freopen("x.in","r",stdin);//freopen("my.out","w",stdout); 27 scanf("%lld",&t); 28 while(t--){ 29 scanf("%lld",&n); 30 for(int i=1;i<=n;++i)f[i]=p1[i]=p2[i]=i; 31 for(int i=1;i<=n;++i)scanf("%lld",&p[i].v),p[i].p=i; 32 sort(p+1,p+1+n); 33 for(int i=1,a,b,vv;i<n;++i)scanf("%lld%lld%lld",&a,&b,&vv),link(a,b,vv),link(b,a,vv); 34 dfs(1,0); 35 for(int i=1;i<=n;++i){ 36 int P=p[i].p;al[P]=1;//printf("Unlocked : %lld\n",P); 37 for(int j=fir[P];j;j=l[j])if(al[to[j]]){ 38 int F=find(to[j]);f[F]=P; 39 int d1=DT(P,p1[P]),d2=DT(P,p2[P]),totd,c1,c2; 40 if(d1>d2)totd=d1,c1=p1[P];else totd=d2,c1=p2[P];//printf("%lld %lld %lld %lld\n",d1,d2,p1[P],p2[P]); 41 d1=DT(to[j],p1[F]),d2=DT(to[j],p2[F]); 42 if(d1>d2)totd+=d1,c2=p1[F];else totd+=d2,c2=p2[F];//printf("%lld %lld %lld %lld\n",d1,d2,p1[F],p2[F]); 43 if(totd+v[j]>d[P])d[P]=totd+v[j],p1[P]=c1,p2[P]=c2;//,puts("UPD"); 44 if(d[F]>d[P])d[P]=d[F],p1[P]=p1[F],p2[P]=p2[F]; 45 ans=max(ans,d[P]*p[i].v);//printf("%lld %lld %lld %lld\n",to[j],d[P],p1[P],p2[P]); 46 } 47 }printf("%lld\n",ans); 48 for(int i=1;i<=n;++i)fir[i]=d[i]=al[i]=0; 49 cnt=ans=0; 50 } 51 }
View Code
思路积累:
- 并查集维护联通块
- 树的直径性质
- 点分治做法(没打,但是简单粗暴,难打好想)
- 贪心,按顺序解锁点
- 预处理做到O(log)求树上点对距离{树上差分+倍增LCA}
- 要会打暴力
T3:玫瑰花精
set大力模拟即可。
维护两个set,一个存两个花精之间的间隔,另一个存位置。
不断按照题意insert,erase即可。
1 #include<cstdio> 2 #include<set> 3 #include<vector> 4 using namespace std; 5 struct ps{ 6 int dt,sp; 7 friend bool operator<(ps a,ps b){ 8 return a.dt/2>b.dt/2||(a.dt/2==b.dt/2&&a.sp<b.sp); 9 } 10 }; 11 set<ps>bl;set<int>p; 12 int n,m,o,x,ip[1000005]; 13 int main(){//freopen("c.in","r",stdin); 14 scanf("%d%d",&n,&m); 15 while(m--){ 16 scanf("%d%d",&o,&x); 17 if(o==1){ 18 if(p.size()==0)p.insert(0),ip[x]=0,puts("1"); 19 else if(p.size()==1){ 20 int xx=*p.begin(); 21 if(xx>=n>>1)p.insert(0),ip[x]=0,puts("1"),bl.insert((ps){xx,0}); 22 else p.insert(n-1),ip[x]=n-1,printf("%d\n",n),bl.insert((ps){n-xx-1,xx}); 23 } 24 else{ 25 int p1=*p.begin(),p2=n-(*(--p.end()))-1,p3=(*bl.begin()).dt/2,p3p=(*bl.begin()).sp; 26 if(p1>=p2&&p1>=p3)p.insert(0),ip[x]=0,puts("1"),bl.insert((ps){p1,0}); 27 else if(p2>p3)p.insert(n-1),ip[x]=n-1,printf("%d\n",n),bl.insert((ps){p2,n-p2-1}); 28 else{ 29 int l=p3p,r=(*bl.begin()).dt+p3p,xx=l+r>>1; 30 p.insert(xx);ip[x]=xx;printf("%d\n",xx+1); 31 bl.insert((ps){r-xx,xx});bl.insert((ps){xx-l,l}); 32 bl.erase(bl.begin());//printf("%d %d %d\n",l,r,xx); 33 } 34 } 35 }else{ 36 if(p.size()<=2)p.erase(ip[x]),bl.clear(); 37 else{ 38 if(ip[x]==(*p.begin()))bl.erase((ps){(*p.upper_bound(ip[x]))-ip[x],ip[x]}); 39 else if(ip[x]>=(*(--p.end())))bl.erase((ps){ip[x]-(*(--p.end())),(*(--p.end()))}); 40 else{ 41 int lp=*(--(p.lower_bound(ip[x]))),rp=*(p.upper_bound(ip[x])); 42 bl.erase((ps){rp-ip[x],ip[x]});bl.erase((ps){ip[x]-lp,lp}); 43 bl.insert((ps){rp-lp,lp}); 44 } 45 p.erase(ip[x]); 46 } 47 } 48 } 49 }
View Code
思路积累:
- STL的熟练应用
- 线段树维护最长区间(同《旅馆Hotel》,我还没有A)
- 代码实现细节,注意先后顺序
- 重载比较函数的多样性