【NOI2019】序列


題面

https://loj.ac/problem/3158

題解

$orz$ $AYSN$

考場上秒出費用流$64pts$的奆神

我想這道題的時候在廁所,一開始想到費用流了,但是后來又想了一個$28pts$的背包,感覺背包更靠譜,於是就寫背包了。。。

費用流貌似沒那么難,可我卻沒有想到。果然還是太弱了。

用流的大小限制取的個數。

最左邊 $S$,左側 $ABC$ 三個點,右側 $n$ 個點,左右邊$ T$。
從 $l$ 到 $k$ 枚舉成對的點的個數 $i$,$S$ 向 $A$ 和 $B$ 連流量 $l-i$,費用 $0$,向 $C$ 連流量 $i$,費用 $0$。
$A$ 向 $n$ 個點連流量 $1$,費用 $ai$,類似的,$B$ 連的費用是 $bi$,C是 $ai+bi$。
最后 $n$ 個點向 $T$ 連流量 $1$ 費用 $0$。 

update 2020 - 5 - 12

一篇隨便的題解占據熱搜,我有點愧疚呢,那就重新寫一個 100 分做法吧!

說一下我在考場上的 28 分的暴力 dp 吧。

設 $f_{i,j,k,l}$ 為考慮到序列的第 $i$ 個位置,已經選了 $j$ 個 a 數組中的元素,$k$ 個 b 數組中的元素,其中有 $l$ 個位置上,a 數組的元素和 b 數組的元素同時被選上,這種情況下最大的選中的元素之和。

然后考慮這一次是

  • 不選
  • 只選 $a_i$
  • 只選 $b_i$
  • $a_i,b_i$ 都選

狀態 $O(n^4)$,轉移 $O(1)$,總復雜度 $O(n^4)$。


 事實上,$O(n^3)$ 也不難。這道題說是一個序列,但如果把 $(a_i,b_i)$ 看做一個二元組,給定的 $\{a_i\},\{b_i\}$ 一個二元組的集合。

對於一個集合,我們可以把集合中元素按某個關鍵字排序,使得 “集合” 的無序性得以體現,這樣更接近本質。

我們按 $a_i$ 從大到小排序。考慮構成答案的元素的形式。顯然,我們可以發現,若 $i$ 位置沒有選 a,一定不存在以后的一個 $j>i$,使得 $j$ 只選了 a。如果這樣,把 $j$ 上的 a 換成 $i$ 上的 a,答案不會變的更劣,都選的地方也不會變的更少。這樣不僅決策少了,狀態也少了。

所以前面一部分只能做出決策:

  • 只選 a
  • a,b 都選

后面一部分只能做出決策:

  • 只選 b
  • a,b 都選
  • 都不選

前一部分只需要記錄 $f_{i,j}$,選到了第 $i$ 個,有 $j$ 個是 $a,b$ 都選的。

后一部分只需要記錄 $g_{i,j,k}$,(從后往前)選到了第 $i$ 個,有 $j$ 個只選 b 的,$k$ 個 a,b 都選的,顯然有 $(n-i+1-j-k)$ 個都不選的。

這樣枚舉前一部分和后一部分的分界,就可以做到 $O(n^3)$ 了。


到了 $O(n^2)$ 了。必須需要點靈感才能得到進一步的分數了。

我們發現,按 $a_i$ 或是 $b_i$ 排序是及其愚蠢的,因為我們不僅要枚舉 a,還要枚舉 b,還要算交集,這樣似乎是怎樣都無法做到 $O(n^2)$ 的。

我們按 $a_i+b_i$ 排序。(自然,按 $a_i+b_i$ 排序上一步也是可以做的,但這一步只能按 $a_i+b_i$ 排序)

如果一個位置,既沒有選 a,又沒有選 b,那它以后肯定不會有都選的位置了。如果有,換過來,肯定不會變的更劣。

所以 $a_i+b_i$ 肯定集中出現在一個前綴中(這個前綴不能有空的),我們枚舉這個前綴,設為 $i$,此時,$l$ 個都選的位置集中出現在前 $i$ 個位置,可以知道前 $i$ 個位置只空了 $i-k$ 個位置,我們枚舉空了 x 個 a,y 個 b,把最小的 x 個 a 和 y 個 b 去掉,再在剩下的序列中把缺的都選最大的加上,就可以了。

一個問題是如果同時去掉了一個位置上的 a 和 b,那該咋辦?事實上,怎么辦都可以,因為這樣一定是劣的,不管它就行了。

為了保證復雜度 $O(n^2)$,我們用 4 個鏈表(分別對前面的 a,前面的 b,后面的 a,后面的 b 開)實現這些操作。添加復雜度 $O(n)$,查詢前 $x$ 大 $O(1)$。


然后是 $O(n\log^2n)$。這一步很顯然,因為刪的 a 是越來越大的,復活的 b 越來越小,新加的 a 越來越小,少的 b 越來越大,$\Delta f(x)$ 單調減,這個函數關於 x 有凸性,我們只需要對找到第一個 $\Delta f(x)<0$ 的位置,就是凸函數的最大值。

