题解:CF475E Strongly Connected City 2

· · 题解

bitset 萌萌题。

思路

首先注意到一个很显然的,如果有环的话,可以通过构造使得环内点互相可达,所以我们先缩点,然后就形成了一个森林,然后对于每颗树单独求解。

容易发现一个东西,对于每个点作为根,枚举它的子树,里面的边(包括这个点到根那一条)都应该是一样的方向,即都是指向儿子或都指向父亲。

原因很显然,因为对于一个联通块来讲如果边都是一样的方向,那么同时翻转贡献是不变的,但如果旁边有指向不一样的联通快翻转后就会有额外贡献,所以对于一个子树来讲肯定是都一样最优,同时由于这些点都是等价点了,所以与其他子树拼接造成的贡献也是最大的(拼接的意思是一个内向子树和一个外向子树)。

这个的贡献是好求的,记 f_i 为点 i 的子树造成的贡献,就是每个点 u,看每个子树大小和乘上当前这个点的权值,因为我们缩点了,所以有点权,然后在加上每个儿子的 f_v 即可。

对于一颗树,其贡献就是枚举一个点为根 i,跑出 f_i,然后就是指向根的子树大小和乘上指向儿子的子树大小,注意到这个和是定值,我们肯定希望两边越接近越好。

考虑每个点的 f_i 可以换根求出来,复杂度 \Theta\left(n\right),然后我们可以设 g_i 表示是否能凑出大小为 i 的数,bitset 优化可以做到 \Theta\left(\frac{n\times V}{\omega}\right),然后用 bitset 自带的 _Find_next(i) 函数就可以查找 i 后面第一个值为一的地方,单次复杂度 \Theta\left(\frac{V}{\omega}\right)i 赋值为总大小除二向下取整后再减一即可。

给出代码,不过没怎么优化,在 n 到一定量级需要卡一卡。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define getchar() (p1 == p2 && (p2 = (p1 = buf1) + fread(buf1, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf1[1 << 23], *p1 = buf1, *p2 = buf1, ubuf[1 << 23], *u = ubuf;
namespace IO
{
    template<typename T>
    void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
    template<typename T,typename... Args>
    void read(T &_x,Args&...others){Read(_x);Read(others...);}
    const int BUF=20000000;char buf[BUF],to,stk[32];int plen;
    #define pc(x) buf[plen++]=x
    #define flush(); fwrite(buf,1,plen,stdout),plen=0;
    template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++to]=48+x%10;while(to) pc(stk[to--]);}
}
using namespace IO;
const int N = 2e3+10,M = 4e6+10;
int n,m,x,y,head[N],dfn[N],low[N],Cnt,cnt,dep[N],fa[N],col[N],st[N],o,f[N],S[N],siz[N],Col[N],ans,p,sum[N],su;
bitset<N>g;
struct w
{
    int to,nxt;
}b[M<<1];
struct w1
{
    int x,y;
}A[M];
inline void add(int x,int y)
{
    b[++cnt].nxt = head[x];
    b[cnt].to = y;
    head[x] = cnt;
}
void dfs(int x,int y)
{
    st[++cnt] = x,dfn[x] = low[x] = ++Cnt; int sum = 0;
    for(int i = head[x];i;i = b[i].nxt)
    {
        if(b[i].to == y && sum == 0) { sum++; continue; }
        if(dfn[b[i].to]) low[x] = min(low[x],dfn[b[i].to]);
        else dfs(b[i].to,x),low[x] = min(low[x],low[b[i].to]); 
    }
    if(low[x] == dfn[x])
    {
        o++,p = 0;
        while(st[cnt+1] != x) p++,col[st[cnt]] = o,cnt--;
        ans += p*p;
    }
}
int find(int x)
{
    if(x == f[x]) return x;
    return f[x] = find(f[x]);
}
void dfs1(int x,int y,int z)
{
    siz[x] = S[x],fa[x] = y; Col[x] = z;// cout<<x<<" %*& "<<y<<" "<<S[x]<<endl;
    for(int i = head[x];i;i = b[i].nxt)
        if(b[i].to != y)
        {
            dfs1(b[i].to,x,z),siz[x] += siz[b[i].to];
            f[x] += f[b[i].to]+siz[b[i].to]*S[x];
        }
}
void dfs2(int x,int y,int z1)
{
    f[x] += z1+(siz[Col[x]]-siz[x])*S[x];
    int Sum = 0;
    for(int i = head[x];i;i = b[i].nxt)
        if(b[i].to != y) Sum += f[b[i].to];
    for(int i = head[x];i;i = b[i].nxt)
        if(b[i].to != y)
            dfs2(b[i].to,x,z1+Sum-f[b[i].to]+(siz[Col[x]]-siz[b[i].to]-S[x])*S[x]);
}
signed main()
{
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
    read(n),read(m);
    for(int i = 1;i <= n;i++) f[i] = i;
    for(int i = 1;i <= m;i++) read(x),read(y),add(x,y),add(y,x),A[i].x = x,A[i].y = y;
    for(int i = 1;i <= n;i++) 
        if(!dfn[i]) cnt = Cnt = 0,dfs(i,0),cnt = Cnt = 0;
    for(int i = 1;i <= n;i++) head[i] = 0,S[col[i]]++;
    for(int i = 1;i <= m;i++)
    {
        x = col[A[i].x],y = col[A[i].y];
        x = find(x),y = find(y);
        if(x != y) f[x] = y,add(col[A[i].x],col[A[i].y]),add(col[A[i].y],col[A[i].x]);
    }
    for(int i = 1;i <= o;i++) f[i] = 0;
    for(int i = 1;i <= o;i++)
        if(!Col[i])
            dfs1(i,0,i),dfs2(i,0,0);
    for(int i = 1;i <= o;i++)
    {
        g.reset(); su = 0; g[0] = 1;
        for(int j = head[i];j;j = b[j].nxt)
        {
            if(fa[i] == b[j].to) g |= (g<<(siz[Col[i]]-siz[i]));
            else g |= (g<<siz[b[j].to]);
        } int ly = g._Find_next((siz[Col[i]]-S[i])/2-1);
        su = ly*(siz[Col[i]]-ly-S[i]);//,cout<<j<<" &*&& "<<i<<" "<<Col[i]<<" ???? "<<siz[Col[i]]-j-S[i]<<endl;
    //  cout<<i<<" * "<<f[i]<<" "<<su<<endl;
        sum[Col[i]] = max(su+f[i],sum[Col[i]]);
    }
    for(int i = 1;i <= o;i++) 
        if(Col[i] == i) ans += sum[i];
    print(ans); flush();
    return 0;
}
/*
f_{i,j}
前i个点互相的都解决了,然后看第i+1个点
先来个暴论,能够成环优先成环
因为此时就都能到了
然后先缩点,然后就是若干联通块了
然后肯定对于一颗数肯定是一个类似拓扑序的东西
肯定有一个点有些点连他,他连一些点
枚举哪个点为根,然后直接f_j表示有j个点连根时最大方案数
转移枚举j,然后看是否选即可,复杂度n^2
注意到一个事实,子树无论是内向还是外向贡献是不变的
然后,唯一的不同就是x*(siz-x)
直接f_i表示是否能凑出i 

17 16
15 6
14 1
16 5
2 16
12 5
4 1
4 3
7 6
6 13
10 11
11 5
8 6
9 5
17 16
5 4
4 15
*/