2019.8.7 NOIP模拟测试14 反思总结

先扔代码


 

调完自闭网络流了,新一轮考试前看看能不能赶完……

考得极其爆炸,心态也极其爆炸,真的是认识到自己能力上的不足

思维跑偏,代码能力差

就这样吧,再努力努力,不行就AFO

 

T1旋转子段:

因为我和正解的绝缘性,我上来先选择想暴力,搞了搞把暴力优化到n2,行了,就交了

大约是想正解的时候被奇怪的问题hack掉没有解决,于是被踢出了正解门外

 

这边正解用的是O(n)的做法。一个可以成为答案的旋转子段,如果它的两个端点旋转以后没有一个成为固定点,那么缩短这个子段的长度直到端点出现旋转以后的固定点,和一开始的子段是等价的。那么显然可以发现我们的答案子段只可能是[min(1,a1),max(1,a1)],[min(2,a2),max(2,a2)]...一共n个区间,把问题转化成检查这n个区间的答案。

从左到右扫一遍,把当前位置的值和下标都扔进桶里。当桶中某个地方已经被扔进第二次的时候,证明出现了一个右端点,然后进行check。记下每个a[i]对应的位置p[i],如果桶里满了的是当前位置的下标i,就利用p[i]计算旋转中心。如果满了的是a[i],a[i]的值就是它对应的左端点下标,一样可以直接计算。因为旋转中心分数值位置以及中间的空隙位置两种,所以这里算旋转中心的时候直接用左右端点加起来,不/2。

算出左端点以及旋转中心以后,只需要知道这个旋转中心在这个范围内已经贡献了几个答案,以及这段区间内原来有多少固定点,就能算出现在得出的答案是多少。因为对于同一个旋转中心,它之前能累计的答案区间右端点一定在当前右端点之前,范围被包含在当前范围之内,所以可以直接把之前扫到的完整区间都扔进旋转中心的桶里【仍然是左端点+右端点来避免空隙的误差】。而这段区间内原来有多少固定点,读入序列的时候就可以用前缀和算出。那么当前的答案就是旋转中心的桶里累计的答案+全局原有固定点减去这一段原有固定点+1。最后再把旋转中心的答案桶+1。

这样扫过去,就可以O(n)地更新出最优答案。

#include<iostream>
#include<cstdio>
using namespace std;
int sum,ans,c[2000010];
int n,a[2000010],b[2000010],p[2000010],v[2000010];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        b[i]=b[i-1]+(a[i]==i?1:0);
        p[a[i]]=i;
    }
    for(int i=1;i<=n;i++){
        sum=0;
        v[i]++;
        v[a[i]]++;
        if(v[i]==2&&a[i]!=i){
            int pos=i+p[i];
            sum=sum+c[pos]+b[n]-(b[i]-b[p[i]-1])+1;
            c[pos]++;
            ans=max(ans,sum);
        }
        sum=0;
        if(v[a[i]]==2&&a[i]!=i){
            int pos=i+a[i];
            sum=sum+c[pos]+b[n]-(b[i]-b[a[i]-1])+1;
            c[pos]++;
            ans=max(ans,sum);
        }
        if(a[i]==i){
            ans=max(ans,1);
            c[i+a[i]]++;
        }
    }
    printf("%d",max(ans,b[n]));
    return 0;
}
View Code

 

 

T2走格子:

考试的时候想岔了,不是说在一个位置就可以直接跳到四个方向最近的墙,而是可以向四个方向都只用走离最近的一个方向的墙+1的步数。

更正确的思路是可以向四个方向走离最近的一堵墙+1的距离【墙可以不在四个方向而是斜着在周围】,但可以证明两种方法是等价的,步数相同。

那么直接用第一种方法,对于每个点找出四个方向离它最近的墙的距离,向四个方向直着最远能走到的地方都连这个距离+1的边权。并且向周围的四个格子也都连1的边权【一步一步走】。

