[NOIp复习计划]:图的连通

本文探讨了在处理有环图问题时采用的Tarjan缩点算法及其在不同场景下的应用,包括软件安装、杀人游戏等题目中的树形DP、最短路径优化等技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[bzoj 2427][HAOI2010]软件安装
考虑到有环,就tarjan一发,把价值和空间合并一下,然后会发现,缩点之后是个森林,就可以跑树形dp了
f[i][j]
f[i][j]=f[to[i]][k]+f[i][jk]
注意边界,然后就RE到死了 a了。
对了vector.size()返回的不是int,而是size_type,它被定义成unsigned,所以 < size(),改写成< =size()-1,就有可能出事。。。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 120;
const int MAXM = 560;
int f[MAXN][MAXM],n,m,w[MAXN],v[MAXN];
#define rep(i,a,b) for(i=a;i<=b;i++)
#define dep(i,a,b) for(i=a;i>=b;i--)
typedef vector<int> P;
vector<P> mp;
vector<P> mp2;
int cnt,clo,dfn[MAXN],low[MAXN],belong[MAXN],top,sta[MAXN];
bool ins[MAXN];
int v2[MAXN],w2[MAXN],in[MAXN];
void dfs(int x){
    int i;
    dfn[x]=low[x]=++clo;
    sta[++top]=x;ins[x]=1;
    rep(i,0,((int)mp[x].size()-1)){
        if(!dfn[mp[x][i]]){
            dfs(mp[x][i]);
            low[x]=min(low[x],low[mp[x][i]]);
        }else if(ins[mp[x][i]]){
            low[x]=min(low[x],dfn[mp[x][i]]);
        }
    }
    if(dfn[x]==low[x]){
        cnt++;
        while(top!=0&&sta[top]!=x){
            belong[sta[top]]=cnt;
            ins[sta[top--]]=0;
        }
        belong[sta[top]]=cnt;
        ins[sta[top--]]=0;
    }
}
void dp(int x){
    int i,j,k;
    int len=(int)mp2[x].size()-1;
    rep(i,0,len){
        if(mp2[x][i]==x)continue;
        dp(mp2[x][i]);
        dep(j,m-w2[x],0){
            rep(k,0,j){
                    f[x][j]=max(f[x][j],f[mp2[x][i]][k]+f[x][j-k]);
            }
        }
    }
    dep(i,m,0){
        if(i>=w2[x]){
            f[x][i]=f[x][i-w2[i]]+v2[x];
        }else{
            f[x][i]=0;
        }
    }
}
int main(){
    int i,j;
    scanf("%d%d",&n,&m);
    mp=vector<P>(n+5);
    rep(i,1,n)scanf("%d",&w[i]);
    rep(i,1,n)scanf("%d",&v[i]);
    rep(i,1,n){
        int a;scanf("%d",&a);
        mp[a].push_back(i);
    }
    rep(i,0,n)if(!dfn[i])dfs(i);
    mp2=vector<P>(cnt+5);
    rep(i,0,n){
        rep(j,0,(int)mp[i].size()-1){
            if(belong[i]==belong[mp[i][j]])continue;
            mp2[belong[i]].push_back(belong[mp[i][j]]);
            in[belong[mp[i][j]]]++;
        }
        v2[belong[i]]+=v[i];
        w2[belong[i]]+=w[i];
    }
    rep(i,1,cnt){if(!in[i]){mp2[0].push_back(i);}};
    dp(0);
    printf("%d",f[0][m]);
    return 0;
} 

[bzoj 2438][中山市选2011]杀人游戏
对于一个环来说,就询问一个人就够了,因为剩下的都能由这个人直接或间接的推出来。
啥都别说,先缩点,然后就变成了DAG,显然对于缩点后入度为0的点我们都要询问一次。
然而这并不对,这就是这题的坑了,对于一个独立或是所连的点的入度大于1的点,我们不需要对它询问,因为它可以由其他n-1个点推出来,但是一张图里最多就这一个点,特判一下就好了。
答案的计算就是最朴素的计算方式,需要询问的点/总点数

