牛客CSP-S提高组赛前集训营2 赛后总结
A.服务器需求
维护每天需要的服务器数量的全局最大值(记为\(Max\))和总和(记为\(sum\)),那么答案为:
\[max(Max,\lceil\dfrac{sum}{m}\rceil)
\]
证明略.
由于只有单点修改,可用一个 multiset 维护全局最大值和总和,当然,线段树也是可以的.
不过有一个易错点,在使用 multiset 的\(\text{.erase()}\)函数时,
- 如果写\(\text{s.erase(5)}\),传入一个值,会删除所有的 5 .
- 如果写\(\text{s.erase(find(5))}\),传入一个迭代器,只会删除 1 个 5.
#include<bits/stdc++.h>
const int SIZE=400005;
#define LL long long
int n,q,p;
LL m,x[SIZE],C,sum;
std::multiset<LL>s;
LL F()
{
LL R=sum/m;
if(sum%m>0)
++R;
if(R<*--s.end())
R=*--s.end();
return R;
}
int main()
{
scanf("%d%lld%d",&n,&m,&q);
for(int i=1;i<=n;i++)
{
scanf("%lld",&x[i]);
sum+=x[i];
s.insert(x[i]);
}
printf("%lld\n",F());
while(q--)
{
scanf("%d%lld",&p,&C);
sum-=x[p];
s.erase(s.find(x[p]));
x[p]=C;
s.insert(C);
sum+=C;
printf("%lld\n",F());
}
return 0;
}
B.沙漠点列
Tarjan 求割边和点双联通分量.
按贪心的思路,首先删除割边,每删除一条割边就会新出现一个连通块.
如果割边删完了,则删除点双联通分量中的边,容易发现,如果删除了一个点双联通分量中的\(j(j>0)\)条边,那么新出现\(j-1\)个连通块.
也就是说,删完一个点双联通分量,需要删除比新产生连通块数多 1 的边数,会浪费掉 1 次删边机会,我们想让这样的浪费尽可能少,所以贪心地从大的点双联通分量开始删起.
开始的时候我以为一个连通块就是一个环,也就是一个点双联通分量,然后才发现不是这样的😫.现场预习了怎么用 Tarjan 求点双连通分量后,才写出了正解.
Tarjan 求点双联通分量的方法:如果\(DFN[u]\le Low[v]\),则\(v\)到栈顶的所有点和\(u\)一起组成一个点双连通分量.
#include<bits/stdc++.h>
const int SIZE=4000005;
int In()
{
char ch=getchar();
int x=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
return x;
}
int n,m,k,head[SIZE],nex[SIZE],to[SIZE],Tot=1;
void Link(int u,int v)
{
nex[++Tot]=head[u];head[u]=Tot;to[Tot]=v;
nex[++Tot]=head[v];head[v]=Tot;to[Tot]=u;
}
std::priority_queue<int>q;
int DFN[SIZE],Low[SIZE],Sta[SIZE],Top,Tim,Ans;
bool mk[SIZE],Visited[SIZE];
void Tarjan(int u,int Fa)
{
DFN[u]=Low[u]=++Tim;
Sta[++Top]=u;
for(int i=head[u];i;i=nex[i])
{
int v=to[i];
if((Fa>>1)==(i>>1))continue;
if(!DFN[v])
{
Tarjan(v,i);
Low[u]=std::min(Low[u],Low[v]);
if(DFN[u]<Low[v])
mk[i>>1]=1;
if(DFN[u]<=Low[v])
{
int Siz=1,Tem;
do{++Siz;}while(Sta[Top--]!=v);
q.push(Siz);
}
}
else
Low[u]=std::min(Low[u],DFN[v]);
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++)
Link(In(),In());
for(int i=1;i<=n;i++)
if(!DFN[i])
{
Tarjan(i,0);
++Ans;
}
int Cut_Edge=0;
for(int i=2;i<=Tot;i+=2)
if(mk[i>>1])
++Cut_Edge;
if(k<=Cut_Edge)
Ans+=k;
else
{
k-=Cut_Edge;
Ans+=Cut_Edge;
while(q.size())
{
int Tem=q.top();
q.pop();
if(k>Tem)
{
k-=Tem;
Ans+=Tem-1;
}
else
{
Ans+=k-1;
break;
}
}
}
printf("%d",Ans);
return 0;
}
C.维护序列
毒瘤