然后跑一个最短路就OK了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;
int n,m,a[510][510],sx,sy,ex,ey;
int h[5]={0,0,-1,0,1};
int l[5]={0,-1,0,1,0};
char c[510][510];
int ver[3000010],head[300010],Next[3000010],edge[3000010],tot,vis[300010],dis[300010];
queue<int>q;
void add(int x,int y,int z){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    edge[tot]=z;
}
void SPFA(){
    memset(dis,0x3f3f3f3f,sizeof(dis));
    int u=(sx-1)*m+sy;
    dis[u]=0;
    q.push(u);
    while(q.size()){
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=Next[i]){
            int v=ver[i],z=edge[i];
            if(dis[v]>dis[u]+z){
                dis[v]=dis[u]+z;
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s",c[i]+1);
        for(int j=1;j<=m;j++){
            if(c[i][j]=='C')sx=i,sy=j,a[i][j]=1;
            else if(c[i][j]=='F')ex=i,ey=j,a[i][j]=1;
            else if(c[i][j]=='.')a[i][j]=1;
        }
    }
    int xx[5],yy[5],val;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(!a[i][j])continue;
            val=0x3f3f3f3f;
            for(int fx=1;fx<=4;fx++){
                int x=i,y=j;
                while(a[x+h[fx]][y+l[fx]]){
                    x+=h[fx],y+=l[fx];
                }
                xx[fx]=x,yy[fx]=y;
                val=min(val,abs(x-i)+abs(y-j));
            }
            for(int fx=1;fx<=4;fx++){
                if(xx[fx]!=i||yy[fx]!=j){
                    add((i-1)*m+j,(xx[fx]-1)*m+yy[fx],val+1);
//                    add((xx[fx]-1)*m+yy[fx],(i-1)*m+j,val+1);
                }
                if(a[i+h[fx]][j+l[fx]]){
                    add((i-1)*m+j,(i+h[fx]-1)*m+j+l[fx],1);
//                    add((i+h[fx]-1)*m+j+l[fx],(i-1)*m+j,1);
                }
            }
        }
    }
    SPFA();
    if(dis[(ex-1)*m+ey]<0x3f3f3f3f)printf("%d",dis[(ex-1)*m+ey]);
    else printf("no");
    return 0;
}
View Code

唔,千万不能连双向边,尤其是传送门那个地方…我简直就是个睿智。

 

 

T3柱状图:

考试的时候一看题目公式:决策单调性?

我大约没有救了。

正解是把枚举n,枚举高度h,再枚举n验证的暴力O(nhn)优化。高度h的答案是一个单峰函数,中间可能是平顶但是不影响这个平顶代表的答案就是最优的,所以可以三分。验证的n可以推一个式子,然后用平衡树或者树状数组优化到logn。

但是我不会写三分【虽然挺快就学会了】,也不想打后面树状数组一堆麻烦操作,或者稍微不麻烦点两棵平衡树。所以我跟机房里大部分人一样打了个模拟退火。

check函数是O(n)的,所以模拟退火不能跑太多次。check的O(n)做法就是,把所有数减去一个等差数列,把问题转化成填平一段序列。然后填平的最优高度一定是这段序列的中位数。如果这个中位数小于等于0就不满足题意,让它等于1。

然后因为long long等正确性问题死了好几次,调参倒是挺快就过了【可能是因为前人经验】。

模拟退火的板子还得再背几遍…

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
int n;
long long a[200010],b[200010],vis[200010];
double d[2]={-1.0,1.0};
double T=100000,eps=0.1,delta=0.986;
long long C(int x){
    long long maxx=max(x-1,n-x);
    long long sum=0;
    for(int i=1;i<=n;i++){
        b[i]=a[i]-(maxx-abs(x-i));
    }
    int mid=(1+n)/2;
    nth_element(b+1,b+mid,b+n+1);
    long long y=b[mid];
    if(y<=0)y=1;
    for(int i=1;i<=n;i++){
        sum=sum+abs(b[i]-y);
    }
    return sum;
}
void solve(){
    long long ans=C(1);
    int x=1;
    vis[1]=1;
    while(T>eps){
        int nowx=-1;
        nowx=((int)(x+d[rand()%2]*T*rand()/RAND_MAX)%n+n)%n+1;
        if(vis[nowx]){
            T*=delta;
            continue;
        }
        long long sum=C(nowx);
        long long cha=ans-sum;
        if(cha>=0){
            ans=sum;
            x=nowx;
        }
        else if(exp(cha/T)>(double)rand()/(double)RAND_MAX){
            x=nowx;
        }
        vis[nowx]=1;
        T*=delta;
    }
    printf("%lld",ans);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    solve();
    return 0;
}
View Code

 

 

好居然写完了,滚了滚了。

洛谷今日运势:忌考试,忌刷题。

轻轻扣下一个问号

 

posted @ 2019-08-08 19:22  Chloris_Black  阅读(137)  评论(0编辑  收藏  举报