1. 程式人生 > >Atcoder&CodeForces雜題11.7

Atcoder&CodeForces雜題11.7

Preface

又自己開了場CF/Atcoder雜題,比昨天的稍難,題目也更有趣了

昨晚爐石檢驗血統果然是非洲人...

希望這是給NOIP2018續點rp吧

A.CF1068C-Colored Rooks

現在還沒理解題意...

B. CF1070K-VideoPosts

一道模擬,沒什麼好說的.

不過一開始還是WA了...一個智障的坑,感覺做題還是不細心

const int maxn=100005;
const int inf=0x7fffffff;
int a[maxn],ave,n,k,cnt=0;
int ans[maxn],tot=0;
ll sum=0;
int main(){
    read(n),read(k);
    for(ri i=1;i<=n;i++){
        read(a[i]);
        sum+=a[i];
    }
    if(sum%k!=0){
        puts("No");
        return 0;
    }
    ave=sum/k;
    sum=0;
    for(ri i=1;i<=n;i++){
        sum+=a[i],cnt++;
        if(sum==ave){
            ans[++tot]=cnt;//printf("%d ",cnt);
            sum=cnt=0;
        }
        else if(sum>ave){
            puts("No");
            return 0;
        }
    }
    puts("Yes");
    for(ri i=1;i<=tot;i++)printf("%d ",ans[i]);
    puts("");
    return 0;
}

C. CF1036D-VasyaAndArrays

一看就是一道貪心題,很多人都會猜測就是兩個指標不斷拓展,如果兩個數相等就不加.

想了想發現似乎是正確的,但是不會嚴格證明,交了發居然A了

const int maxn=300005;
const int inf=0x7fffffff;
int n,m;
int a[maxn],b[maxn];
ll sum1=0,sum2=0;
int main(){
    read(n);
    for(ri i=1;i<=n;i++)read(a[i]),sum1+=a[i];
    read(m);
    for(ri i=1;i<=m;i++)read(b[i]),sum2+=b[i];
    if(sum1!=sum2){
        puts("-1");
        return 0;
    }
    int len=0,pos1=1,pos2=1;
    sum1=a[1],sum2=b[1];
    while(1){
        while(sum1!=sum2){
            if(sum1>sum2)sum2+=b[++pos2];
            else sum1+=a[++pos1];
        }
        len++;
        if(pos1==n&&pos2==m)break;
        sum1=a[++pos1],sum2=b[++pos2];
    }
    printf("%d\n",len);
    return 0;
}

D. CF1038D-Slime

非常有意思的題,一開始想區間DP,但是發現好像是有後效性的,交了發果然WA了

然後只能往貪心這方面想

偶然發現樣例非常有意思,一個全正數, 一個全負數+0.

易知如果全是正數的話就可以貪心,你用最小的史萊姆吃掉出最大值以外的所有數,然後最大值吃掉這個數肯定是最優的.這樣子答案就是sum-mi-mi

如果是全負數加上一個0,我們就是用0吃掉所有負數顯然是最優的

那如果既有非負數又有負數,我們可以用負數減去除最大值以外所有非負整數,然後最大值吃掉所有負數就是最優的,這樣子就是所有元素之和

但是如果全都是負數按照全都是正數的思路就是sum-mx-mx

const int maxn=500005;
const int inf=1e9;
int n,a[maxn],mi=inf,mx=-inf;
int main(){
    bool flag=0,flag_2=0;
    ll sum=0;
    read(n);
    if(n==1){
        read(a[1]);
        printf("%d\n",a[1]);
        return 0;
    }
    for(ri i=1;i<=n;i++){
        read(a[i]);
        mi=min(a[i],mi);
        mx=max(a[i],mx);
        sum+=abs(a[i]);
        if(a[i]<0)flag=1;
        if(a[i]>=0)flag_2=1;
    }
    if(flag){
        if(flag_2)printf("%lld\n",sum);
        else printf("%lld\n",sum+mx*2);
    }
    else {
        printf("%lld\n",sum-mi*2);
    }
    return 0;
}

E. Atcoder4167-Equal Cut

一道ARC的B題就不會做了,太菜了...

感覺solution很妙啊,列舉中間的斷點.這時候分成了L,R兩部分,顯然我們還要把L,R分成L1,L2和R1,R2兩部分

這時候有一個結論就是讓abs(L1-L2)和abs(R1-R2)儘量小一定是最優方案,這個還是比較顯然的

這時候可以發現我們在列舉中間斷點的時候分割L1,L2的斷點和分割R1,R2的斷點都是單調的

這裡的斷點是指abs(L1-L2)和abs(R1-R2)儘量小的斷點