用平衡樹或是樹狀數組維護,平衡樹常數太大了,只能得 64 分。好像我寫的樹狀數組也才得 80 分。


最后一步也很顯然,我們對每一個 $i$ ,把它取到最大值的點輸出出來,發現從 $i$ 到 $i+1$,要么位置不變,要么位置向右平移一位,所以直接判斷一下就行了,復雜度 $O(n\log n)$!可以得到 100 分。

#include<bits/stdc++.h>
#define N 200500
#define K (1<<19)
#define ri register int
#define LL long long 
using namespace std;

int n,k,l,t[N],tru[1<<19];
LL ss[N],tt[N];
struct node {
  int a,b;
  bool operator < (const node &rhs) const {
    return tru[a]+tru[b]>tru[rhs.a]+tru[rhs.b];
  }
} v[N];

inline int read() {
  int ret=0,f=0; char ch=getchar();
  while (ch<'0' || ch>'9') f|=(ch=='-'),ch=getchar();
  while (ch>='0' && ch<='9') ret*=10,ret+=ch-'0',ch=getchar();
  return f?-ret:ret;
}

struct treap {
  int f[K],a[N],cnt;
  LL sum[K];
  inline void clear() {
    for (ri i=1;i<=cnt;i++) {
      for (ri j=a[i];j<K;j+=j&(-j)) f[j]=sum[j]=0;
    }
    cnt=0;
  }
  inline void insert(ri x) {
    a[++cnt]=x;
    for (ri i=x;i<K;i+=(i&(-i))) f[i]++,sum[i]+=tru[x];
  }
  inline void erase(ri x) {
    for (ri i=x;i<K;i+=(i&(-i))) f[i]--,sum[i]-=tru[x];
  }
  inline LL findxsum(ri k) {
    register LL ret=0; register int cur=0;
    for (ri i=18;i>=0;i--) if (f[cur+(1<<i)]<=k ) {
      k-=f[cur+(1<<i)];
      cur+=(1<<i);
      ret+=sum[cur];
    }
    if (cur+1<K) ret+=k*1LL*tru[cur+1];
    return ret;
  }
  inline LL finddsum(ri k) {
    register LL r1=0; register int tot=0;
    for (ri i=K-1;i;i-=(i&(-i))) r1+=sum[i],tot+=f[i];
    return r1-findxsum(tot-k);
  }
} t1,t2,t3,t4;

inline LL solve(ri x,ri tot,ri i) {
  if (t[x]==i) return tt[x];
  ri y=tot-x;
  t[x]=i; tt[x]=ss[i]-t1.findxsum(x)-t2.findxsum(y)+t3.finddsum(k-i+x)+t4.finddsum(k-i+y);
  return tt[x];
}

inline LL dp() {
  sort(v+1,v+n+1);
  for (ri i=1;i<=n;i++) ss[i]=ss[i-1]+tru[v[i].a]+tru[v[i].b];
  for (ri i=0;i<=n;i++) t[i]=-1;
  t1.clear(); t2.clear(); t3.clear(); t4.clear();
  for (ri i=1;i<l;i++) t1.insert(v[i].a);
  for (ri i=l;i<=n;i++) t3.insert(v[i].a);
  for (ri i=1;i<l;i++) t2.insert(v[i].b);
  for (ri i=l;i<=n;i++) t4.insert(v[i].b);
  LL ans=0;
  for (ri i=l,pre=0;i<=n && i<=l+(k-l)+(k-l);i++) {
    t1.insert(v[i].a); t2.insert(v[i].b); t3.erase(v[i].a); t4.erase(v[i].b); 
    int tot=i-l,lb=max(max(0,l+tot-k),l-i+tot),ret=lb,rb=min(min(tot-1,k-l-1),i-l-1);
    if (lb>rb+1) continue;
    if (pre<lb) pre=lb;
    if (pre+1<=rb+1 && solve(pre+1,tot,i)>=solve(pre,tot,i)) ret=pre+1; else ret=pre;
    LL cur=solve(ret,tot,i);
    pre=ret;
    if (cur>ans && lb<=rb+1) ans=cur;
  }
  return ans;
}

int main() {
  int T=read();
  while (T--) {
    n=read(); k=read(); l=read();
    int co=0;
    for (ri i=1;i<=n;i++) v[i].a=read(),tru[++co]=v[i].a;
    for (ri i=1;i<=n;i++) v[i].b=read(),tru[++co]=v[i].b;
    sort(tru,tru+co+1);
    int tn=unique(tru,tru+co+1)-tru-1;
    for (ri i=1;i<=n;i++) v[i].a=lower_bound(tru+1,tru+tn+1,v[i].a)-tru;
    for (ri i=1;i<=n;i++) v[i].b=lower_bound(tru+1,tru+tn+1,v[i].b)-tru;
    printf("%lld\n",dp());
  }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM