题解 | #分组#

分组

https://ac.nowcoder.com/acm/contest/11170/D

D题
因为细节错误,最后一分钟才A出来。。。。。。。

首先,我们有一个很明显的贪心。
就是,我们从左到右去枚举边的话,肯定是尽量囊括边 到濒临阈值的情况下

即,我们从最左端一直向右取边,取到极限,算作一组
然后以当前位置为最左端,向右取边取到极限,算作一组
以此类推。

但是我们是无法这样做的,因为我们无法做到动态的查询强连通分量

那么二分可不可以呢?
首先我们以最左端编号1开始二分,找到他可以取到的最右端的编号l2
然后再次二分。。。。
每次二分都使用一次tajan算法验证一遍

但是这样行不行呢?
没试,但是预计会T 我们可以大致感觉到这个复杂度是有点高的

这时,我看了下数据

然后我们可以意识到一个重要的结论:
如果我这个图中只有n个点,那么这个图的重量一定是<=n^2的!!!!
即,我这些点连成了一个强连通

这个结论扩展到边也差不多
如果这个图有m个边,n个点。那么这个图的重量一定是<=m^2 + n-m !!!!!
即,我m条边构造了一个有m个点的强连通分量,然后剩余n-m个孤立的点!!

一句这个结论我们可以做到什么?
这说明了,我们在一开始就可以按照结论,将整个边序列分成大致 个区间!!!!

然后我们需要考虑的就变成了:
从最左边开始,一个一个地合并区间!
能合并就合并,无法合并就取最右(依照我们之前的贪心策略,我们要尽量包含多的边取到上限!)

而这里,如何找到最右边的那个上限,我们就可以使用二分了!

当然,为了不T我们还需要,稍微修改一下tarjan,中遍历点的情况
即:我们只要保留我们所选区间的点即可。
具体实现见代码:

#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<stack>
using namespace std;
typedef long long ll;
const int max_n = 1e6+100;
int n,m;
ll k;
int es[max_n][2];
vector<int> V;
struct edge
{
    int to,next;
}E[max_n<<1];
int head[max_n];
int cnt=1;
void add(int from,int to)
{
    E[cnt].to=to;
    E[cnt].next=head[from];
    head[from]=cnt++;
}

int dfn[max_n],low[max_n];
stack<int> st;
int colour=0;
int color[max_n];
int sz[max_n<<1];
int tot=0;
void tarjan(int u) {
    dfn[u] = low[u] = ++tot;
    st.push(u);
    for (int i = head[u];i;i = E[i].next)
    {
        int v = E[i].to;
        if (!dfn[v]) 
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if (color[v] == 0)low[u] = min(low[u], dfn[v]);
    }
    if (low[u] != dfn[u])return;

    colour++;
    while (st.top() != u) 
    {
        color[st.top()] = colour;
        st.pop();
        sz[colour]++;
    }
    color[st.top()] = colour;
    st.pop();
    sz[colour]++;
}


int que[max_n];
int h=0;
int cct=0;
int vis[max_n];
bool check(int l,int r)
{
    ++cct;
    int nodes = 0;
    h=0;
    for (int i=l;i<=r;++i)
    {
        que[++h]=es[i][0];
        que[++h]=es[i][1];
        if (vis[es[i][0]]!=cct)
        {
            vis[es[i][0]]=cct;
            ++nodes;
        }
        if (vis[es[i][1]]!=cct)
        {
            vis[es[i][1]]=cct;
            ++nodes;
        }
    }
    nodes = n-nodes;
    cnt=1;
    for (int i=1;i<=h;++i)head[que[i]]=dfn[que[i]]=low[que[i]]=color[que[i]]=0;
    for (int i=l;i<=r;++i)add(es[i][0],es[i][1]);

    ll ans = 0;
    tot=0;
    colour=0;
    for (int i=0;i<=h;++i)sz[i]=0;
    while (!st.empty())st.pop();

    for (int i=1;i<=h;++i)
    {
        if (dfn[que[i]])continue;
        tarjan(que[i]);
    }

    for (int i=1;i<=colour;++i)
        ans+= (ll)sz[i]*(ll)sz[i];

    return ans+nodes<=k;
}

int Solve(int cur,int l,int r)
{
//    cout<<cur<<" "<<l<<" "<<r<<endl;
    --r;
    if (check(cur,r))return cur;
    int ans=l-1;
    int lft = l-1,rgt=r;
    while (lft<=rgt)
    {
        int mid = (lft+rgt)>>1;
        if (check(cur,mid))
        {
            lft = mid+1;
            ans = mid;
        }
        else rgt =mid-1;
    }
    ++ans;
    if (ans==r+1)return cur;
    else return ans;
}

int main()
{
    ios::sync_with_stdio(0);
    cin>>n>>m>>k;
    for (int i=1;i<=m;++i)cin>>es[i][0]>>es[i][1];
    V.push_back(1);
    ll lim = sqrt(k);
    while (lim*lim+n-lim>k)--lim;
    for (int i=2;i<=m;++i)
        if (i-V.back()==lim)
            V.push_back(i);

    if (V.size()==1)
    {
        cout<<1<<endl;
        return 0;
    }

    else if (V.size()==2)
    {
        if (check(1,m))cout<<1<<endl;
        else cout<<2<<endl;
        return 0;
    }

    int ans = 1;
    V.push_back(m+1);
    int cur = 1;

    for (int i=1;i<V.size()-1;++i)
    {
        int pre = cur;
        cur = Solve(cur,V[i],V[i+1]);
        if (cur!=pre)++ans;
    }
    cout<<ans<<endl;
}
全部评论

相关推荐

点赞 评论 收藏
分享
头像
11-09 12:17
清华大学 C++
out11Man:小丑罢了,不用理会
点赞 评论 收藏
分享
6 收藏 评论
分享
牛客网
牛客企业服务