• 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

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

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

    轻轻扣下一个问号

  • 相关阅读:
    IntelliJ IDEA设置JVM运行参数
    IntelliJ IDEA 乱码解决方案 (项目代码、控制台等)
    188.索引与视图
    187.数据库操作
    186.元素
    185.流程设计
    184.数据操纵语言DML
    改变linux shell前景色和背景色
    Centos文本方式安装情况下lvm分区的创建
    深入理解计算机系统第二版习题解答CSAPP 2.20
  • 原文地址:https://www.cnblogs.com/chloris/p/11323225.html
Copyright © 2020-2023  润新知