CF475E Strongly Connected City 2
题目描述
想象有一座城市,这座城市有 \(n\) 个路口和 \(m\) 条街道。路口编号从 \(1\) 到 \(n\)。
为了提高交通流量,市长决定将每条街道改为单行道。这意味着在连接路口 \(u\) 和 \(v\) 的街道上,交通只能从 \(u\) 到 \(v\),或只能从 \(v\) 到 \(u\) 单向通行。
现在的问题是,需要为这些街道指定单向通行的方向,使得能够到达的点对 \((u, v)\) 的数量最大,其中 \(1 \le u, v \le n\),且要求从 \(u\) 出发经过指定方向的街道能够到达 \(v\)。你的任务是找出最大可能的此类点对数量。
题解:
先大概猜猜,发现一个简单的性质,对于一个环,一定满足全部都一个方向最优秀。
所以我们先做一个 tarjan
。点权为环的点数。然后发现贪心不大行,考虑 dp
。
这个时候还要一个性质,对于最优解。就是在只在重心上,能满足其一部分子树的所有点能到达他,其他点都可以从他到达。
证明:
考虑有一个小联通快所有边的方向与其他不同,那么发现把这个所有边全部翻转过来,答案一定变大,因为重心一侧的点至少为所有点的一半。
这下就可以 dp 了。我们把重心作为根,考虑每个点的贡献是多少:
-
(除 \(root\))自己 \(\times\) 自己的子树和
-
通过重心的到达另一端
其实就是要判断那些子树进入,那些子树要出去。
我们发现第一个贡献不论怎么分配都是一样的。
第二个贡献实际上是一个数学问题:设你进入的节点总数为 \(x\),重心点权为 \(y\) 另一侧点的数量和为 \(z\)。那么有答案为 \((x+y)\times (y+z)\)。
那其实就是要找到每一个 \(x\) 能不能被表示出来,这是一个典型的 01背包
直接做复杂度 \(O(n^2)\) 用bitset
维护的话是 \(\frac{n^2}{w}\) 的。
code:
#include<bits/stdc++.h>
#define pb push_back
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int N=2e5+10;
int n,m,k,T,a[N],vis[N],dfn[N];
int low[N],tot,col,scc[N],siz[N];
int rt,mx=INT_MAX,sz[N],ans,sum;
vector<int> g[N],g1[N];
stack<int> st;
void tar(int u,int fa){dfn[u]=low[u]=++tot;vis[u]=1;st.push(u);for(int v:g[u]){if(v==fa) continue;if(!dfn[v]){tar(v,u);low[u]=min(low[u],low[v]);}else if(vis[v]) low[u]=min(low[u],dfn[v]);}if(low[u]==dfn[u]){col++;while(1){int t=st.top();st.pop();scc[t]=col;vis[t]=0;siz[col]++;if(t==u) break;}}
}
void getrt(int u,int fa){sz[u]=siz[u];int res=0;for(int v:g1[u]){if(v==fa) continue;getrt(v,u);sz[u]+=sz[v];res=max(res,sz[v]);}res=max(res,n-sz[u]);if(res<mx) mx=res,rt=u;
}
void dfs(int u,int fa){sz[u]=siz[u];for(int v:g1[u]){if(v==fa) continue;dfs(v,u);sz[u]+=sz[v];}if(fa)sum+=siz[u]*sz[u];
}
bitset<N> s;
signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>n>>m;rep(i,1,m){int u,v;cin>>u>>v;g[u].pb(v);g[v].pb(u);}tar(1,0);rep(i,1,n){for(int v:g[i]){if(scc[i]!=scc[v]){g1[scc[i]].pb(scc[v]);}}}rt=0;getrt(1,0);dfs(rt,0);s[0]=1;if(g1[rt].size()==n-1){cout<<1ll*((n-1)/2)*(n-((n-1)/2))+(n-((n-1)/2)-1)+n;return 0; }for(int v:g1[rt]){s=s|(s<<sz[v]);}rep(i,0,n){if(s[i]==1){ans=max(ans,(i+siz[rt])*(n-i));}}cout<<ans+sum;return 0;
}