一本通:
提高篇:
圖論:
強聯通分量:
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;
}