模拟14 题解

A. 旋转子段

对于位置i上的数$a_i$,易知有且仅有一个旋转点使它旋转到$a_i$,这个旋转点是$\frac{i+a_i}{2}$

因为旋转点分落在点上的旋转点和两点之间的旋转点,除2不易处理。

不妨将位置i上的数存放在$i+a_i$处。

设1~i的原本固定点为$pre_i$个,后缀同理为$suf_i$,这两条信息可以预处理。

那么问题转化为找一个区间,使得

$pre_{l-1}+suf_{r+1}+sum_{l+r}$最大。

$sum_{l+r}$的意思为对于l+r旋转点,范围l,r内能以它为旋转点变为固定点的个数。

考虑每个旋转点,当它向两边拓展一个长度。

仅在这个长度上存在至少一个会被旋转到固定点的点时能使答案更优。

所以将同一旋转点的一些点压进vector,

枚举每个旋转点vector里的每个点,二分查找它对称的点的坐标,

尝试更新答案。

(二分查找的操作是单调的,单调指针也可做,懒得打

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<vector>
 5 #include<algorithm>
 6 using namespace std;
 7 const int N=500100;
 8 int n,ans=0,a[N],pre[N],suf[N];
 9 vector<int> ve[2*N];
10 int main(){
11     scanf("%d",&n);
12     for(int i=1;i<=n;++i){
13         scanf("%d",&a[i]);
14         ve[i+a[i]].push_back(i);
15         pre[i]=pre[i-1]+(a[i]==i);
16     }
17     for(int i=n;i;--i) suf[i]=suf[i+1]+(a[i]==i);
18     ans=pre[n];
19     for(int i=1;i<=2*n;++i){
20         if(ve[i].empty()) continue;
21         if(i&1){
22             int p=i+1>>1;//右侧
23             for(int j=0;j<ve[i].size();++j){
24                 if(ve[i][j]<p){
25                     int s=p+(p-ve[i][j])-1;
26                     ans=max(ans,upper_bound(ve[i].begin(),ve[i].end(),s)-ve[i].begin()-j+pre[ve[i][j]-1]+suf[s+1]);
27                 }
28                 if(ve[i][j]>=p){
29                     int s=p-(ve[i][j]-p)-1;
30                     ans=max(ans,j+ve[i].begin()-lower_bound(ve[i].begin(),ve[i].end(),s)+1+pre[s-1]+suf[ve[i][j]+1]);
31                 }
32             }
33         }
34         else{
35             int p=i>>1;
36             for(int j=0;j<ve[i].size();++j){
37                 if(ve[i][j]<p){
38                     int s=p+(p-ve[i][j]);
39                     ans=max(ans,upper_bound(ve[i].begin(),ve[i].end(),s)-ve[i].begin()-j+pre[ve[i][j]-1]+suf[s+1]);
40                 }
41                 if(ve[i][j]>p){
42                     int s=p-(ve[i][j]-p);
43                     ans=max(ans,j+ve[i].begin()-lower_bound(ve[i].begin(),ve[i].end(),s)+1+pre[s-1]+suf[ve[i][j]+1]);
44                 }
45             }  
46         }
47     }
48     printf("%d\n",ans);
49     return 0;
50 }
View Code

 

 


 

 

B. 走格子

对两种情况建边:

1.暴力走向旁边的四个格子,边权为1

2.向四个方向的墙开一枪,走向最近的墙以到达子弹打到的墙,边权为由当前点到最近墙的距离。

显然开枪的目的是到达子弹打到的位置,而开枪不需要时间,所以这个算法是没有问题的。

跑最短路即可。

至于如何处理出每个点向它最近的墙的距离。

将每个墙同时入队,bfs即可。

 1 #include<algorithm>
 2 #include<iostream>
 3 #include<cstdio>
 4 #include<cstring>
 5 #include<vector>
 6 #include<queue>
 7 using namespace std;
 8 const int N=505;
 9 const int dx[4]={1,-1,0,0};
10 const int dy[4]={0,0,1,-1};
11 int n,m,stx,sty,edx,edy,front,tail;
12 int map[N][N],dis[N][N],wal[N][N];
13 bool vis[N][N];
14 pair<int,int> que[N*N];
15 priority_queue<pair<int,pair<int,int> > > p;
16 char ch;
17 vector<int> h[N],l[N];
18 int main(){
19     scanf("%d%d",&n,&m);
20     front=1; tail=0;
21     for(int i=1;i<=n;++i){
22         for(int j=1;j<=m;++j){
23             scanf(" %c",&ch);
24             if(ch=='#'){
25                 map[i][j]=0; vis[i][j]=1;
26                 h[i].push_back(j);
27                 l[j].push_back(i);
28                 que[++tail]=make_pair(i,j);
29             }
30             else map[i][j]=1;
31             if(ch=='C') stx=i,sty=j;
32             if(ch=='F') edx=i,edy=j;
33         }
34     }
35     while(front<=tail){
36         int x=que[front].first,y=que[front++].second,tx,ty;
37         for(int i=0;i<4;++i){
38             tx=x+dx[i],ty=y+dy[i];
39             if(!tx||!ty||tx==n+1||ty==m+1) continue;
40             if(map[tx][ty]&&!vis[tx][ty]){
41                 vis[tx][ty]=1;
42                 wal[tx][ty]=wal[x][y]+1;
43                 que[++tail]=make_pair(tx,ty);
44             }
45         }
46     }
47     for(int i=1;i<=n;++i) sort(h[i].begin(),h[i].end());
48     for(int i=1;i<=m;++i) sort(l[i].begin(),l[i].end());
49     
50     memset(vis,0,sizeof(vis));
51     memset(dis,0x3f,sizeof(dis));
52     p.push(make_pair(0,make_pair(stx,sty)));
53     dis[stx][sty]=0;
54     while(!p.empty()){
55         int x=p.top().second.first,y=p.top().second.second,tx,ty;
56         p.pop();
57         if(vis[x][y]) continue;
58         vis[x][y]=1;
59         if(x==edx&&y==edy){
60             printf("%d\n",dis[x][y]);
61             return 0;
62         }
63         for(int i=0;i<4;++i){ 
64             tx=x+dx[i],ty=y+dy[i];
65             if(map[tx][ty]&&dis[x][y]+1<dis[tx][ty]){
66                 dis[tx][ty]=dis[x][y]+1;
67                 p.push(make_pair(-dis[tx][ty],make_pair(tx,ty)));
68             }
69         }
70         tx=x,ty=(*(lower_bound(h[x].begin(),h[x].end(),y)-1))+1;
71         if(map[tx][ty]&&dis[x][y]+wal[x][y]<dis[tx][ty]){
72             dis[tx][ty]=dis[x][y]+wal[x][y];
73             p.push(make_pair(-dis[tx][ty],make_pair(tx,ty)));
74         }
75         tx=x,ty=(*upper_bound(h[x].begin(),h[x].end(),y))-1;
76         if(map[tx][ty]&&dis[x][y]+wal[x][y]<dis[tx][ty]){
77             dis[tx][ty]=dis[x][y]+wal[x][y];
78             p.push(make_pair(-dis[tx][ty],make_pair(tx,ty)));
79         }
80         tx=(*(lower_bound(l[y].begin(),l[y].end(),x)-1))+1,ty=y;
81         if(map[tx][ty]&&dis[x][y]+wal[x][y]<dis[tx][ty]){
82             dis[tx][ty]=dis[x][y]+wal[x][y];
83             p.push(make_pair(-dis[tx][ty],make_pair(tx,ty)));
84         }
85         tx=(*upper_bound(l[y].begin(),l[y].end(),x))-1,ty=y;
86         if(map[tx][ty]&&dis[x][y]+wal[x][y]<dis[tx][ty]){
87             dis[tx][ty]=dis[x][y]+wal[x][y];
88             p.push(make_pair(-dis[tx][ty],make_pair(tx,ty)));
89         }
90     }
91     puts("no");
92     return 0;
93 }
View Code

 

 

 


 

 

C. 柱状图

模拟退火

想一个类似的问题:

x轴上的n个点,移动到同一个点,移动单位长度的花费是1,最小总花费是多少?

答案是每个点与中位数所在点的距离和。

不妨将答案所在点左移,因为右侧的点多于左侧的点,会使答案更差。

右移同理。

一眼想到找中位数,发现不太好找。  

发现图的形状是有规律的。

将形状转换一下仍然可以找中位数。

设最高点为i,对每个位置上的数$x_j$,设$s_j=x_j+abs(i-j)$。

我们将一个屋顶上的点转化到了一条直线上,求中位数。

枚举最高点,O(n)可以算出答案,

于是得到一个$O(n^2)$的算法。

通过打表发现  答案 对于 枚举的最高点 非常的单调。

它几乎满足单谷,但也有一些特例。

上模拟退火,结束了。

关于模拟退火:见洛谷P1337,直接进题解区

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstdlib>
 5 #include<cmath>
 6 using namespace std;
 7 const int N=100100;
 8 int n,mn=0x3f3f3f3f,mx=0;
 9 long long ans=0x3f3f3f3f3f3f3f3f;
10 int h[N],s[N];
11 inline int _abs(int x){
12     return x<0?-x:x;
13 }
14 long long check(int pos){
15     for(int j=1;j<=n;++j) s[j]=h[j]+_abs(pos-j);
16     int mid=n+1>>1;
17     nth_element(s+1,s+mid,s+n+1);
18     int val=s[mid]; long long tot=0;
19     if(val<max(pos,n-pos+1)) val=max(pos,n-pos+1);
20     for(int i=1;i<=n;++i) tot+=abs(s[i]-val);
21     return tot;
22 }
23 const double eps=1e-5;
24 const double delta=0.975;
25 const double fj=1;
26 int main(){
27     srand(time(0));
28     scanf("%d",&n);
29     for(int i=1;i<=n;++i) scanf("%d",&h[i]);
30     double T=1000;
31     int now=n+1>>1;
32     long long nowans=check(now);
33     while(T>eps){
34         int tmp=now+(2ll*rand()-RAND_MAX)*T*0.000001;
35         if(T<fj) tmp=max(tmp,1),tmp=min(tmp,n);
36         else tmp=(tmp%n+n-1)%n+1;
37         long long tmpans=check(tmp),D=tmpans-nowans;
38         if(tmpans<nowans||exp(-D/T)*RAND_MAX>rand()) nowans=tmpans,now=tmp;
39         if(tmpans<ans) ans=tmpans;
40         T*=delta;
41     }
42     printf("%lld\n",ans);
43     return 0;
44 }
不一定能AC

正解:

发现对于最高点左侧的点,满足:

$h_i-i=h_j-j$

同理,右侧满足:

$h_i+i=h_j+j$

因为要求的只有差值,将一条折线上的问题转换到一条斜率为0的直线上。

将每个值的信息以排名的形式存在树状数组里。

通过二分查找确定分界线的排名,即可将abs转化为相减。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #define ll long long
 5 #define P pair<ll,int>
 6 #define X first
 7 #define Y second
 8 using namespace std;
 9 const int N=100100;
10 const ll inf=0x3f3f3f3f3f3f3f3f;
11 int n,x[N],rkl[N],rkr[N],mn,mx;
12 P tl[N],tr[N];
13 struct Bit{
14     #define lowbit(x) (x&-x)
15     P s[N];
16     inline P query(register int x,P ans=make_pair(0,0)){
17         for(;x;x^=lowbit(x)) ans.X+=s[x].X,ans.Y+=s[x].Y;
18         return ans;
19     }
20     inline void add(register int x,const P ad){
21         for(;x<=n;x+=lowbit(x)) s[x].X+=ad.X,s[x].Y+=ad.Y;
22     }
23         #undef lowbit
24 }lbit,rbit;
25 inline ll calc(const int pos,int val){
26     if(val<max(pos,n-pos+1)) val=max(pos,n-pos+1);
27     P t1,t2; int rk;
28     rk=lower_bound(tl+1,tl+n+1,make_pair(1ll*val-pos,pos))-tl-1;
29     t1=lbit.query(rk),t2=lbit.query(n);
30     t2.X-=t1.X,t2.Y-=t1.Y;
31     ll ans=1ll*(val-pos)*t1.Y-t1.X+t2.X-1ll*(val-pos)*t2.Y;
32     rk=lower_bound(tr+1,tr+n+1,make_pair(1ll*val+pos,pos))-tr-1;
33     t1=rbit.query(rk),t2=rbit.query(n);
34     t2.X-=t1.X,t2.Y-=t1.Y;
35     return ans+1ll*(val+pos)*t1.Y-t1.X+t2.X-1ll*(val+pos)*t2.Y;
36 }
37 inline ll ask(const int pos,int l=mn,int r=mx,ll ans=inf){
38     while(r-l>2){
39         const int ml=l+(r-l)/3,mr=r-(r-l)/3;
40         if(calc(pos,ml)>calc(pos,mr)) l=ml;
41         else r=mr;
42     }
43     for(register int i=l;i<=r;++i) ans=min(ans,calc(pos,i));
44     return ans;
45 }
46 int main(){
47     scanf("%d",&n);
48     for(register int i=1;i<=n;++i){
49         scanf("%d",&x[i]);
50         mn=min(mn,x[i]);
51         mx=max(mx,x[i]);
52     }
53     for(register int i=1;i<=n;++i) tl[i]=make_pair(x[i]-i,i);
54     sort(tl+1,tl+n+1);
55     for(register int i=1;i<=n;++i) rkl[tl[i].Y]=i;
56     for(register int i=1;i<=n;++i) tr[i]=make_pair(x[i]+i,i);
57     sort(tr+1,tr+n+1);
58     for(register int i=1;i<=n;++i) rkr[tr[i].Y]=i;
59     for(register int i=1;i<=n;++i) rbit.add(rkr[i],make_pair(x[i]+i,1));
60     ll ans=inf;
61     for(register int i=1;i<=n;++i){
62         rbit.add(rkr[i],make_pair(-x[i]-i,-1));
63         lbit.add(rkl[i],make_pair(x[i]-i,1));
64         ans=min(ans,ask(i));
65     }
66     printf("%lld\n",ans);
67     return 0;
68 }
AC

 

posted @ 2019-08-08 06:21  skyh  阅读(280)  评论(4编辑  收藏  举报