题目链接
https://ac.nowcoder.com/acm/contest/11170/D
题目大意
举个栗子,现在有六个点,但只有一条有向边(1->2),此时每个点都是只有一个点的强连通分量,所以平方和是6。
解题思路
贪心的想,我们每次选,肯定每次要尽可能选多的边加入。那么对于每一个当前左端点L,二分选右端点。但这样时间复杂度是O(n2logn)的。我们可以用倍增的想法优化,对于当前左端点L,看其后20的边加入是否满足,再看21…2k一直到不满足条件,这时再二分区间[2k-1,2k]找到具体的右端点。这样每次倍增的复杂度O(LlogL),二分复杂度O(LlogL)。所以最后总时间复杂度是O(nlogn)的。最后写代码的时候一些小的优化要注意下,不然会超时。
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M=1e6+5;
const int N=1e5+5;
int n,m;
ll k;
pair<int,int>ask[M];
vector<int>edge[N];
vector<int>node;
int cnt,top;
int low[N],dfn[N];
bool vis[N];
int stc[N];
ll now;
void tarjan(int u)
{
dfn[u]=low[u]=++cnt;
vis[u]=1;
stc[++top]=u;
for(auto v:edge[u])
{
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v])
low[u]=min(low[v],low[u]);
}
ll sum=0;
if(dfn[u]==low[u])
{
sum++;
vis[u]=0;
while(stc[top]!=u)
{
vis[stc[top--]]=0;
sum++;
}
top--;
now+=sum*sum;
}
}
inline void init()
{
now=n;
cnt=top=0;
}
bool check(int L,int R)
{
init();
R=min(R,m);
for(int i=L;i<=R;++i)
{
edge[ask[i].first].push_back(ask[i].second);
node.push_back(ask[i].first);
}
for(auto x:node)
if(!dfn[x])
{
cnt=0;
tarjan(x);
now-=cnt;
}
for(int i=L;i<=R;++i)
{
edge[ask[i].first].pop_back();
dfn[ask[i].first]=low[ask[i].first]=0;
dfn[ask[i].second]=low[ask[i].second]=0;
}
node.clear();
return now<=k;
}
int main()
{
scanf("%d%d%lld",&n,&m,&k);
for(int i=1;i<=m;++i)
{
int x,y;
scanf("%d%d",&x,&y);
ask[i]=make_pair(x,y);
}
int ans=0;
for(int L=1;L<=m;++L)
{
int k=0;
++ans;
while(check(L,L+(1<<k)-1))
{
if(L+(1<<k)-1>m)
break;
++k;
}
int l=L+(1<<k-1)-1;
int r=L+(1<<k)-1;
int tmp=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(L,mid))
{
l=mid+1;
tmp=max(tmp,mid);
}
else
r=mid-1;
}
L=tmp;
}
printf("%d\n",ans);
return 0;
}