模拟14 题解
T1[A. 旋转子段]
20%算法
枚举旋转起始点,再枚举旋转长度,得到旋转区间,再扫一边统计答案,
60%算法
考虑节省统计答案的复杂度,预处理出来每个节点的初始固定点个数
枚举旋转的中心,(可以是原来的点或某两个点中间的轴),再从中心点向两边扩展区间,出现了新的固定点时cnt++
对于区间外的前缀和O(1)查询
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #define R register 7 using namespace std; 8 const int maxn=100005; 9 inline int read() 10 { 11 int f=1,x=0;char ch=getchar(); 12 while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} 13 while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} 14 return f*x; 15 } 16 int n,a[maxn],t[maxn],per[maxn]; 17 int main() 18 { 19 n=read(); 20 for(R int i=1;i<=n;++i){ 21 a[i]=read(); 22 per[i]=per[i-1]; 23 if(i==a[i]) 24 per[i]++; 25 } 26 R int ans=per[n]; 27 for(int i=1;i<=2*n-1;++i) 28 { 29 int sum=0,x,cnt=0; 30 if(i&1)//奇数是指原点 31 { 32 x=(i+1)>>1; 33 if(a[x]==x)cnt++; 34 for(int l=x-1,r=x+1;l>=1&&r<=n;--l,++r) 35 { 36 sum=per[l-1]+per[n]-per[r]; 37 if(a[l]==r)cnt++; 38 if(a[r]==l)cnt++; 39 sum+=cnt; 40 ans=max(ans,sum); 41 } 42 } 43 else 44 { 45 x=i>>1; 46 for(int l=x,r=x+1;l>=1&&r<=n;--l,++r) 47 { 48 sum=per[l-1]+per[n]-per[r]; 49 if(a[l]==r)cnt++; 50 if(a[r]==l)cnt++; 51 sum+=cnt; 52 ans=max(ans,sum); 53 } 54 } 55 } 56 printf("%d\n",ans); 57 }
100%算法
假设[l,r]为最优解区间,则必定有l==a[r]或a[l]==r(否则一定可以将区间扩展或缩小)
那么将每组点对(i,a[i])按照l为较小值,r为较大值,放入下标为i+a[i]的vector中
此时的每个vector里存的点都是有相同的轴的点对,将vector中的点按照大区间包含小区间的方式排序,从小区间开始向外扩展,cnt++,记录枚举到当前区间的里面固定点个数
区间外仍然使用前缀和
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<vector> 7 #define R register 8 using namespace std; 9 const int maxn=500005; 10 inline int read() 11 { 12 int f=1,x=0;char ch=getchar(); 13 while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} 14 while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} 15 return f*x; 16 } 17 int n,a[maxn],per[maxn]; 18 struct node{ 19 int l,r; 20 }; 21 bool cmp(node x,node y){return x.l>y.l;} 22 vector<node>v[2*maxn]; 23 int main() 24 { 25 //freopen("data","r",stdin); 26 //freopen("1.out","w",stdout); 27 n=read(); 28 for(R int i=1;i<=n;++i){ 29 a[i]=read(); 30 node tt; 31 tt.l=min(i,a[i]),tt.r=max(i,a[i]); 32 v[i+a[i]].push_back(tt); 33 per[i]=per[i-1]; 34 if(i==a[i]) 35 per[i]++; 36 } 37 R int ans=per[n]; 38 for(int i=1;i<=2*n-1;++i) 39 { 40 sort(v[i].begin(),v[i].end(),cmp); 41 int cnt=0; 42 for(int k=0;k<v[i].size();++k) 43 { 44 int ll=v[i][k].l,rr=v[i][k].r; 45 int sum=per[ll-1]+per[n]-per[rr]; 46 cnt++; 47 sum+=cnt; 48 ans=max(ans,sum); 49 } 50 } 51 printf("%d\n",ans); 52 }
T2[B. 走格子]
把网格图建边成图对于每个非墙节点,连向与之相邻的四个格子(特判是不是墙),再连向四个方向能到的最近的墙的前一个格子,
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 #include<vector> 8 #define R register 9 #define INF 1061109567 10 using namespace std; 11 const int maxn=250005; 12 int n,m,a[505][505]; 13 int d[maxn],v[maxn]; 14 vector<int>r[505],c[505]; 15 int st,en; 16 struct node{ 17 int u,w,v,nxt; 18 }e[16*maxn];int h[maxn],nu; 19 void add(int x,int y,int z) 20 { 21 e[++nu].u=x; 22 e[nu].v=y; 23 e[nu].nxt=h[x]; 24 e[nu].w=z; 25 h[x]=nu; 26 } 27 int cal(int x,int y){return (x-1)*m+y;} 28 void spfa() 29 { 30 queue<int>q; 31 memset(d,0x3f,sizeof d); 32 d[st]=0,v[st]=1; 33 q.push(st); 34 while(q.size()) 35 { 36 int x=q.front(); 37 q.pop(),v[x]=0; 38 for(int i=h[x];i;i=e[i].nxt) 39 { 40 int y=e[i].v; 41 if(d[y]>d[x]+e[i].w){ 42 d[y]=d[x]+e[i].w; 43 if(!v[y])q.push(y),v[y]=1; 44 } 45 } 46 } 47 } 48 int main() 49 { 50 scanf("%d%d",&n,&m); 51 for(int i=1;i<=n;i++) 52 { 53 char ci[505]; 54 scanf("%s",ci+1); 55 for(int j=1;j<=m;j++) 56 { 57 if(ci[j]=='#')a[i][j]=1,r[i].push_back(j),c[j].push_back(i); 58 if(ci[j]=='C')st=cal(i,j); 59 if(ci[j]=='F')en=cal(i,j); 60 } 61 } 62 for(int i=1;i<=n;i++) 63 for(int j=1;j<=m;j++) 64 { 65 if(a[i][j])continue; 66 int x=cal(i,j); 67 if(!a[i][j-1])add(x,cal(i,j-1),1); 68 if(!a[i][j+1])add(x,cal(i,j+1),1); 69 if(!a[i-1][j])add(x,cal(i-1,j),1); 70 if(!a[i+1][j])add(x,cal(i+1,j),1); 71 int t1=lower_bound(c[j].begin(),c[j].end(),i)-c[j].begin(); 72 int t2=lower_bound(r[i].begin(),r[i].end(),j)-r[i].begin(); 73 int d1=min(c[j][t1]-i,i-c[j][t1-1]),d2=min(r[i][t2]-j,j-r[i][t2-1]); 74 int md=min(d1,d2); 75 if(c[j][t1]-1!=i) add(x,cal(c[j][t1]-1,j),md); 76 if(c[j][t1-1]+1!=i) add(x,cal(c[j][t1-1]+1,j),md); 77 if(r[i][t2]-1!=j) add(x,cal(i,r[i][t2]-1),md); 78 if(r[i][t2-1]+1!=j) add(x,cal(i,r[i][t2-1]+1),md); 79 } 80 spfa(); 81 int ans=d[en]; 82 if(ans>=INF)puts("no"); 83 else printf("%d\n",ans); 84 }
T3[C. 柱状图]
引问:1-N的任意序列,每个数加上或减去一个值,使的所有的数成为一个相同的数,求变化值之和的最小值
引理:变化后的数为这个序列中位数,此时变化值之和最小
证明:先将这个序列排序,设变化后的相同值为p,则小于p的所有数变化值之和为$1+2+...+(p-1)$大于p的数变化值之和为$1+2+...+n-p+1$
等差求和并化简为$p^2-(n+1)p+(n+n^2)/2$,单谷函数,由图像,取$(n+1)/2$时取到最小值
推广:任意一个序列,进行以上操作,都要变为这个序列的中位数,才能使解最优
30%算法 n^3暴力
最外层枚举屋顶的位置,然后枚举屋顶高度,再扫一边统计答案
60%算法
最外层枚举屋顶的位置,思考如何将枚举屋顶高度的过程优化,
定义 s[i]=a[i]+abs(pos-i) ,H为最高高度,det[i]为其变化的高度;
a[i]+abs(pos-i)=H-det[i],左式形成了一个序列,将左式序列变化det值使得它成为一个定值H
使用以上证明,所以就是找这个序列的中位数$O(n^2)$
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #define R register 7 #define ll long long 8 using namespace std; 9 const int maxn=100005; 10 inline int read() 11 { 12 int f=1,x=0;char ch=getchar(); 13 while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} 14 while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} 15 return f*x; 16 } 17 inline int ab(int x){return (x<0)?(-x):x;} 18 int n,a[maxn],s[maxn]; 19 int main() 20 { 21 // freopen("data","r",stdin); 22 n=read(); 23 int mx=n; 24 for(R int i=1;i<=n;++i) 25 a[i]=read(),mx=max(a[i],mx); 26 R ll ans=0x3f3f3f3f3f3f3f3f; 27 for(R int pos=1;pos<=n;++pos) 28 { 29 for(R int i=1;i<=n;++i) s[i]=a[i]+ab(pos-i); 30 R int mid=(n+1)>>1; 31 nth_element(s+1,s+mid,s+n+1); 32 R int val=s[mid],mx=max(pos,n-pos+1); 33 val=max(val,mx); 34 ll tot=0; 35 for(R int i=1;i<=n;i++) 36 tot+=ab(val-s[i]); 37 ans=min(ans,tot); 38 } 39 printf("%lld\n",ans); 40 }
理论60%算法2
发现一个性质:变化的总值sum 关于 屋顶高度h 的图像为单谷函数
可以这么想:如果屋顶高度特别高,那么每一个i都是增大的,sum就会随h单调递增,反之递减,当h适中时,代价会最小
用三分搞一下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 long long n,h[100010],ans=0x7fffffffffffffff,now,k,maxx; 4 long long check(const int he,const int pos){ 5 long long an=0; 6 for(int i=1;i<=n;i++) 7 an+=abs(he-abs(pos-i)-h[i]); 8 return an; 9 } 10 int main(){ 11 scanf("%lld",&n); 12 for(int i=1;i<=n;i++) scanf("%lld",&h[i]),maxx=max(maxx,h[i]); 13 for(int i=1;i<=n;i++){ 14 long long l=n,r=maxx*3; 15 while(l<r-2){ 16 long long lmid=l+(r-l)/3,rmid=l+(r-l)*2/3; 17 if(check(lmid,i)<=check(rmid,i)) r=rmid; 18 else l=lmid; 19 } 20 //cout<<i<<" "<<l<<endl; 21 ans=min(ans,check(l,i)); 22 ans=min(ans,check(l+1,i)); 23 ans=min(ans,check(l+2,i)); 24 } 25 printf("%lld",ans); 26 }
100%算法:「乱搞」「模拟退火」
打表发现,sum关于屋顶位置是个多谷函数(就是没有任何规律)
模拟退火可A
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 #include<iostream> 3 #include<cstdio> 4 #include<cstdlib> 5 #include<cstring> 6 #include<cmath> 7 #include<algorithm> 8 #define R register 9 #define ll long long 10 using namespace std; 11 const int maxn=100005; 12 const double eps=1e-5; 13 const double delta=0.983; 14 const double jl=1; 15 inline int read() 16 { 17 int f=1,x=0;char ch=getchar(); 18 while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} 19 while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} 20 return f*x; 21 } 22 inline int ab(int x){return (x<0)?(-x):x;} 23 int n,a[maxn],s[maxn]; 24 ll ans=0x3f3f3f3f3f3f3f3f; 25 inline ll cal(int pos) 26 { 27 for(int i=1;i<=n;i++) s[i]=a[i]+ab(pos-i); 28 int mid=(n+1)>>1; 29 nth_element(s+1,s+mid,s+n+1); 30 int val=s[mid],mx=max(pos,n-pos+1); 31 val=max(val,mx); 32 ll tot=0; 33 for(int i=1;i<=n;i++) 34 tot+=ab(val-s[i]); 35 return tot; 36 } 37 void SA() 38 { 39 double T=1000; 40 int now=(n+1)>>1; 41 ll nowans=cal(now); 42 while(T>eps) 43 { 44 int tmp=now+(2LL*rand()-RAND_MAX)*T*0.000001; 45 if(T<jl) tmp=max(tmp,1),tmp=min(tmp,n); 46 else tmp=(tmp%n+n)%n+1; 47 ll tmpans=cal(tmp); 48 ll DE=tmpans-nowans; 49 if(DE<0||exp(-DE/T)*RAND_MAX>rand())nowans=tmpans,now=tmp; 50 if(tmpans<ans)ans=tmpans; 51 T*=delta; 52 } 53 } 54 int main() 55 { 56 //freopen("data","r",stdin); 57 srand(time(0)); 58 n=read(); 59 for(R int i=1;i<=n;++i) 60 a[i]=read(); 61 SA(); 62 printf("%lld\n",ans); 63 return 0; 64 }
推了一下午的式子,然后发现,最一开始的定义就有些问题,所以努力无果QAQ
自己瞎颓式子的时候一定要严谨证明
有一些东西不必要用个式子表示,用理说明(感性理解)就好
打表打表找规律