模拟14 题解

T1[A. 旋转子段]

20%算法

枚举旋转起始点,再枚举旋转长度,得到旋转区间,再扫一边统计答案,

60%算法

考虑节省统计答案的复杂度,预处理出来每个节点的初始固定点个数

枚举旋转的中心,(可以是原来的点或某两个点中间的轴),再从中心点向两边扩展区间,出现了新的固定点时cnt++

对于区间外的前缀和O(1)查询

 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 }
View Code

100%算法

假设[l,r]为最优解区间,则必定有l==a[r]或a[l]==r(否则一定可以将区间扩展或缩小)

 那么将每组点对(i,a[i])按照l为较小值,r为较大值,放入下标为i+a[i]的vector中

此时的每个vector里存的点都是有相同的轴的点对,将vector中的点按照大区间包含小区间的方式排序,从小区间开始向外扩展,cnt++,记录枚举到当前区间的里面固定点个数

区间外仍然使用前缀和

 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 }
View Code

 

T2[B. 走格子]

把网格图建边成图对于每个非墙节点,连向与之相邻的四个格子(特判是不是墙),再连向四个方向能到的最近的墙的前一个格子,

 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 }
View Code

 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)$

 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 }
View Code

理论60%算法2

发现一个性质:变化的总值sum  关于 屋顶高度h 的图像为单谷函数

可以这么想:如果屋顶高度特别高,那么每一个i都是增大的,sum就会随h单调递增,反之递减,当h适中时,代价会最小

用三分搞一下

 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 }
mikufun

100%算法:「乱搞」「模拟退火」

打表发现,sum关于屋顶位置是个多谷函数(就是没有任何规律)

模拟退火可A

 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 }
View Code

推了一下午的式子,然后发现,最一开始的定义就有些问题,所以努力无果QAQ

自己瞎颓式子的时候一定要严谨证明

有一些东西不必要用个式子表示,用理说明(感性理解)就好

打表打表找规律

转载于:https://www.cnblogs.com/casun547/p/11319133.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值