• 寿司:函数单调性


    n2还要讲吗?提一下吧,毕竟考场上我也只能想到这个。

    我的思路和下发的题解所讲的思路又不太一样。如果你不想看我的思路而想直接跟着正解走,可以直接从下面的分界线以下开始阅读。

    我的想法也是化环为序列,枚举所有可能的n长的序列。(这个应该没人不知道吧?)

    然后,我的思路是:对于每个序列,枚举j表示把所有的颜色聚集到位置j附近。

    我们假设我们移动的颜色为1,而不动的颜色为0。那么我们现在需要聚集的数字是1。

    最后的效果大概是00000111111111000这样的

    对于一个位置,需要的变换总数是多少?

    101010010(1)001011

    假如我们需要把两边所有的1移动到带括号的1附近,所有数从左往右下标是1~16,这是j是10。

    对于第4个1:(10-1)-8=1步

    对于第3个1:(10-2)-5=3步

    对于第2个1:(10-3)-3=4步

    对于第1个1:(10-4)-1=5步

    对于这些在分界线左边的1,观察上面的式子:被减数形成等差数列,减数是这个数字1所在的位置

    等差数列求和可以O(1)搞定,而位置的求和可以用前缀和维护。

    对于第6个1:13-(10+1)=2步

    对于第7个1:15-(10+2)=3步

    对于第8个1:16-(10+3)=3步

    在分界线右边的这些1其实也同理,一个等差数列一个前缀和。

    而括号里的那个10就是枚举的j,把它们提出来乘以个数很好算,剩下的就是以1为首项1为公比的等差数列了。

    我这样是为了方便求,当然也可以不提出来,反正我的代码实现里是这样的。

    规律大概找到了,把通式写出来就不难了吧。

    特别的,如果你选中的j位置上是一个0,想把那些1移过来,会是这样的效果:

    0000001111(0)110000

    这肯定没完啊,它没连成一块。接下来的操作就很好想了:要么把左边的都右移,要么右边的左移。

    额外的操作步数就是min(左边1的个数,右边1的个数)。

    至于怎么O(1)找到一段上1的个数。。前缀和呗。

    (我的代码中j的含义是整个序列的起点位置,i是数字1聚集的位置,和上面说的不同,注意区分)

    (a[i]是[下标小于等于i的所有数字1的下标前缀和],c[i]是[下标小于等于i的所有数字1的个数前缀和])

     1 #include<cstdio>
     2 #include<iostream>
     3 using namespace std;
     4 int c[2000005],n,x[2000005],t;long long a[2000005],ans;
     5 #define r register
     6 inline void read(){
     7     r char ch=getchar();n=0;
     8     while(ch!='R'&&ch!='B')ch=getchar();
     9     while(ch=='R'||ch=='B')x[++n]=(ch=='R'?1:0),ch=getchar();
    10 }
    11 int main(){
    12     scanf("%d",&t);
    13     while(t--){
    14         read();ans=(long long)1e15;
    15         const r int nn=n,nnn=n*2;
    16         for(r int i=1;i<=nn;++i)x[i+n]=x[i];
    17         for(r int i=1;i<=nnn;++i)c[i]=c[i-1]+x[i],a[i]=a[i-1]+i*x[i];
    18         for(r int i=1;i<=nnn;++i){
    19             const r int mx=min(i,n);
    20             for(r int j=max(i-n+1,1);j<=mx;++j){
    21                 const r int ll=c[i-1]-c[j-1],rr=c[j+n-1]-c[i];
    22                 ans=min(ans,(ll-rr)*i+a[j+n-1]+a[j-1]-a[i]-a[i-1]-(ll*(ll+1ll)+rr*(rr+1ll))/2+(x[i]?0:min(ll,rr)));
    23                 //printf("%d %d %d %d %lld
    ",i,j,ll,rr,(ll-rr)*i+a[j+n-1]+a[j-1]-a[i]-a[i-1]-(ll*(ll+1ll)+rr*(rr+1ll))/2+(x[i]?0:min(ll,rr)));
    24             }
    25         }
    26         printf("%lld
    ",ans);
    27     }
    28 }
    给出考场上的40分代码

    接下来开始讲正解。。的n2

    正解所说的思路是找到分界线,分界线左边的直接扔到左边界,右边的往右扔。

    基础的式子的话大致思路和我上面的那个差不多,也是一个[区间的位置和]和[一个等差数列]做差。

    const r int ll=c[jj-1]-c[i-1],rr=c[i+n-1]-c[jj-1];//分别表示分界线左右边各有多少个1
    const r long long rans=-ll*(ll-1ll)/2-rr*(rr-1ll)/2+a[jj-1]*2-a[i-1]-ll*i+rr*(i+n-1)-a[i+n-1];
    //a和c数组含义同上。jj是分界线位置,i是序列起始位置

    还是比较好理解的。不细讲了。

    接下来开始对复杂度进行优化。

    性质1.在同一个序列中,关于分界线位置j的移动次数的函数是一个单峰函数。(其实是谷,U型)

    证明的话可能不太严谨,skyh给我yy半天也没说明白。所以先不瞎说了。

    转一个大神yxs的证明

    暂且。。。凭感觉?反正这不是重要结论。

    然后我们就可以愉快的三分了。三分的过程还是比较简单的吧。

    但毕竟之前没见过,所以还是大概的说一说。

    如果我们现在能够确定一个函数的最值(以本题最小值为例)就在某个特定区间(l,r)中

    我们把这个区间分成等宽的3段,(l,m1),(m1,m2),(m2,r)

    求出对于每一个点的函数值。

    若f(m1)>f(m2),那么最小值不可能出现在m1的左端,否则从最小值到m1是增加的,从m1到m2是减小的,不符合单峰函数性质。

    同理若f(m1)<f(m2)那么就能排除m2右边的区间。

    这样每次可以缩小区间的1/3,复杂度是log级别。

    那么对于每一个枚举的i所限定的特定区间,其操作次数为单峰函数,通过三分找到它的值,n log1.5 n。

    据说mikufun/yxm有二分的做法(nlog2能AC),但据说是用了下面的性质,故不再赘述。

    所以接下来就是O(n)算法咯!

    性质2.决策具有单调性,即随着i的增大,j一定不会减小。

    有了这条性质我们就可以愉快的做题了,快,去切了它!

    咳,我们需要客观严谨的证明好伐?(虽说我证明也不是很严谨)

    考虑我们决策的方法:不断枚举i,j是全局变量全程只增不减,也就是i++时j从上一个i所枚举的最后一个j开始。

    只要j++能够使操作次数变小或不变,那么就使j++,否则break不再继续枚举j。

    我们现在要证明它的正确性。

    一共两种情况,一个是i++使一个0从区间最左边移动到了最右边,另一种移动的是1

    先讨论第一种,考虑n=8,i=3->4,j=8,[]内是目前考虑的区间,|是分界线(位置任取),x可以是0或1任意

    原区间:xxx[0xxxx|xxxx]0xxx

    新区间:xxx0[xxxx|xxxx0]xxx

    因为按照我们的决策方法,分界线向右移动当且仅当这么做会使答案更优或不变

    那么反过来,只要我们严格按照这样的决策方式操作,分界线向左移一定不会使答案更优(可不变)

    这样的话,我们把一个0从左端移动到右端,要动的所有的1并没有变化,故新区间对于这个分界线的答案和原区间一样。

    故上述结论仍然成立,分界线左移不会使答案更优。

    再考虑把1从左端移动到右端。

    原区间:xxx[1xxxx|xxxx]1xxx

    新区间:xxx1[xxxx|xxxx1]xxx

    原区间里,第一个1不动还在位置4,对于分界线左边的其他所有的1分别要移动到位置5,6,7...右边的分别要移动到位置12,11,10...

    在新区间里,区间的最左端位置是5了,除了位置4上的那个1以外,分界线左端的1的个数与位置均未发生变化。

    现在,它们需要移动到的位置还是5,6,7...。

    那个1跑到了新区间的最右端,没有关系,除了它以外,分界线右边的数字1的个数和位置也不变。

    虽然区间最右端点扩展到了13,但是这个位置已经被那个1占据了,所以其他的分界线右边的数字1现在需要移动到的位置是12,11,10...

    所以,可以发现,虽然有一个1的位置发生了变化,但是对于任意一条分界线,除了新的这个1以外,每一个原区间里的数字1它们的个数,位置,目标位置均没有发生变化,而在新旧区间当中,那个变化了的数字1一直都在端点上无需额外操作,故新旧区间对于同一个分界线,它们的答案值相同。

    所以区间右移之后仍然满足“分界线左移不会使答案更优”的性质。

    这样无论区间是如何移动的,分界线始终不会左移,证毕。

    那么我们只要像证明里所说的那样进行决策,就能够保证最优答案一定会被枚举到,正确性有保证。

    所以就是[在能够右移分界线j而不使答案变差时右移分界线,否则就右移区间i]。

    完事!

    对了。看数据范围了么?

    咳,我要提醒什么,你应该知道了吧。。

    如果你看到你的错解比别人内存小一半。。咳咳。。。

     1 #include<cstdio>
     2 #define int long long
     3 int c[2000005],n,x[2000005],t;long long a[2000005],ans;
     4 #define r register
     5 inline long long min(const register long long a,const register long long b){return a<b?a:b;}
     6 inline void read(){
     7     r char ch=getchar();n=0;
     8     while(ch!='R'&&ch!='B')ch=getchar();
     9     while(ch=='R'||ch=='B')x[++n]=(ch=='R'?1:0),ch=getchar();
    10 }
    11 signed main(){
    12     scanf("%d",&t);
    13     while(t--){
    14         read();ans=(long long)1e15;
    15         for(r int i=1;i<=n;++i)x[i+n]=x[i];
    16         for(r int i=1;i<=n*2;++i)c[i]=c[i-1]+x[i],a[i]=a[i-1]+i*x[i];
    17         r int j=1;
    18         for(r int i=1;i<=n;++i){
    19             r long long bans=(long long)1e15;
    20             for(r int jj=j;jj<=i+n+1;++jj){
    21                 const r int ll=c[jj-1]-c[i-1],rr=c[i+n-1]-c[jj-1];
    22                 const r long long rans=-ll*(ll-1ll)/2-rr*(rr-1ll)/2+a[jj-1]*2-a[i-1]-ll*i+rr*(i+n-1)-a[i+n-1];
    23                 if(rans<=bans)bans=rans,j=jj;//,printf("%d %d %d %d %lld
    ",i,j,ll,rr,rans);
    24                 else break;
    25                 ans=min(ans,bans);
    26             }
    27         }
    28         printf("%lld
    ",ans);
    29     }
    30 }
    981B
  • 相关阅读:
    leetCode算法题(一)
    node学习笔记1(http模块)
    mySql常用命令总结
    django学习遇到的问题解决方式
    WebSSH2 界面ssh(转载)
    垃圾回收策略学习总结
    5种数组扁平化方法
    冒泡、插入、选择、归并、快速排序
    手写ES6数组方法
    HTTP options预请求
  • 原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/11246615.html
Copyright © 2020-2023  润新知