AGC Language_agc072b

问题是一步一步解决的,将简单的工具组合起来就可以解决似乎没有头绪的问题。

先让n=2n,表示序列的总长度。

先考虑k=1的情况,对于这种"括号序列"问题,如果遇到A让y加1,遇到C让y减1,可以在坐标轴上画出图像,对某个x,y>=0,代表长度为x的前缀满足A的数量>=C的数量,y<0则不满足,所以,进行变换之后的图像,要满足所有点(对所有x)y都大于等于0。

这个图像一定是从y=0开始,到y=0结束,因为一开始没有A和C,A和C数量相等,整个序列中A和C数量相同,所以最后x=n时,y=0。

ACCCAACAAAACCC为例,图像如下:

接着如果没有第一步的反转操作,只有交换相邻两个字母,将所有的h(也就是之前说的y)变为>=0,需要多少次呢?

我们分类讨论看看: 交换AA或者CC是没有意义的。 将AC变成CA,第一个位置的值减少2,其他位置的值都没有变。要通过交换增加某个x对应的h,这种操作反而减少了h,是不应该采取的。 将CA变成AC,第一个位置的值增加2,其他位置的值不变,因此,一次CA的交换可以让C这个位置的值增加2。 按照以下方法可以保证每次总能找到CA对交换:找到最靠左的负位置,这个位置的高度一定是-1,因此这个位置上的字母一定是C,这个位置右边一定有A,因为最后到第n个位置高度是0,后面必须有A来让高度增加。将在这个位置 右边离其最近的A交换到当前位置,要么是C A,要么是C....CA,这个过程中每次CA交换都能起到效果让负高度增加2的效果。因此,每次都能找到CA对,且每次交换能起到高度+2的效果,

如果每个高度是负数的位置:p1,p2...pm。

那么最少的交换次数是:ceil(-h_p1/2)+ceil(-h_p2/2)+...+ceil(-h_pm/2)。

接下来看看反转操作对x-h图像有什么作用:

可见,反转的效果是选定一组(l,r) (l<r且l,r间的距离大于1,实际上,即使等于1也不影响),将[l,r]间的图像旋转180度。

反转之后,位置i的新高度是h_p-(h_i-h_q),因为旋转前,从q开始从右向左看高度相对于q是正数的点,从p开始从左向右看,相对于p的高度是同等大小的负数。

h_i_new=h_p+h_q-h_i,因此原高度如果大于(h_p+h_q),则旋转后高度为负数。得知旋转后的新高度,可以用之前计算最少交换次数的方法,计算[l,r]间的,再计算[l,r]外左右两边的,来获得最少交换次数。

如果l和r从1到n遍历,复杂度是n^2,l和r的选取有哪些特征吗:

将图像分为正的"块"和负的"块",如图,用红色描出的为正,蓝色为负:

1.如果l选到了高度小于0的位置,将l向左移动到第一个高度为0的位置,则[l,r]间的负部分减少了,[l,r]外的负部分也减少,所以l应该选择高度>=0的位置,同样,r也是。

2.因此,l和r应该在"正"块中选点,且在一个正块中,l应该选这个块中高度最大的点,这是因为,[l,r]外的负点不会变多,h_l增大,[l,r]间高度大于h_l+h_r的点会减少,结果不会更劣。同样,r应该选一个正块中高度最大的点。

3.已知1,2,我们知道l选择的是每个正块中高度最高的点,让这些点是p1,p2,p3....。

我们只挑选最大高度被更新的点,理由:如果一个p_i的高度相较之前的最大高度p_j小或相等,将l从p_i改成p_j,[l,r]间的负部分不会增加,[l,r]外的负部分只有可能减少,结果不会更劣。下图中的l候选对象是p1,p2,p5。

同理,r是从右向左最大高度被更新的点。

有了以上的特殊性质,(l,r)对还有多少个?让l和r的候选对象尽量多的图像如下:

如果l和r都有x个候选对象,那么 [2x(这是每个候选对象带的-1)+2(1+2+...+x)]*2=n,计算得到x在根号n级别。这样 (l,r)对有n个,复杂度ok。

获得(l,r)对以后,需要以O(1)时间计算出第二步的交换需要多少次,用c_l表示l左边ceil(负高度/2)的和,c_r表示r右边ceil(负高度/2)的和,但[l,r]间的部分怎么处理呢?这部分需要的交换次数是:若h=h_l+h_r,令c_h表示整个序列ceil((大于等于h的高度-h)/2)的和,则需要的c_h是需要的次数。为什么是整个序列?因为l左边和r右边其实没有高度大于等于h的点。我们预处理出c_l,c_r,c_h就可以了。

