考完19了再写14,我也是够咕的。
14的题很好,也充分暴露了我的问题。
T1是个分析性质推结论的题
对于区间[L,R],不妨设a[L]!=a[R],那么两个端点对答案没有贡献,也就是[L+1,R],[L,R-1]都是符合题意的区间。
即 最优的区间的两个端点一定相等
然后把每个数的位置和大小作为区间的两端点,统计答案即可。
1 #include<bits/stdc++.h> 2 #define F(n) for(int i=1;i<=n;i++) 3 int a[500005],sum[500005],n,ans; 4 std::vector< std::pair<int,int> >k[500005<<1]; 5 int main(){ 6 scanf("%d",&n); 7 F(n)scanf("%d",&a[i]),sum[i]=sum[i-1]+(a[i]==i);ans=sum[n]; 8 F(n)k[a[i]+i].push_back(std::make_pair(std::min(i,a[i]),std::max(i,a[i]))); 9 F((n<<1))sort(k[i].begin(),k[i].end()); 10 F((n<<1)) 11 for(int t=k[i].size(),j=t-1,last=0;j>=0;j--,last++) 12 ans=std::max(ans,last+1+sum[k[i][j].first-1]+sum[n]-sum[k[i][j].second]); 13 std::cout<<ans; 14 }
T2走格子
关键在于如何将题目抽象成图论模型,而不是只一味的搜索。
这道题没有太多的限制,只有移动到每个格子的距离的不同。
因此直接建边,跑dijstra即可
1 #include<bits/stdc++.h> 2 #define MAXN 505 3 using namespace std; 4 const int h[5]={0,1,-1,0,0},l[5]={0,0,0,1,-1}; 5 int mp[MAXN][MAXN],endx,endy,n,m,tot=0; 6 priority_queue<pair<int,int> >Q; 7 vector<int>zh[MAXN],zl[MAXN]; 8 bool vst[MAXN*MAXN]; 9 int ans=0x7f7f7f7f,stax,stay; 10 int p[MAXN][MAXN],d[MAXN*MAXN],nxt[MAXN*MAXN*100],val[MAXN*MAXN*100],to[MAXN*MAXN*100],cnt,head[MAXN*MAXN]; 11 void add(int u,int v,int w) 12 { 13 to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;val[cnt]=w; 14 return ; 15 } 16 int dijstra() 17 { 18 memset(d,0x3f,sizeof(d));d[p[stax][stay]]=0; 19 Q.push(make_pair(0,p[stax][stay])); 20 while(Q.size()) 21 { 22 int k=Q.top().second;Q.pop(); 23 if(k==p[endx][endy])return d[k]; 24 if(vst[k])continue; 25 vst[k]=1; 26 for(int i=head[k];i;i=nxt[i]) 27 { 28 int y=to[i]; 29 if(d[y]>d[k]+val[i]) 30 { 31 d[y]=d[k]+val[i]; 32 Q.push(make_pair(-d[y],y)); 33 } 34 } 35 } 36 return 0x7f7f7f7f; 37 } 38 int main() 39 { 40 scanf("%d%d",&n,&m); 41 for(int i=1;i<=n;i++) 42 { 43 for(int j=1;j<=m;j++) 44 { 45 p[i][j]=++tot; 46 char c=getchar(); 47 while(c!='C'&&c!='F'&&c!='.'&&c!='#')c=getchar(); 48 if(c=='C')stax=i,stay=j,mp[i][j]=1; 49 else if(c=='F')endx=i,endy=j,mp[i][j]=1; 50 else if(c=='.')mp[i][j]=1; 51 else mp[i][j]=0; 52 } 53 } 54 for(int i=1;i<=n;i++) 55 for(int j=1;j<=m;j++) 56 if(!mp[i][j])zh[i].push_back(j); 57 for(int j=1;j<=m;j++) 58 for(int i=1;i<=n;i++) 59 if(!mp[i][j])zl[j].push_back(i); 60 for(int i=1;i<=n;i++) 61 for(int j=1;j<=m;j++) 62 { 63 if(!mp[i][j])continue; 64 if(i^1&&mp[i-1][j])add(p[i][j],p[i-1][j],1); 65 if(i^n&&mp[i+1][j])add(p[i][j],p[i+1][j],1); 66 if(j^1&&mp[i][j-1])add(p[i][j],p[i][j-1],1); 67 if(j^m&&mp[i][j+1])add(p[i][j],p[i][j+1],1); 68 int up=*(--lower_bound(zl[j].begin(),zl[j].end(),i)); 69 int down=*(lower_bound(zl[j].begin(),zl[j].end(),i)); 70 int lef=*(--lower_bound(zh[i].begin(),zh[i].end(),j)); 71 int rig=*(lower_bound(zh[i].begin(),zh[i].end(),j)); 72 int dis=min(i-up,min(down-i,min(j-lef,rig-j))); 73 add(p[i][j],p[up+1][j],dis); 74 add(p[i][j],p[down-1][j],dis); 75 add(p[i][j],p[i][lef+1],dis); 76 add(p[i][j],p[i][rig-1],dis); 77 } 78 ans=dijstra(); 79 if(ans==0x7f7f7f7f)cout<<"no"<<endl; 80 else cout<<ans<<endl; 81 return 0; 82 }
T3柱状图 好题标记
这道题考察的是三分。
观察一下可知对于某一位置来说,其高度大小与最终答案的关系是单谷的。
(当你得到一个最优答案的时候,如果变大会使其余的柱子在原基础上再上升一个,反之亦然)
于是60分做法诞生:
枚举位置,三分高度,统计答案 O(n^2logn)
1 #include<bits/stdc++.h> 2 #define MAXN 100005 3 #define int long long 4 #define F(x) for(int i=1;i<=n;i++) 5 #define re register 6 #define min(a,b) (a)<(b)?(a):(b) 7 using namespace std; 8 int a[MAXN],now,n,maxn; 9 inline int Get(int h) 10 { 11 int ans=abs(h-a[now]); 12 F(n) 13 { 14 if(i==now)continue; 15 if(h-abs(i-now)<=0)return 0x7f7f7f7f7f7f7ff; 16 ans+=abs(a[i]-(h-abs(i-now))); 17 } 18 return ans; 19 } 20 inline int San_fen() 21 { 22 int l=now,r=maxn*2; 23 while(l+2<r) 24 { 25 int lmid=l+(r-l)/3; 26 int rmid=r-(r-l)/3; 27 if(Get(lmid)<Get(rmid))r=rmid; 28 else l=lmid; 29 } 30 return min(Get(l),min(Get(l+1),Get(l+2))); 31 } 32 signed main() 33 { 34 re int ans=0x7f7f7f7f7f7f7ff; 35 scanf("%lld",&n); 36 F(n)scanf("%lld",&a[i]),maxn=max(maxn,a[i]); 37 for(now=1;now<=n;now++) 38 ans=min(ans,San_fen()); 39 cout<<ans<<endl; 40 return 0; 41 }
考虑优化,对于统计答案,我们分情况讨论一下
(讨论个毛线)详情见这里
主要说一下skyh的垃圾退火(雾)
模拟退火是一个特别神(kan)奇(lian)的算法。
是这样的:
根据物理学常识我们可以知道,物体内能越小越稳定。
所谓的模拟退火,就是模拟这个过程。
1.设置初始温度T0
2.找出下一个目标位置
3.尝试用新的答案更新旧的答案
4.降温
是不是一脸蒙蔽?
详细来说:
1 while(T>eps) //终止边界
2 {
3 tmp=now+(rand()*2ll-RAND_MAX)*T*0.000001; //温度越高,变化越大
4 if(T<1.0)tmp=max(tmp,1ll),tmp=min(tmp,n); //当温度小于一定程度的时候,让下一个位置尽量小地变化
5 else tmp=(tmp%n+n)%n+1; //让下一个位置在要求的范围内
6 tmpans=calc(tmp);D=-fabs(tmpans-nowans); //找到新的解
7 if(tmpans<nowans||exp(D/T)*RAND_MAX>rand())nowans=tmpans,now=tmp; //根据概率确定答案是否改变
8 if(tmpans<ans)ans=tmpans; //更新最终的答案
9 T*=0.975; //降温
10 }
根据前人的经验,这个更新的概率是exp(D/T)(D是变化量绝对值的相反数,T是当前温度)
最后附上完整代码
1 #include<bits/stdc++.h> 2 #define MAXN 100005 3 #define int long long 4 using namespace std; 5 int yx[MAXN],n,now; 6 struct Bit{ 7 int tr[2][MAXN]; 8 void insert(int x,int val,int opt) 9 { 10 for(int i=x;i<=n;i+=i&-i)tr[0][i]+=val,tr[1][i]+=opt; 11 return ; 12 } 13 pair<int,int> query(int x) 14 { 15 int num=0,ans=0; 16 for(int i=x;i;i-=i&-i)num+=tr[1][i],ans+=tr[0][i]; 17 return make_pair(ans,num); 18 } 19 }T[2]; 20 int sa[MAXN],sb[MAXN],ap[MAXN],bp[MAXN],ans=0x7f7f7f7f7f7f7ff,maxa,a[MAXN],b[MAXN]; 21 inline int check(int x) 22 { 23 if(x<now||x<(n-now+1))return 0x7f7f7f7f7f7f7ff; 24 int ans=abs(x-yx[now]); 25 int num=upper_bound(a+1,a+n+1,x-now)-a-1; 26 pair<int,int> w=T[0].query(num); 27 ans+=abs(w.first-w.second*(x-now))+abs(sa[now-1]-w.first-(now-1-w.second)*(x-now)); 28 num=upper_bound(b+1,b+n+1,x+now)-b-1; 29 w=T[1].query(num); 30 ans+=abs(w.first-(now+x)*w.second)+abs(sb[n]-sb[now]-w.first-(n-now-w.second)*(now+x)); 31 return ans; 32 } 33 inline int san_fen() 34 { 35 int l=1,r=maxa*2; 36 while(l+2<r) 37 { 38 int lmid=l+(r-l)/3, 39 rmid=r-(r-l)/3; 40 if(check(lmid)<check(rmid))r=rmid; 41 else l=lmid; 42 } 43 return min(check(l+1),min(check(l+2),check(l))); 44 } 45 signed main() 46 { 47 cin>>n; 48 for(int i=1;i<=n;i++) 49 { 50 scanf("%lld",&yx[i]); 51 ap[i]=a[i]=yx[i]-i; 52 bp[i]=b[i]=yx[i]+i; 53 sa[i]=sa[i-1]+a[i]; 54 sb[i]=sb[i-1]+b[i]; 55 maxa=max(maxa,yx[i]); 56 } 57 sort(a+1,a+n+1);sort(b+1,b+n+1); 58 for(int i=1;i<=n;i++) 59 { 60 ap[i]=lower_bound(a+1,a+n+1,ap[i])-a; 61 bp[i]=lower_bound(b+1,b+n+1,bp[i])-b; 62 } 63 for(int i=1;i<=n;i++)T[1].insert(bp[i],yx[i]+i,1); 64 for(now=1;now<=n;now++) 65 { 66 T[1].insert(bp[now],-(yx[now]+now),-1); 67 ans=min(san_fen(),ans); 68 T[0].insert(ap[now],yx[now]-now,1); 69 } 70 cout<<ans<<endl; 71 return 0; 72 }
1 #include<bits/stdc++.h> 2 #define MAXN 100005 3 #define int long long 4 #define re register 5 using namespace std; 6 double eps=1e-5,T=100,D; 7 int mid,n,a[MAXN],b[MAXN]; 8 inline int calc(int x) 9 { 10 for(int i=1;i<=n;i++)b[i]=a[i]+abs(i-x); 11 nth_element(b+1,b+mid,b+n+1); 12 re int now=max(b[mid],max(x,n-x+1)),ans=0; 13 for(int i=1;i<=n;i++) 14 { 15 if(now-abs(i-x)<=0)return 0x7f7f7f7f7f; 16 ans+=abs(now-b[i]); 17 } 18 return ans; 19 } 20 signed main() 21 { 22 srand((unsigned)time(0)); 23 cin>>n; 24 mid=(1+n)>>1; 25 for(int i=1;i<=n;i++)scanf("%lld",&a[i]); 26 int ans=0x7f7f7f7f7f7f; 27 int now=(1+n>>1); 28 int nowans=calc(now),tmp,tmpans; 29 while(T>eps) 30 { 31 tmp=now+(rand()*2ll-RAND_MAX)*T*0.000001; 32 if(T<1.0)tmp=max(tmp,1ll),tmp=min(tmp,n); 33 else tmp=(tmp%n+n)%n+1; 34 tmpans=calc(tmp);D=-fabs(tmpans-nowans); 35 if(tmpans<nowans||exp(D/T)*RAND_MAX>rand())nowans=tmpans,now=tmp; 36 if(tmpans<ans)ans=tmpans; 37 T*=0.975; 38 } 39 cout<<ans<<endl; 40 return 0; 41 }
模拟退火是一个特别优秀的算法,请不要针对算法,(尽管你可以针对脸某)。