9.15 提高4
目錄
- A 天(貪心)
- B 的(Prim)
- C 碳(線段樹)
比賽鏈接
A 天(貪心)
題目鏈接
選擇用小根堆維護。我們發現問題在於,當前\(j\)取了一個前面最小的\(i\)配對,但有可能後面有更優的\(k\)與\(i\)配對。
但是註意到\(a[k]-a[i]=a[k]-a[j]+a[j]-a[i]\),我們可以讓\(j\)選\(i\),同時有機會讓\(j\)撤銷選\(i\),即再在堆中加一個\(A[j]\),但選取它不會增加次數(原本的\(A[j]\)當然還要有)。
而且選最小值的順序是沒有影響的,即\(j\)選了\(i\)而\(k\)選了個也比\(j\)小比\(i\)大的\(i'\)
題解做法:
//185ms 2888kb #include <queue> #include <cstdio> #include <cctype> #include <algorithm> //#define gc() getchar() #define MAXIN 150000 #define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++) #define fir first #define sec second #define mp std::make_pair #define pr std::pair<LL,int> typedef long long LL; const int N=5e4+5; char IN[MAXIN],*SS=IN,*TT=IN; inline int read() { int now=0;register char c=gc(); for(;!isdigit(c);c=gc()); for(;isdigit(c);now=now*10+c-'0',c=gc()); return now; } void Work() { std::priority_queue<pr,std::vector<pr>,std::greater<pr> > q; // while(!q.empty()) q.pop();//不清空要快很多... int n=read(); LL Ans=0; int cnt=0; for(int i=1; i<=n; ++i) { int ai=read(); if(q.empty()||q.top().fir>=ai) q.push(mp(ai,2)); else { Ans+=ai-q.top().fir, cnt+=q.top().sec; q.pop(); q.push(mp(ai,0)), q.push(mp(ai,2)); } } printf("%lld %d\n",Ans,cnt); } int main() { for(int T=read(); T--; Work()); return 0; }
B 的(Prim)
題目鏈接
首先我們可以二分答案。如何判斷直徑為\(x\)的球能否通過呢。
將上下邊界也看做一個障礙點。任意兩個障礙點如果距離不超過\(x\)則連邊。當最後上下邊界連通時,說明存在某些障礙點使得球不能通過。
這樣復雜度為\(O(n^2\log Ans\alpha n)\)。
我們可以利用Kruskal去做。將邊全部從小到大排序,依次加入,當某一時刻上下邊界連通時,則輸出。
復雜度為\(O(n^2\log n^2+n^2\alpha n)\)。(因為邊數太多所以和上面差不多?)
這實際上是在求一棵\(n+2\)個點的最小生成樹,直到上下邊界連通。對於這樣的圖我們用Prim就可以\(O(n^2)\)
//9ms 612kb
#include <cmath>
#include <cstdio>
#include <cctype>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 200000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
const int N=505;
int read();
bool vis[N];
double dis[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Point
{
int x,y;
inline int Init() {return x=read(),y=read();}
}p[N];
inline int read()
{
int now=0,f=1;register char c=gc();
for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now*f;
}
inline double Dis(double x,double y)
{
return sqrt(x*x+y*y);
}
int main()
{
int n=read(); double L=read();
for(int i=1; i<=n; ++i) dis[i]=p[i].Init();
dis[++n]=L; double ans=0;
for(int i=1; i<=n; ++i)
{
int now=n;
for(int j=1; j<n; ++j) if(!vis[j]&&dis[j]<dis[now]) now=j;
vis[now]=1, ans=std::max(ans,dis[now]);
if(now==n) break;
for(int j=1; j<n; ++j)
if(!vis[j]) dis[j]=std::min(dis[j],Dis(p[now].x-p[j].x,p[now].y-p[j].y));
dis[n]=std::min(dis[n],L-p[now].y);
}
printf("%.3lf\n",ans);
return 0;
}
C 碳(線段樹)
題目鏈接
記前綴和為\(pre\),後綴和為\(suf\)。
一個顯然的貪心是,從前往後枚舉,找到一個\(pre<0\)的位置就把這個\(1\)刪掉。然後對修改後的後綴和再這麽求一遍。
事實上如果只考慮前綴(或者處理完前綴考慮後綴和),我們只需要找到一個\(\min\{pre_k\}/\min\{sum_k\}\),記\(i/j\)為最小的前/後綴和的下標,那麽\(|pre_i|\)就是前面總共要刪的次數(\(|sum_j|\)為後面要刪的次數)。
所以我們要求:\(\min\{pre_i\}-pre_{l-1}+\min\{sum_j\}-sum_{r+1}\)。
兩個\(\min\)的和能否直接用線段樹維護?
我們發現\(i<j\)時,兩個\(\min\)互不影響;\(j\leq i\)時,答案可以表示為\(\min\{pre_k\}+sum_j(k<j)\)(怎麽說...)。所以可以用線段樹先找左邊的最小的\(pre\),然後用\(sum\)更新。
前/後綴和可能沒有(如\(01\)),可以把初始前/後綴和\(>0\)的直接設為\(0\);或者查的時候直接查\([l-1,r+1]\)。
//1329ms 11708kb
#include <cstdio>
#include <cctype>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
const int N=2e5+5,INF=1e8;
int pre[N],suf[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Segment_Tree
{
#define ls rt<<1
#define rs rt<<1|1
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define S N<<2
int ml[S],mr[S],mv[S];
#undef S
inline void Update(int rt)
{
ml[rt]=std::min(ml[ls],ml[rs]),
mr[rt]=std::min(mr[ls],mr[rs]),
mv[rt]=std::min(ml[ls]+mr[rs],std::min(mv[ls],mv[rs]));
}
void Build(int l,int r,int rt)
{
if(l==r)
{
ml[rt]=pre[l], mr[rt]=suf[l], mv[rt]=INF;
return;
}
int m=l+r>>1;
Build(lson), Build(rson);
Update(rt);
}
void Query(int l,int r,int rt,int L,int R,int &ans,int &minl)
{
if(L<=l && r<=R)
{
// if(minl==INF) minl=ml[rt], ans=mv[rt];
ans=std::min(ans,std::min(minl+mr[rt],mv[rt])), minl=std::min(minl,ml[rt]);
return;
}
int m=l+r>>1;
if(L<=m) Query(lson,L,R,ans,minl);
if(m<R) Query(rson,L,R,ans,minl);
}
}T;
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
int main()
{
#define S 0,n+1,1
int n=read(),Q=read();
register char c=gc(); while(!isdigit(c)) c=gc();
for(int i=1; i<=n; ++i) pre[i]=c=='0'?1:-1, c=gc();
for(int i=n; i; --i) suf[i]=pre[i]+suf[i+1];
for(int i=1; i<=n; ++i) pre[i]+=pre[i-1];
T.Build(S);
for(int l,r,ans,minl; Q--; )
{
l=read()-1, r=read()+1, ans=minl=INF;
T.Query(S,l,r,ans,minl);
printf("%d\n",-(ans-pre[l]-suf[r]));
}
return 0;
}
9.15 提高4