在实现过程中,可以这样计算c_h:

用奇偶分类的方式:c_h_even是h,h+2,h+4,h+6...这些高度需要的交换次数,c_h_odd是h+1,h+3...这些高度需要的交换次数。

让cnt[i]代表高度为i的点的数量,pref[i]是cnt[i]+cnt[i+2]+cnt[i+4]+...,则c_h_even[i]=c_h_even[i+2]+pref[i+2],下图中ch代表c_h_even,计算原理如下图:

得到c_h_even后,c_h_odd[i]=c_h_even[i+1]+pref[i+1],原理如下:

将k拓展到>1:因为l,r的选取特点,l'选的是第一个序列的l,r'选的是最后一个(也就是第k个)序列的r,那么l和r中间需要的交换次数是k*c_h,左右两边需要的还是c_l和c_r。

之后对每个(l,r)对,h=h_l+h_r,则所需交换次数是:c_l+c_r+k* c_h,如果只有一个k,遍历所有(l,r)取最大即可,但现在有多个k,则问题变为y=k* x+b,有n组(x,b)和K组k,可以使用带斜率优化的动态规划来找到y的最小值。

cpp 复制代码
/*

*/

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

#define fio ios::sync_with_stdio(0);cin.tie(0);
#define fin freopen("D:/in.txt","r",stdin);
#define fout freopen("D:/out.txt","w",stdout);

const ll maxn=1e6*2+5;
ll n,k,lcnt,rcnt,pcnt;
string s;
ll h[maxn],l[maxn],r[maxn],ch[maxn],cl[maxn],cr[maxn],ch_even[maxn],ch_odd[maxn],pref[maxn],cnt[maxn],ans[maxn],q[maxn];

struct Point{
    ll x,y;
    bool operator < (const Point &rhs) const {
        if(x==rhs.x) return y<rhs.y;
        return x<rhs.x;
    }
    bool operator == (const Point &rhs) const {
        return (x==rhs.x && y==rhs.y);
    }
}point[maxn];

double slope(ll i,ll j) {
    ll x_1=point[i].x,y_1=point[i].y;
    ll x_2=point[j].x,y_2=point[j].y;
    if(x_1==x_2) return 1e12;
    else return (double)(y_2-y_1)/(double)(x_2-x_1);
}

ll comp_slope(ll i1,ll j1,ll i2,ll j2) {
    __int128 dx1=point[j1].x-point[i1].x,dy1=point[j1].y-point[i1].y;
    __int128 dx2=point[j2].x-point[i2].x,dy2=point[j2].y-point[i2].y;
    if(dx1==0 && dx2==0) return 1;
    if(dx1==0) return 2;
    if(dx2==0) return 0;

    if(dy1*dx2==dy2*dx1) return 1;
    if(dy1*dx2<dy2*dx1) return 0;
    if(dy1*dx2>dy2*dx1) return 2;

    return 0;
}

ll comp_slope2(ll i,ll j,ll tocomp) {
    __int128 dx=point[j].x-point[i].x,dy=point[j].y-point[i].y;
    if(dx==0) return 2;

    if(dy==(__int128)tocomp*dx) return 1;
    if(dy<(__int128)tocomp*dx) return 0;
    if(dy>(__int128)tocomp*dx) return 2;
    return 0;
}

