2018 09.23 挖掘機(二分答案)
描述
派了一群瘋狂伊文成功摧毀敵軍的碉堡後, L終於得到了他想要的挖掘機,於是開始 情不自禁地挖掘。 L依舊把地面看作連續的 N 個格子。由間諜傳回的情報,敵軍在這些格子中的每一個 裡都埋有一顆地雷,且第 i 格中地雷的種類為 ti。 ti 為一個 1 到 M 之間的整數,並且第 i 類雷 的重量均為 wi。 為了為我軍的坦克開闢前進的道路,當然也為了使用自己的挖掘機, L 開始愉快地掃 雷(確切地說是挖雷)。 L會事先選擇好 L; R(1 6 L 6 R 6 N),然後從第 L 個格子出發開到第 R 個格子,邊 開邊挖地上的雷。 Mr.Lin 喜歡新奇之物,假如當前挖出的雷不與之前挖出的任何一顆雷屬於同 一類,他就會把這顆雷收藏起來。否則,他會把地雷拆解回收。 不過, L 現在還沒有想好合適的 L 和 R。他的幸運數字是 k,於是希望你幫他選擇合 適的 L; R 使得最後收藏的雷的重量之和為所有方案中第 k 大。
輸入
第一行兩個正整數 N; M 分別表示格子數和地雷的種數。 第二行 M 個正整數 wi。 第三行 N 個正整數 ti。 第四行一個正整數 k,表示L的幸運數字。
輸出
第一行一個整數為收藏的雷的總重量。 第二行兩個正整數為滿足條件的 L 和 R。如果有多個滿足條件的 L; R,輸出字典序最小的 (即第一關鍵字為 L,第二關鍵字為 R)。
樣例輸入
3 2 1 2 1 2 1 2
樣例輸出
3 1 2
提示
對於 20% 的資料, N; M <=100。 對於 40% 的資料, N; M <= 1000。 另有 20% 的資料, k <= 10^5。 對於 100% 的資料, 1 <= N; M <= 10^5; 1 <= wi <= 10^9; 1 <= ti <= M; 1 <= k <= (1+N )*N/2
一道很好的二分答案。 考慮二分第k個值是val的時候如何檢驗。 我們可以借用雙指標的思想來統計小於二分值val之內有多少個數。 然後通過跟k比較來判斷是否合法,最後再次使用雙指標的思想找到字典序最小的區間就行了。 程式碼:
#include<bits/stdc++.h>
#define N 100005
#define ll long long
using namespace std;
inline ll read(){
ll ans=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ans= (ans<<3)+(ans<<1)+(ch^48),ch=getchar();
return ans;
}
ll w[N],l=0,r=0,k,ret,tot,ans;
int pos,col[N],pre[N],n,m;
bool in[N];
inline ll check(ll val){
tot=0,pos=1,ret=0,memset(in,false,sizeof(in)),memset(pre,0,sizeof(pre));
for(int i=1;i<=n;++i){
if(pre[col[i]]<pos)tot+=w[col[i]];
in[pre[col[i]]]=false,in[(pre[col[i]]=i)]=true;
while(tot>=val&&pos<=i)tot-=in[pos]*w[col[pos++]];
ret+=pos-1;
}
return ret;
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;++i)r+=(w[i]=read());
for(int i=1;i<=n;++i)col[i]=read();
k=read();
while(l<=r){
ll mid=l+r>>1;
if(check(mid)<k)r=mid-1;
else l=mid+1,ans=mid;
}
cout<<ans<<'\n',tot=0,pos=1,ret=0,memset(in,false,sizeof(in)),memset(pre,0,sizeof(pre));
for(int i=1;i<=n;++i){
if(pre[col[i]]<pos)tot+=w[col[i]];
in[pre[col[i]]]=false,in[(pre[col[i]]=i)]=true;
while(tot>ans&&pos<=i)tot-=in[pos]*w[col[pos++]];
if(tot==ans){cout<<pos<<' '<<i;return 0;}
}
return 0;
}