#include<bits/stdc++.h>
using namespace std;
const int maxn = 100505;
const int maxm = 500505;
#define rep(i,a,b) for(i=a;i<=b;i++)
#define dep(i,a,b) for(i=a;i>=b;i--)
int h[maxn],to[maxm],nx[maxm],tot;
int n,m;
int dfn[maxn],low[maxn],ti,sta[maxn],cnt,top,size[maxn],in[maxn],belong[maxn];
void add_edge(int u,int v){
    to[++tot]=v;nx[tot]=h[u];h[u]=tot;
}
bool ins[maxn];
void dfs(int x){
    dfn[x]=low[x]=++ti;
    sta[++top]=x;ins[x]=1;
    for(int i=h[x];i;i=nx[i]){
        if(!dfn[to[i]]){
            dfs(to[i]);
            low[x]=min(low[x],low[to[i]]);
        }else if(ins[to[i]]){
            low[x]=min(low[x],dfn[to[i]]);
        }   
    }
    if(dfn[x]==low[x]){
        int now=0;cnt++;
        while(x!=now){
            now=sta[top];top--;
            belong[now]=cnt;
            ins[now]=0;
            size[belong[now]]++;
        }
    }
}
void build(){
    int i,j;
    rep(i,1,n){
        for(j=h[i];j;j=nx[j]){
            if(belong[i]!=belong[to[j]]){
                in[belong[to[j]]]++;

            }
        }
    }
}
int main(){
    int i,a,b,ans=0;
    bool flag=0;
    scanf("%d%d",&n,&m);
    rep(i,1,m){
        scanf("%d%d",&a,&b);
        add_edge(a,b);
    }
    rep(i,1,n)if(!dfn[i])dfs(i);;
    build();
    rep(i,1,cnt)if(in[i]==0)ans++;
    rep(i,1,n){
        if(size[belong[i]]==1&&in[belong[i]]==0){
            bool flag2=1;
            for(int j=h[i];j;j=nx[j]){
                if(in[belong[to[j]]]==1){
                    flag2=0;
                    break;
                }
            }
            if(flag2)flag=1;
        }
    }
    if(flag)ans--;
    printf("%.6lf",(double)(n-ans)/(double)n);
    return 0;
}

[bzoj 2730][HNOI2012]矿场搭建
破坏一个点,我们就考虑破坏割点的时候,如果一张图没有割点,我们就需要两个点,保证答案合法
而对于其他因为割点分成的连通块只需要选一个就好了,剩下的就是乘法原理乱搞。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 10005
using namespace std;

int ri,n,m,cnt,now,all,tot,dfsclk,fst[N],pnt[N],nxt[N],pos[N],low[N],mrk[N],ans1;
bool cut[N]; long long ans2;
void add(int x,int y){
    pnt[++tot]=y; nxt[tot]=fst[x]; fst[x]=tot;
}
void dfs(int x,int fa){
    int p,sz=0; pos[x]=low[x]=++dfsclk;
    for (p=fst[x]; p; p=nxt[p]){
        int y=pnt[p]; if (y==fa) continue;
        if (!pos[y]){ sz++; dfs(y,x); }
        low[x]=min(low[x],low[y]);
        if (low[y]>=pos[x] && fa) cut[x]=1;
    }
    if (!fa && sz>1) cut[x]=1; if (cut[x]) cnt++;
}
void calc(int x){
    mrk[x]=now; all++; int p;
    for (p=fst[x]; p; p=nxt[p]){
        int y=pnt[p];
        if (cut[y] && mrk[y]!=now){ mrk[y]=now; cnt++; }
        if (!mrk[y]) calc(y);
    }
}
int main(){
    while (~scanf("%d",&m) && m){
        int i,x,y; tot=n=0; memset(fst,0,sizeof(fst));
        for (i=1; i<=m; i++){
            scanf("%d%d",&x,&y);
            n=max(n,max(x,y)); add(x,y); add(y,x);
        }
        memset(pos,0,sizeof(pos)); memset(low,0,sizeof(low));
        dfsclk=cnt=0; memset(cut,0,sizeof(cut));
        for (i=1; i<=n; i++)
            if (!pos[i]) dfs(i,0);
        if (!cnt){ ans1=2; ans2=(long long)n*(n-1)>>1; } else{
            ans1=0; ans2=1; memset(mrk,0,sizeof(mrk));
            for (i=1; i<=n; i++) if (!cut[i] && !mrk[i]){
                cnt=all=0; now++; calc(i);
                if (cnt==1){ ans1++; ans2*=all; }
            }
        }
        printf("Case %d: %d %lld\n",++ri,ans1,ans2);
    }
}

