有向图的强连通分量;
把所有环缩成一个点,使图变成有向无环图
#include<bits/stdc++.h> using namespace std; #define endl '\n' //typedef __int128 LL;//2的128 typedef long long ll; //2的64 // 1e9,, 1e19,,1e38 #define int long long const int N=50200; int cnt,head[N],n,m,timestamp; int dfn[N]; int low[N]; int stk[N],top,in_stk[N];//栈 int scc_cnt;//分量数 int Size[N];//强连通分量点的个数 int id[N];//强连通分量 int dout[N];//缩点后的出度 int din[N];//缩点后的入度 struct edge { int to,nx; }; edge ed[N]; void add(int a,int b) { ed[++cnt].to=b; ed[cnt].nx=head[a]; head[a]=cnt; } void tarjan(int u){ //u的时间戳 dfn[u]=low[u]=++timestamp; //把当前点加到栈中,当前点在栈中 stk[++top]=u,in_stk[u]=true; //从u开始搜索 for(int i=head[u];i!=-1;i=ed[i].nx){ int j=ed[i].to; if(!dfn[j]){//如果没有遍历过 tarjan(j);//继续搜索,遍历j //如果j存在反向边到达比u还高的层就更新最小的序 low[u]=min(low[u],low[j]); } //j点在栈中 说明还没出栈 是dfs序比当前点u小的 //则其 1要么是横插边(左边分支的点) // o // / \ // j ← u // 2要么是u的祖宗节点 // j // ↗/ // u // 两种情况u的dfs序都比j大 所以用dfn[j]更新low[u] else if(in_stk[j]){ low[u]=min(low[u],dfn[j]);//直接用j的时间戳更新u } //栈代表当前未被搜完的强连通分量的所有点 } // ? // 解释一下为什么tarjan完是逆dfs序 // 假设这里是最高的根节点fa // 上面几行中 fa的儿子节点j都已经在它们的递归中走完了下面9行代码 // 其中就包括 ++scc_cnt // 即递归回溯到高层节点的时候 子节点的scc都求完了 // 节点越高 scc_id越大 // 在我们后面想求链路dp的时候又得从更高层往下 // 所以得for(int i=scc_cnt(根节点所在的scc);i;i--)开始 // 所以当遍历完u的所有能到的点后 发现u最高能到的点是自己 // 1 则u为强连通分量中的最高点,则以u为起点往下把该强连通分量所有节点都找出来 // 2 要么它就没有环,就是一个正常的往下的点 if(dfn[u]==low[u]){ int y=0; ++scc_cnt;//强连通分量总数+1 do{ y=stk[top--];//取栈顶元素y in_stk[y]=false;//则y不再在栈中 id[y]=scc_cnt;//点y在此连通图 Size[scc_cnt]++;//第scc_cnt个连通块点数+1 }while(y!=u); //1 因为栈中越高的元素的dfs序越大,那么我们只需要把dfs序比u大的这些pop到u //即因为最终会从下至上回到u 所以当y==u //则说明点u所在的所有强连通分量都标记了id // → u // / / // / ne1 // ← ne2 // 因为ne2会在u能到的dfs序里最大的,也就是此时的栈顶 // 那么我们就逐一pop出ne2和ne1 //2 要么它就是一个没有环的点 则该点单点成一个连通分量 } } void solve() { cnt=0;top=0;timestamp=0;cin>>n>>m; for(int i=1;i<=n;i++) {head[i]=-1;dfn[i]=0;} while(m--){ int a,b;cin>>a>>b; add(a,b);//有向图 } //缩点,把所有强连通分量缩成一个点 for(int i=1;i<=n;i++){ if(!dfn[i]){ tarjan(i); } } //统计新图中点的出度 for(int i=1;i<=n;i++){//判断i到k是不是在一个分量中 for(int j=head[i];j!=-1;j=ed[j].nx){ int k=ed[j].to; int a=id[i],b=id[k]; //a,b不为一个连通分量 if(a!=b) dout[a]++; //a出度+1 dout[a] += i→k } } int zeros=0,sum=0; //sum 存的所有出度为0的强连通分量的点的数量 for(int i=1;i<=scc_cnt;i++){ if(!dout[i]){//如果第i个强连通分量出度==0 zeros++; sum+=Size[i]; //则加上第i个强连通分量的点的个数 //如果有k>1个出度为0的 则会存在k-1头牛不被所有牛欢迎 if(zeros>1){ sum=0;break; } } } cout<<sum; return ; } signed main() { ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); solve(); return 0; }
算法分析:先把有向图中强连通分量缩点,使得图变成了有向无环图。为了使一个点集内所有点能到达任意的点,所以我们要把入度为0的强连通分量都记录到这个点集中。如果点集中有多个强连通分量,则输出每个强连通分量的最小点。
注意点:要记录每个强连通分量的最小点可以用pre数组来记录,在tarjan的出栈时用
pre=min(pre,u);记录这个强连通分量的最小点
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
#define int long long
const int INF=999999;
const int N=50200;
int cnt,head[N],n,m,timestamp;
int dfn[N];
int low[N];
int stk[N],top,in_stk[N];//栈
int scc_cnt;//分量数
int Size[N];//强连通分量点的个数
int id[N];//强连通分量
int dout[N];//缩点后的出度
int din[N];//缩点后的入度
int pre[N];//缩点后,每个点集的最小点
struct edge
{
int to,nx;
};
edge ed[N];
void add(int a,int b)
{
ed[++cnt].to=b;
ed[cnt].nx=head[a];
head[a]=cnt;
}
void tarjan(int u){
dfn[u]=low[u]=++timestamp;
stk[++top]=u,in_stk[u]=true;
for(int i=head[u];i!=-1;i=ed[i].nx){
int j=ed[i].to;
if(!dfn[j]){
tarjan(j);
low[u]=min(low[u],low[j]);
}
else if(in_stk[j]){
low[u]=min(low[u],dfn[j]);
}
}
if(dfn[u]==low[u]){
int y=0;
++scc_cnt;//强连通分量总数+1
do{
y=stk[top--];//取栈顶元素y
in_stk[y]=false;//则y不再在栈中
id[y]=scc_cnt;//点y在此连通图
Size[scc_cnt]++;//第scc_cnt个连通块点数+1
pre[scc_cnt]=min(pre[scc_cnt],y);
}while(y!=u);
}
}
void solve()
{ cnt=0;top=0;timestamp=0;cin>>n>>m;
for(int i=1;i<=n;i++) {head[i]=-1;dfn[i]=0;pre[i]=INF;}
while(m--){
int a,b;cin>>a>>b;
add(a,b);//有向图
}
//缩点,把所有强连通分量缩成一个点
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i);
}
}
for(int i=1;i<=n;i++){
for(int j=head[i];j!=-1;j=ed[j].nx){
int k=ed[j].to;
int a=id[i],b=id[k];
if(a!=b) din[b]++;//该点入度
}
}
vector<int>w;
for(int i=1;i<=scc_cnt;i++){
if(!din[i]){//入度为0则为起点
w.push_back(pre[i]);
}
}
cout<<w.size()<<endl;
sort(w.begin(),w.end());
for(auto k:w){
cout<<k<<" ";
}
return ;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
solve();
return 0;
}
强连通分量+树形dp
a->b->c->d 如果使用D的话,那么C必须选择,那么b又必须选择,那么a又必须选择
具有传递性, 如果使用x那么x的父亲必须选择,父亲的父亲。
之后建新图
将图进行缩点后,是一个有向图,又因每一个节点最多有一个父亲,可以有多个儿子,
那么该图就是一个树,
确定源点, 将缩点后全部入度为0的源点,作为超级源点0的儿子。
最后从超级源点0开始树形dp
dp[ i ][ j ],,就是第i个节点,容量为j的时候,最大价值,
从尾节点更新
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N=50200;
int cnt,head[N],n,m,timestamp;
int dfn[N];//时间戳
int low[N];
int stk[N],top,in_stk[N];//栈
int scc_cnt;//分量数
int Size[N];//强连通分量点的个数
int id[N];//强连通分量
int dout[N];//缩点后的出度
int din[N];//缩点后的入度
int v[N],w[N];
int g[1010][1010];//缩点后新图关系
int dp[N][600];
int vv[N],ww[N];
struct edge
{
int to,nx;
};
edge ed[N];
void add(int a,int b)
{
ed[++cnt].to=b;
ed[cnt].nx=head[a];
head[a]=cnt;
}
void tarjan(int u){
dfn[u]=low[u]=++timestamp;
stk[++top]=u,in_stk[u]=true;
for(int i=head[u];i!=-1;i=ed[i].nx){
int j=ed[i].to;
if(!dfn[j]){
tarjan(j);
low[u]=min(low[u],low[j]);
}
else if(in_stk[j]){
low[u]=min(low[u],dfn[j]);
}
}
if(dfn[u]==low[u]){
int y=0;
++scc_cnt;//强连通分量总数+1
do{
y=stk[top--];//取栈顶元素y
vv[scc_cnt]+=v[y];
ww[scc_cnt]+=w[y];
in_stk[y]=false;//则y不再在栈中
id[y]=scc_cnt;//点y在此连通图
Size[scc_cnt]++;//第scc_cnt个连通块点数+1
}while(y!=u);
}
}
void dfs(int u){
for(int i=w[u];i<=m;i++)
dp[u][i]=v[u];
for(int i=head[u];i!=-1;i=ed[i].nx){
int j=ed[i].to;
dfs(j);
for(int k=m;k>=w[u];k--){
for(int l=0;l+w[u]<=k;l++){
dp[u][k]=max(dp[u][k],dp[j][l]+dp[u][k-l]);
}
}
}
}
void solve()
{ cnt=0;top=0;timestamp=0;
cin>>n>>m;
for(int i=0;i<=2*n;i++) {
head[i]=-1;dfn[i]=0;}
for(int i=1;i<=n;i++)
cin>>w[i];
for(int i=1;i<=n;i++)
cin>>v[i];
for(int i=1;i<=n;i++){
int b;cin>>b;
if(b==0) continue;
add(b,i);
}
//缩点,把所有强连通分量缩成一个点
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i);
}
}
//统计新图中点的出度
for(int i=1;i<=n;i++){//判断i到k是不是在一个分量中
for(int j=head[i];j!=-1;j=ed[j].nx){
int k=ed[j].to;
int a=id[i],b=id[k];
//a,b不为一个连通分量
if(a!=b) {dout[a]++;
g[a][b]=1;
din[b]++;}
//a出度+1 dout[a] += i→k
}
}
queue<int>q;
for(int i=1;i<=scc_cnt;i++) {
w[i+n]=ww[i];
v[i+n]=vv[i];
if(din[i]==0)//源点
q.push(i+n);}
for(int i=1;i<=scc_cnt;i++)
for(int j=1;j<=scc_cnt;j++){
if(g[i][j]==0) continue;
add(i+n,j+n);
}
//新图是一个树
while(q.size()){
int a=q.front();
q.pop();
add(0,a);//0是超级源点
}
dfs(0);//进行树形dp
cout<<dp[0][m];
return ;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
solve();
return 0;
}
题目:
题意翻译
很久以前有一个老国王,他有N个儿子。在他的王国里面,也有N个漂亮的女孩。国王知道他的每个孩子喜欢哪一个女孩。国王的孩子们很年轻,比较花心,所以一个孩子可能喜欢好几个女孩。
国王让他的巫师为他的每个儿子找到他最喜欢的女孩,这样这个儿子就可以娶这个女孩了。国王的巫师也做到了这点要求——他为每个儿子选择了他可以结婚的女孩,即这个儿子必须喜欢选中的女孩。当然了,每个儿子只能选择一个女孩。
然而,国王说:“我喜欢你做的清单,但是我还是有点不满意。对于我的每个儿子,我想知道可以和他结婚的所有女孩。当然了,这前提必须是所有的儿子都要找到一个女孩。”
国王希望巫师为他解决的问题对于巫师来说太难了。为了保全巫师的脑袋,请你写一个程序帮助巫师。
Input
输入的第一行包含N - 国王的儿子数(11 ≤≤ N ≤≤ 20002000)。 接下来每个国王的儿子的N行包含他喜欢的女孩的名单:第一个Ki - 他喜欢的女孩的数量,然后有Ki个不同的整数,从1到N表示女孩的编号。 所有Ki的总和不超过200000200000。
输入的最后一行是巫师所做的清单 - N个不同的整数:对于每个儿子,给出他将按照此列表结婚的女孩的编号。 保证这个清单是正确的,也就是说,每个儿子都喜欢这个清单里面他所要结婚的女孩。
Output
输出N行。对于每个国王的儿子,首先打印Li - 他喜欢和可以结婚的不同女孩的数量。 在那之后,输出Li个不同的整数表示那些女孩的编号,按升序排列。
强连通分量
王子喜欢的女孩,王子->女孩 ,连接一条边。
最后巫师所说的一条完备匹配图,则女孩->王子,连接一条边。
此时并非二分图,直接进行缩点,
每一个强连通分量,王子与女孩数一定相同,并且他们可以互相到达。
因此,此时强连通分量里面每一个女生都可以与任何一个男孩任意匹配。
最后遍历每一个王子后面的女孩,如果在同一个强连通分量,那么就可以任意匹配。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N=50200;
int cnt,head[N],n,m,timestamp;
int dfn[N];//时间戳
int low[N];
int stk[N],top,in_stk[N];//栈
int scc_cnt;//分量数
int Size[N];//强连通分量点的个数
int id[N];//强连通分量
int dout[N];//缩点后的出度
int din[N];//缩点后的入度
struct edge
{
int to,nx;
};
edge ed[N*1000];
void add(int a,int b)
{
ed[++cnt].to=b;
ed[cnt].nx=head[a];
head[a]=cnt;
}
void tarjan(int u){
dfn[u]=low[u]=++timestamp;
stk[++top]=u,in_stk[u]=true;
for(int i=head[u];i!=-1;i=ed[i].nx){
int j=ed[i].to;
if(!dfn[j]){
tarjan(j);
low[u]=min(low[u],low[j]);
}
else if(in_stk[j]){
low[u]=min(low[u],dfn[j]);
}
}
if(dfn[u]==low[u]){
int y=0;
++scc_cnt;//强连通分量总数+1
do{
y=stk[top--];//取栈顶元素y
in_stk[y]=false;//则y不再在栈中
id[y]=scc_cnt;//点y在此连通图
Size[scc_cnt]++;//第scc_cnt个连通块点数+1
}while(y!=u);
}
}
void solve()
{ cnt=0;top=0;timestamp=0;
cin>>n;
for(int i=1;i<=2*n;i++) {head[i]=-1;dfn[i]=0;}
for(int i=1;i<=n;i++){
int k,a;
cin>>k;
while(k--){
cin>>a;
add(i,a+n);
}
}
for(int i=1;i<=n;i++){
int a;cin>>a;
add(a+n,i);
}
//缩点,把所有强连通分量缩成一个点
for(int i=1;i<=2*n;i++){
if(!dfn[i]){
tarjan(i);
}
}
for(int i=1;i<=n;i++){
vector<int>w;
for(int j=head[i];j!=-1;j=ed[j].nx){
int k=ed[j].to;
int a=id[i],b=id[k];
if(a==b){
w.push_back(k-n);
}
}
sort(w.begin(),w.end());
cout<<w.size()<<" ";
for(auto l : w)
cout<<l<<" ";
cout<<endl;
}
return ;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
solve();
return 0;
}