於是我們就可以\(O(N)\)掃一遍得出答案了

同時發現一個很坑的地方:

std::abs()而不是abs(),因為差點沒查出錯來

const int maxn=1000005;
const int inf=0x7fffff;
int n;
ll a[maxn];
ll sum[maxn],mi=1e18,mx=-1e18,ans=1e18,tmp;
int main(){
    int x,y;
    read(n);
    sum[0]=0;
    for(ri i=1;i<=n;i++)read(a[i]),sum[i]=sum[i-1]+a[i];
    int l=1,r=3;
    for(ri k=2;k<=n-2;k++){
        while((abs((sum[k]-sum[l+1])-(sum[l+1]))<abs(sum[k]-sum[l]-sum[l]))&&l+1<k)l++;
        while((abs((sum[n]-sum[r+1])-(sum[r+1]-sum[k]))<abs((sum[n]-sum[r])-(sum[r]-sum[k])))&&r+1<n)r++;
        mi=1e18,mx=-1e18;
        tmp=sum[k]-sum[l];
        mi=min(mi,tmp),mx=max(mx,tmp);
        tmp=sum[l];
        mi=min(mi,tmp),mx=max(mx,tmp);
        tmp=sum[n]-sum[r];
        mi=min(mi,tmp),mx=max(mx,tmp);
        tmp=sum[r]-sum[k];
        mi=min(mi,tmp),mx=max(mx,tmp);
        ans=min(ans,mx-mi);
    }
    printf("%lld\n",ans);
    return 0;
}

F. Atcoder4351-Median Of Medians

這題考試的時候也不會做,考試後發現正解簡直太妙了

首先我們需要知道一個性質:如果\(x\)是一個長度為\(N\)的序列的中位數,那麼小於等於x的數的個數至少有\(N/2+1\)

於是我們不妨二分一下最後的中位數是哪個,然後我們只需要知道有多少個區間的中位數是小於等於我們二分的這個數就可以了

這裡就需要一個高階操作:我們將原序列中小於等於這個數的數置為1,否則置為-1;這樣操作之後可以發現如果一個區間的中位數小於等於x,那麼這個區間之和肯定大於0.所以我們轉化成有多少個區間之和大於0.

我們對這個新序列求一遍字首和,我們都知道區間\([L,R]\)的和等於\(sum[R]-sum[L-1]\),如果想要這個區間和大於0,實際上就是\(sum[R]>sum[L-1]\),又由於\(R>L-1\),那麼我們發現區間之和大於0的區間個數實際上就是新序列字首和後的順序對個數,我們離散化之後按照逆序對套路處理即可

但是注意這時候我們沒有計算類似\([1,R]\)的區間,這個直接記錄一下就好了

但是回到前面的話題,我們已經知道了有多少個區間的中位數小於等於當前二分的這個數,我們該如何更改這個二分邊界呢?

還是一開始那個性質,由於區間個數總共有\(N \times (N+1)/2\)個,所以大於等於二分的這個數的區間個數如果大於等於\(N \times (N+1)/4+1\)的話,則將二分右邊界左移,否則左邊界右移

注意一開始\(N \times (N+1)\) 前沒打1ll,結果爆int了,查了好久的錯...

const int maxn=200005;
const int inf=0x7fffffff;
int n,nn,a[maxn],b[maxn],c[maxn],mx=-inf,mi=inf;
ll sum[maxn<<2];
inline void update(int x,int d){for(;x<=nn;x+=x&(-x))sum[x]+=d;}
inline ll query(int x){ll ans=0;for(;x;x-=x&(-x))ans+=sum[x];return ans;}
inline ll chk(int k){
    ll ans=0;
    b[0]=0;
    for(ri i=1;i<=n;i++){
        b[i]=b[i-1]+((a[i]<=k)?1:-1);
        c[i]=b[i];
        ans+=(b[i]>0);//記錄[1,R]的區間
    }
    std::sort(c+1,c+1+n);
    nn=std::unique(c+1,c+1+n)-(c+1);
    memset(sum,0,sizeof(sum));
    for(ri i=1;i<=n;i++){
          b[i]=lower_bound(c+1,c+1+nn,b[i])-c;//離散化
        ans+=query(b[i]-1);
        update(b[i],1);
    }
    return ans;
}
int main(){
    read(n);
    for(ri i=1;i<=n;i++){
        read(a[i]);
        mi=min(mi,a[i]),mx=max(mx,a[i]);
    }
    int L=mi,R=mx,mid,ans;
      ll lim=1ll*n*(n+1)/4+1;
    while(L<=R){
        mid=(L+R)>>1;
        if(chk(mid)>=lim)ans=mid,R=mid-1;
        else L=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}