[bzoj 3391][Usaco2004 Dec]Tree Cutting网络破坏
太水了,随便dfs一下就完了
懒癌发作,复制一篇0_0

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define N 10005
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct Node{
    int to,next;
}e[2*N];
int head[N],n,num[N],tot=0;
bool vis[N];
void add(int u,int v)
{
    e[++tot]=(Node){v,head[u]};head[u]=tot;
}
void dfs(int rt)
{
    num[rt]=1;vis[rt]=1;
    for(int i=head[rt];i;i=e[i].next)
    {
        if(!vis[e[i].to])
        {
            dfs(e[i].to);
            num[rt]+=num[e[i].to];
        }
        else e[i].to=-1;
    }
}
int main()
{
    n=read();int u,v;
    for(int i=1;i<n;i++)
    {
        u=read();v=read();
        add(u,v);add(v,u);
    }
    dfs(1);
    double mid=n/2;
    for(int i=1;i<=n;i++)
    {
        bool ok=true;
        for(int j=head[i];j;j=e[j].next)
          if(e[j].to!=-1&&num[e[j].to]>mid){ok=false;break;}
        if(n-num[i]>mid)ok=false;
        if(ok==false)continue;
        printf("%d\n",i);
    }
    return 0;
}

[bzoj 2208] [Jsoi2010]连通数
有环很烦的,于是先缩点,然后重新建图,这个时候就是DAG了,于是我们就可以dp了,这里我用了bitset优化,我以为我这么卡常一定垫底了,结果我看看rk6一脸懵逼,然后我才发现他们都是传递闭包做的……

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2300;
int n,h[maxn],to[maxn*maxn],nx[maxn*maxn],tot;
bitset<2005>vis[maxn];
int bl[maxn],cnt;
int h2[maxn],to2[maxn*maxn],nx2[maxn*maxn],tot2;
char str[3000];
int dfn[maxn],low[maxn],sta[maxn],ti,top;
bool ins[maxn];
bool has_solved[maxn];
void add_edge(int u,int v){
    to[++tot]=v;nx[tot]=h[u];h[u]=tot;
}
void add_edge2(int u,int v){
    to2[++tot2]=v;nx2[tot2]=h2[u];h2[u]=tot2;
}
void dfs(int x){
    dfn[x]=low[x]=++ti;
    sta[++top]=x;ins[x]=1;
    for(int i=h[x];i;i=nx[i]){
        if(!dfn[to[i]]){
            dfs(to[i]);
            low[x]=min(low[x],low[to[i]]);
        }else if(ins[to[i]]){
            low[x]=min(low[x],dfn[to[i]]);
        }
    }
    if(dfn[x]==low[x]){
        int now=0;cnt++;
        while(now!=x){
            now=sta[top--];
            bl[now]=cnt;
            ins[now]=0;
            vis[cnt][now]=1;
        }
    }
}
void build(){
    for(int i=1;i<=n;i++){
        for(int j=h[i];j;j=nx[j]){
            if(bl[i]!=bl[to[j]]){
                add_edge2(bl[i],bl[to[j]]);
            }
        }
    }
}
void dp(int x){
    if(has_solved[x]==1)return;
    has_solved[x]=1;
    for(int i=h2[x];i;i=nx2[i]){
        dp(to2[i]);
        vis[x] |= vis[to2[i]];  
    }
}
int getans(){
    int ans=0;
    for(int i=1;i<=n;i++){
        ans += vis[bl[i]].count();
    }   
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",str+1);
        int j=1;
        while(str[j]!=0){
            if(str[j]=='1'){
                add_edge(i,j);
            }
            j++;
        }
    }
    for(int i=1;i<=n;i++)if(!dfn[i])dfs(i);
    build();
    for(int i=1;i<=cnt;i++)if(!has_solved[i])dp(i);
    printf("%d",getans());
    return 0;
}

