1. 程式人生 > >2018 09.23 挖掘機(二分答案)

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; }