int main()
{
    fio;

    cin>>n>>k>>s;
    n=n*2;

    for(ll i=0;i<s.size();i++) {
        if(s[i]=='A') h[i+1]=h[i]+1;
        else h[i+1]=h[i]-1;
        if(h[i+1]>=0) cnt[h[i+1]]++;
    }

    //获得l[]和r[]
    //l[i]:第i个l点的横坐标(1-indexed)
    ll lst=-1;
    ll curmax=-1,candi=-1;
    for(ll i=0;i<=n;i++) {
        if(h[i]>curmax) {
            curmax=h[i];
            candi=i;
        }
        if(h[i]==0 && i!=0) {   //连通块结束
            if(curmax>=0 && curmax>lst) {
                l[++lcnt]=candi;
                lst=curmax;
            }
        }
    }
    lst=-1;
    curmax=-1,candi=-1;
    for(ll i=n;i>=0;i--) {
        if(h[i]>curmax) {
            curmax=h[i];
            candi=i;
        }
        if(h[i]==0 && i!=n) {   //连通块结束
            if(curmax>=0 && curmax>lst) {
                r[++rcnt]=candi;
                lst=curmax;
            }
        }
    }

    /*//debug:查看选出的l,r
    printf("l:\n");
    for(ll i=1;i<=lcnt;i++) cout<<l[i]<<" ";
    puts("");
    printf("r:\n");
    for(ll i=1;i<=rcnt;i++) cout<<r[i]<<" ";
    puts("");
    */

    //计算cl,cr
    ll dep=0,nxtid=1;
    for(ll i=0;i<=n;i++) {
        if(h[i]<0) dep+=(-h[i]+1)/2;
        if(i==l[nxtid]) {
            cl[nxtid]=dep;
            nxtid++;
        }
        if(nxtid>lcnt) break;
    }
    dep=0,nxtid=1;
    for(ll i=n;i>=0;i--) {
        if(h[i]<0) dep+=(-h[i]+1)/2;
        if(i==r[nxtid]) {
            cr[nxtid]=dep;
            nxtid++;
        }
        if(nxtid>rcnt) break;
    }

    //计算ch
    ll maxh=*max_element(h,h+1+n);
    for(ll i=maxh;i>=0;i--) {
        pref[i]=cnt[i]+pref[i+2];
        ch_even[i]=ch_even[i+2]+pref[i+2];
    }
    for(ll i=maxh;i>=0;i--) {
        ch_odd[i]=pref[i+1]+ch_even[i+1];
        ch[i]=ch_even[i]+ch_odd[i];
    }

    pcnt=0;
    for(ll i=1;i<=lcnt;i++) {
        for(ll j=1;j<=rcnt;j++) {
            Point tmp;
            tmp.x=-ch[ h[l[i]]+h[r[j]] ];
            tmp.y=cl[i]+cr[j];
            point[++pcnt]=tmp;
        }
    }

    stable_sort(point+1,point+1+pcnt);
    pcnt=unique(point+1,point+1+pcnt)-(point+1);



    ll head=1,tail=0;
    for(ll i=1;i<=pcnt;i++) {
        //while(tail>head && slope(q[tail],i)<=slope(q[tail-1],q[tail])) tail--;
        while(tail>head && comp_slope(q[tail],i,q[tail-1],q[tail])<2) tail--;
        q[++tail]=i;
    }
/*
    //debug
    puts("开始输出点");
    for(ll i=1;i<=tail;i++) {
        ll j=q[i];
        cout<<point[j].x<<" "<<point[j].y<<"\n";
    }
    puts("输出点结束");
    puts("开始输出每段斜率");
    for(ll i=2;i<=tail;i++) {
        cout<<slope(q[i],q[i-1])<<"\n";
    }
    puts("输出斜率结束");
*/
    for(ll i=1;i<=k;i++) {
        ll curk=i;
        while(head<tail && comp_slope2(q[head],q[head+1],curk)<=1) head++;
        //while(head<tail && slope(q[head],q[head+1])<curk) head++;
        //debug
        //printf("k=%lld选点%lld\n",curk,head);

        ans[i]=point[q[head]].y-point[q[head]].x*curk;

    }

    for(ll i=1;i<=k;i++) {
        cout<<ans[i]<<"\n";
    }

    return 0;
}
相关推荐
Vesan,18 分钟前
无人机开发分享——基于行为树的无人机集群机载自主决策算法框架搭建及开发
c++·算法·决策树·无人机
爱coding的橙子1 小时前
每日算法刷题Day58:8.7:leetcode 单调栈5道题,用时2h
算法·leetcode·职场和发展
董莉影1 小时前
学习嵌入式第二十二天
数据结构·学习·算法·链表
Lukeding2 小时前
Magnetic-UI源码解析
算法
chirrupy_hamal2 小时前
排序算法详解
算法
csdn_aspnet2 小时前
四边形面积
算法·四边形
爱coding的橙子2 小时前
每日算法刷题Day57:8.6:leetcode 单调栈6道题,用时2h
算法·leetcode·职场和发展
GawynKing2 小时前
图论(1):图数据结构
数据结构·算法·图论·图形理论
乌萨奇也要立志学C++3 小时前
【LeetCode】set和map相关算法题 前K个高频单词、随机链表的复制、两个数组的交集、环形链表
算法·leetcode·链表