[bzoj 1123][POI2008]BLO
答案是有向的数对,以及算了毁掉的点,参见discuss
我们考虑有向图的dfs树是没有交叉边的(连接到别的树上的边)这样的话如果该点是割点,那么分割的点对,是可以用dfs求的,就是每个点维护一个size,而对于非割点的点,答案一定是一样的

#include<bits/stdc++.h>
using namespace std;
const int maxn = 100050;
const int maxm = 500050;
int n,m;
int h[maxn],to[maxm<<1],nx[maxm<<1],tot;
int low[maxn],dfn[maxn],ti;
int sz[maxn];
bool vis[maxn];
typedef long long ll;
ll ans[maxn];
void ins(int u,int v){
    to[++tot]=v;nx[tot]=h[u];h[u]=tot;
    to[++tot]=u;nx[tot]=h[v];h[v]=tot;
}
void dfs(int x){
    ll sum=0; 
    vis[x]=1;sz[x]=1;
    low[x]=dfn[x]=++ti;
    for(int i=h[x];i;i=nx[i]){
        if(!dfn[to[i]]){
            dfs(to[i]);
            sz[x]+=sz[to[i]];
            low[x]=min(low[x],low[to[i]]);
            if(dfn[x]<=low[to[i]]){
                ans[x]+=sum*sz[to[i]];
                sum+=sz[to[i]];
            }
        }else if(vis[to[i]]){
            low[x]=min(low[x],dfn[to[i]]);
        }
    }
    ans[x]+=sum*(n-sum-1);
}
int main(){
    scanf("%d%d",&n,&m);
    int a,b;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&a,&b);
        ins(a,b);
    }
    dfs(1);
    for(int i=1;i<=n;i++)
        printf("%lld\n",(ans[i]+n-1)*2);
    return 0;
} 


[codevs 2822] 爱在心中
缩点之后是DAG各种O(n)的性质乱搞就行了,其实暴力也可以。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2000;
int n,m,mp[maxn][maxn];
int mp2[maxn][maxn],bl[maxn],cnt,ti;
int low[maxn],dfn[maxn];
int pre[maxn];
int sta[maxn],top,sz[maxn];
bool ins[maxn];
void dfs(int x){
    sta[++top]=x;
    ins[x]=1;
    low[x]=dfn[x]=++ti;
    for(int i=1;i<=n;i++){
        if(!mp[x][i])continue;
        if(!dfn[i]){
            dfs(i);
            low[x]=min(low[x],low[i]);
        }else if(ins[i]){
            low[x]=min(low[x],dfn[i]);
        }
    }
    if(dfn[x]==low[x]){
        int now=0;cnt++;
        while(now!=x){
            now=sta[top--];ins[now]=0;
            bl[now]=cnt;sz[cnt]++;
        }
    }
}
int in[maxn];
void build(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(mp[i][j] && bl[i]!=bl[j])
                mp2[bl[i]][bl[j]]=1,in[bl[j]]++;
}
void solve(int x,int va){
    for(int i=1;i<=cnt;i++){
        if(mp2[x][i]){
            sz[i]+=va;
            solve(i,va);
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        mp[a][b]=1;
    }
    for(int i=1;i<=n;i++)if(!dfn[i])dfs(i);
    build();
    int ans=0;
    for(int i=1;i<=cnt;i++)pre[i]=sz[i];
    for(int i=1;i<=cnt;i++)if(sz[i]>1)ans++;
    printf("%d\n",ans);
    for(int i=1;i<=cnt;i++)solve(i,pre[i]);
    int id=0;
    for(int i=1;i<=cnt;i++)if(sz[i]>=n && pre[i]>1)id=i;
    for(int i=1;i<=n;i++)
        if(bl[i]==id){
            printf("%d ",i);
        }
    if(id==0)printf("-1");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值