一本通:
提高篇:
图论:
强联通分量:
1513:【 例 1】受欢迎的牛
受欢迎的奶牛只有可能是图中唯一的出度为零的强连通分量中的所有奶牛,所以若出现两个以上出度为0的强连通分量则不存在明星奶牛,因为那几个出度为零的分量的爱慕无法传递出去。那唯一的分量能受到其他分量的爱慕同时在分量内相互传递,所以该分量中的所有奶牛都是明星。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+7,M = 5e4+7;
int head[M],ver[M],nex[M];
int dfn[N],low[N],ins[N],all[N],id[N],du[N];
int col=0,n,m,cnt,dfstime;
stack<int> st;
void add(int x,int y){
ver[++cnt] = y;
nex[cnt] = head[x];
head[x] = cnt;
}
void read(){
scanf("%d%d",&n,&m);
for(int i=1,x,y;i<=m;i++){
scanf("%d%d",&x,&y);
add(x,y);
}
}
void tarjan(int x){
dfn[x] = low[x] = ++dfstime;
st.push(x); ins[x] = 1;
for(int i=head[x];i;i=nex[i]){
int y =ver[i];
if(!dfn[y]){
tarjan(y);
low[x] = min(low[x],low[y]);
}else
low[x] = min(low[x],dfn[y]);
}
int i;
if(low[x] == dfn[x]){
++col;
do{
i = st.top();st.pop();
id[i] = col;
all[col]++;
}while(i!=x);
}
}
void solve(){
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;j=nex[j]){
int y = ver[j];
if(id[i] != id[y])
du[id[i]]++;
}
int ans = 0;
for(int i=1;i <= col;i++){
if(!du[i]){
if(ans){
puts("0");
return;
}
ans = i;
}
}
printf("%d\n",all[ans]);
}
int main(){
read();
solve();
return 0;
}
1514:【例 2】最大半连通子图
先缩点,再建图,建图的时候去重边,我这里是用map去重。最后再用拓扑排序找出最长链。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+7,M = 2e6+7,INF = 0x3f3f3f3f;
int n,m,mod,scc;
int head[N],ver[M],nex[M];
int low[N],dfn[N],id[N],all[N];//id是用来记录第i个点的颜色,也可以用作判断是否在栈中
int stk[N],tp;
int dis[N],e[N],du[N];//e[i]表示在最长的情况下有多少条路能到i点,dis[i]是到i点的最长链
void read(){
scanf("%d%d%d",&n,&m,&mod);
for(int i=1,u,v;i <= m;i++){
scanf("%d%d",&u,&v);
ver[i] = v,nex[i] = head[u],head[u] = i;
}
}
void tarjan(int u,int &clk){
low[u] = dfn[u] = ++clk;
stk[++tp] = u;
for(int i=head[u];i;i = nex[i]){
int v = ver[i];
if(!dfn[v]) tarjan(v,clk), low[u] = min(low[u],low[v]);
else if(!id[v]) low[u] = min(low[u],dfn[v]);//判断是否在栈中
}
if(low[u] == dfn[u])
for(++scc;stk[tp+1] != u;tp--)
id[stk[tp]] = scc, ++all[scc];
}
unordered_map<int,int> mp[N];
int head2[N],ver2[M],nex2[M];
void rbuild(){
int cnt = 0;
for(int i=1;i<=n;i++){
int x=id[i];
for(int j=head[i];j;j=nex[j]){
int y=id[ver[j]];
if(x!=y&&!mp[x].count(y))
mp[x][y]=1,du[y]++,ver2[++cnt] = y,nex2[cnt] = head2[x],head2[x] = cnt;
}
}
}
queue<int> q;
void topsort(){
for(int i=1;i <= scc;i++)
if(!du[i]) q.push(i),e[i] = 1;
while(q.size()){
int u = q.front();q.pop();
dis[u] += all[u];
for(int i=head2[u];i;i=nex2[i]){
int v = ver2[i];
if(dis[v] < dis[u] ){
dis[v] = dis[u] ;
e[v] = e[u];
}else if(dis[v] == dis[u]) e[v] = (e[u] + e[v]) % mod;
if(!--du[v]) q.push(v);
}
}
}
int ans,sum; //ans保存最长链的编号,sum表示最长的个数
void solve(){
for(int i=1;i<=n;i++)
if(dis[i]>ans) ans=dis[i],sum=e[i];
else if(dis[i]==ans) sum=(sum+e[i])%mod;
printf("%d\n%d",ans,sum);
}
int main(){
read();
for(int i=1,clk=0;i <= n;i++)
if(!dfn[i]) tarjan(i,clk);
rbuild();
topsort();
solve();
return 0;
}
1515:网络协议
缩点,不需要建新图,直接在缩点后找到出度为0和入度为0的点,特判缩点后只有一个点的情况,任务一的解就是入度为0的点,任务二的解是选择出度和入度中较大的点数,如果不是较大的点数,那么会出现仍然有点未被链接的情况。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2+7,M = 1e4+7;
int n;
int cnt=1,head[N],nex[M],ver[M];
int low[N],dfn[N],all[N],id[N],scc;
int stk[N],tp;
int out[N],in[N];
void add(int u,int v){
ver[cnt] = v;
nex[cnt] = head[u];
head[u] = cnt++;
}
void read(){
scanf("%d",&n);
for(int i=1,x; i <= n;i++){
while(scanf("%d",&x) && x)
add(i,x);
}
}
void tarjan(int u,int &clk){
low[u] = dfn[u] = ++clk;
stk[++tp] = u;
for(int i=head[u];i;i=nex[i]){
int v = ver[i];
if(!dfn[v]) tarjan(v,clk),low[u] = min(low[u],low[v]);
else if(!id[v]) low[u] = min(low[u],dfn[v]);
}
if(low[u] == dfn[u])
for(++scc;stk[tp+1] != u;tp--)
id[stk[tp]] = scc,++all[scc];
}
int ansa,ansb;
int main(){
read();
for(int i=1,clk=0; i <= n;i++)
if(!dfn[i]) tarjan(i,clk);
for(int i=1;i <= n;i++){
for(int j=head[i];j;j=nex[j]){
int v = ver[j];
if(id[i] != id[v])
++out[id[i]],++in[id[v]];
}
}
for(int i=1;i <= scc;i++){
if(!in[i]) ansa++;
if(!out[i]) ansb++;
}
if(scc==1) printf("1\n0\n");
else printf("%d\n%d",ansa,max(ansa,ansb));
return 0;
}
1516:消息的传递
裸题,直接缩点后找入度为0的点,说明可以他不可以通过别的点来传递。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+7;
int g[N][N];
int n;
int low[N],dfn[N],scc,id[N],all[N];
int stk[N],tp;
int in[N];
void read(){
scanf("%d",&n);
for(int i=1; i <= n;i++)
for(int j=1; j <= n;j++)
scanf("%d",&g[i][j]);
}
void tarjan(int u,int &clk){
low[u] = dfn[u] = ++clk;
stk[++tp] = u;
for(int i=1; i <= n;i++){
if(g[u][i]){
if(!dfn[i]) tarjan(i,clk),low[u]=min(low[i],low[u]);
else if(!id[i]) low[u] = min(low[u],dfn[i]);
}
}
if(low[u] == dfn[u])
for(++scc;stk[tp+1] != u;tp--)
id[stk[tp]] = scc,++all[scc];
}
int main(){
read();
for(int i=1,clk=0; i <= n;i++)
if(!dfn[i]) tarjan(i,clk);
for(int i=1;i <= n;i++){
for(int j=1;j <= n;j++){
if(g[i][j])
if(id[i] != id[j]) ++in[id[j]];
}
}
int ans = 0;
for(int i=1;i <= scc;i++)
if(!in[i]) ans++;
printf("%d",ans);
return 0;
}
1517:间谍网络
缩点,缩点后的权值是在这个连通块中最小的点的权值,在找入度为0的点,加上此点所在的连通块的权值,单独for循环判断是否有点未被链接上。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+4,INF = 0x3f3f3f3f;
int n,p,r;
int ver[N],head[N],nex[N];
int w[N],blk[N],in[N];
int dfn[N],low[N],id[N],all[N],scc;
int stk[N],tp;
void read(){
scanf("%d%d",&n,&p);
for(int i=1; i <= n;i++) w[i] = blk[i] = INF;
for(int i=1,jx,fy; i <= p;i++) scanf("%d%d",&jx,&fy),w[jx]=fy;
scanf("%d",&r);
for(int i=1,u,v; i <= r;i++){
scanf("%d%d",&u,&v);
ver[i] = v,nex[i] = head[u],head[u] = i;
}
}
void tarjan(int u,int &clk){
dfn[u] = low[u] = ++clk;
stk[++tp] = u;
for(int i = head[u];i;i=nex[i]){
int v = ver[i];
if(!dfn[v]) tarjan(v,clk),low[u] = min(low[u],low[v]);
else if(!id[v]) low[u] = min (low[u],dfn[v]);
}
if(low[u] == dfn[u]){
for(++scc;stk[tp+1]!=u;tp--){
id[stk[tp]] = scc,++all[scc];
if(blk[scc] > w[stk[tp]]) blk[scc] = w[stk[tp]];
}
}
}
int main(){
read();
for(int i = 1,clk=0; i <= n;i++)
if(!dfn[i] && w[i] != INF) tarjan(i,clk);
for(int i=1;i <= n;i++)
if(!dfn[i]){
printf("NO\n%d",i);
return 0;
}
for(int i=1;i <= n;i++){
for(int j=head[i];j;j=nex[j]){
int v=ver[j];
if(id[i] != id[v]) ++in[id[v]];
}
}
int ans = 0;
for(int i=1; i<= scc;i++){
if(!in[i]) ans += blk[i];
}
printf("YES\n%d",ans);
return 0;
}
1518:抢掠计划
kun哥写的代码,真的强,学习一下建图缩点方式。
和b题差不多,把拓扑变找最长链变成spfa
#include <cstdio>
#include <queue>
#include <algorithm>
const int N = 5e6 + 7;
int hd[N], to[N], fr[N], nx[N], stk[N], tp, scc;
int low[N], dfn[N], id[N], sum[N], w[N], ans;
bool bar[N], inq[N], stop[N];
std::pair<int, int> eg[N];
void Tarjan(int u, int &clk) {
low[u] = dfn[u] = ++clk;
stk[++tp] = u;
for (int i = hd[u], v; i; i = nx[i])
if (!dfn[v=to[i]])
Tarjan(v, clk), low[u] = std::min(low[u], low[v]);
else if (!id[v])
low[u] = std::min(low[u], dfn[v]);
if (low[u] == dfn[u])
for (++scc; stk[tp+1] != u; --tp)
id[stk[tp]] = scc, sum[scc] += w[stk[tp]], stop[scc] |= bar[stk[tp]];
}
int dis[N];
void spfa(int s) {
std::queue<int> que;
que.push(s), ans = dis[s] = sum[s];
while (que.size()) {
int u = que.front(); que.pop();
inq[u] = false;
for (int i = hd[u]; i; i = nx[i]) {
int v = to[i], tot = dis[u] + sum[v];
if (dis[v] < tot) {
dis[v] = tot;
if (stop[v]) ans = std::max(ans, dis[v]);
if (not inq[v]) que.push(v);
}
}
}
}
int main() {
int n, m, s, p;
scanf("%d%d", &n, &m);
for (int i = 1, u, v; i <= m; ++i) {
scanf("%d%d", &u, &v);
nx[i] = hd[u], hd[u] = i, to[i] = v, fr[i] = u;
}
for (int i = 1; i <= n; ++i) scanf("%d", w+i);
scanf("%d%d", &s, &p);
for (int i = 1, x; i <= p; ++i)
scanf("%d", &x), bar[x] = true;
for (int i = 1, clk = 0; i <= n; ++i) if (!dfn[i]) Tarjan(i, clk);
//缩点建新图
for (int i = 1; i <= m; ++i)
eg[i].first = id[fr[i]], eg[i].second = id[to[i]];
std::sort(eg+1, eg+m+1);
for (int i = 1; i <= scc; ++i) hd[i] = 0;
for (int i = 1; i <= m; ++i) {
int u = eg[i].first, v = eg[i].second;
int uu = eg[i-1].first, vv = eg[i-1].second;
if (u!=v && (u!=uu || v!=vv))
nx[i] = hd[u], hd[u] = i, to[i] = v;
}
//在新图上直接跑spfa
spfa(id[s]);
printf("%d\n", ans);
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5+7,M=2e5+7;
int n,m;
int cnt,head[N],ver[M],nex[M];
int low[N],dfn[N],all[N],id[N],scc;
int stk[N],tp;
void add(int u,int v){
ver[++cnt] = v;
nex[cnt] = head[u];
head[u] = cnt;
}
void read(){
scanf("%d%d",&n,&m);
for(int i=1;i <= m;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b&1?b+1:b-1);
add(b,a&1?a+1:a-1);
}
}
void tarjan(int u,int &clk){
low[u] = dfn[u] = ++clk;
stk[++tp] = u;
for(int i=head[u];i;i = nex[i]){
int v = ver[i];
if(!dfn[v]) tarjan(v,clk), low[u] = min(low[u],low[v]);
else if(!id[v]) low[u] = min(low[u],dfn[v]);//判断是否在栈中
}
if(low[u] == dfn[u])
for(++scc;stk[tp+1] != u;tp--)
id[stk[tp]] = scc, ++all[scc];
}
bool t_sat(){
for(int i=1,clk=0;i <= 2*n;i++)
if(!dfn[i]) tarjan(i,clk);
for(int i=1;i <= n*2;i+=2)
if(id[i]==id[i+1]) return false;
return true;
}
int main(){
read();
if(t_sat()){
for(int i=1;i <=2*n;i+=2)
printf("%d\n",id[i]<id[i+1]?i:i+1);
}else printf("NIE");
return 0;
}