拓扑排序题集

HDU - 5438

题意:有n个池塘和m个管道;每个池塘的价值是v, 现在由于资金问题要删除池塘;但是删除的池塘必须是最多只连接一个管道,否则会爆炸;管子会因为池塘的删除而消失

求最后相连的池塘有奇数个的价值总和是多少

解析:

建立无向图

先用拓扑排序删除入度为1的所以池塘并标记,然后再用深搜判断是否是奇数连通

ac:

#include<bits/stdc++.h>
#define ll long long
#define MAXN 100005
using namespace std;
int w[MAXN],in[MAXN];
int to[MAXN<<2],next1[MAXN<<2],head[MAXN<<2];
int tot,n,m;
int vis[MAXN];
void init()
{
    tot=0;
    for(int i=0;i<MAXN;i++)
        in[i]=vis[i]=head[i]=next1[i]=0;
}

void add(int u,int v)
{
    to[++tot]=v;
    next1[tot]=head[u];
    head[u]=tot;
}

void topo()
{
    queue<int> que;
    for(int i=1;i<=n;i++)
        if(in[i]==1)
            que.push(i),vis[i]=1;
    while(que.size()!=0)
    {
        int u=que.front();
        in[u]--;
        que.pop();
        for(int i=head[u];i;i=next1[i])
        {
            int v=to[i];
            if(vis[v]==1)
                continue;
            in[v]--;
            if(in[v]==1)
                que.push(v),vis[v]=1;
        }
    }
}
ll ans=0,num=0,sum=0;

void dfs(int x)
{
    num++;
    sum+=w[x];
    vis[x]=1;
    for(int i=head[x];i;i=next1[i])
    {
        int v=to[i];
        if(vis[v]==1)
            continue;
        dfs(v);
    }
}

int main()
{
    int t,a,b;
    scanf("%d",&t);
    while(t--)
    {
        init();//ÖØÖÃ
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&w[i]);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&a,&b);
            add(a,b);
            add(b,a);
            in[a]++;
            in[b]++;
        }
        topo();
        ans=0;
        for(int i=1;i<=n;i++)
        {
            if(in[i]>=2&&vis[i]==0)
            {
                num=0;//ÅжÏÆæż
                sum=0;//ÀÛÊý
                dfs(i);
                ans+=sum*(num%2);
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

D. Gourmet choice

链接:D - Gourmet choice

题意:

给两个数组,长度分别为n,m.给两个数组的关系

构造出最小的两个数组,如果无法构造,输出NO,否则YES,输出两个数组

解析:

并查集(缩点)+拓扑排序

首先要判断是否有环,有环NO

对与"=",我们把它合成一个点,用并查集

用拓扑排序判断是否有环和构造数组

ac:
 

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
#define IO ios_base::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
#define ll long long
#define MAXN 5005
using namespace std;

char str[1010][1010]={0};
int f[MAXN];
int in[MAXN]={0};
vector<int> mp[MAXN];
int reward[MAXN]={0};
int n,m;

void init(){
    rep(i,1,MAXN)
        f[i]=i;
}
int find(int x){
    return x==f[x]?x:f[x]=find(f[x]);
}
void unite(ll a,ll b){
    ll x=find(a),y=find(b);f[x]=y;
}

void topo()
{
    queue<int> que;
    for(int i=1;i<=n+m;i++)
        if(in[i]==0)
        {
            reward[i]=1;
            que.push(i);
        }
    int count=0;
    while(!que.empty())
    {
        int top=que.front();
        que.pop();
        count++;
        for(int i=0;i<mp[top].size();i++)
        {
            in[mp[top][i]]--;
            if(in[mp[top][i]]==0)
            {
                que.push(mp[top][i]);
                reward[mp[top][i]]=max(reward[mp[top][i]],reward[top]+1);
            }
        }
    }
    if(count!=n+m)//判环
        cout<<"NO"<<endl;
    else{
        cout<<"YES"<<endl;
        for(int i=1;i<=n;i++)
            cout<<reward[find(i)]<<(n-i?' ':'\n');
        for(int i=1;i<=m;i++)
            cout<<reward[find(n+i)]<<(n-i?' ':'\n');
    }
}

int main()
{
    init();
    scanf("%d%d",&n,&m);
     rep(i,1,n)
        scanf("%s",str[i]+1);
     rep(i,1,n)
        rep(j,1,m)
            if(str[i][j]=='=')
                unite(i,j+n);//合点

    rep(i,1,n)
        rep(j,1,m)
        {
            if(str[i][j]=='=')
                continue;
            if(str[i][j]=='>')
            {
                mp[find(n+j)].push_back(find(i));
                in[find(i)]++;
            }
            else {
                mp[find(i)].push_back(find(n+j));
                in[find(n+j)]++;
            }
        }
    topo();
    return 0;
}

https://ac.nowcoder.com/acm/contest/885/H

题意:

有一个字符串,把所以字符的关系给你,让你输出这个字符,如果不存在这个字符,输出-1

解析:

这里每种字母最多10000个,我们直接把不同的字母构成分别村好,每种字母前后差10000

然后就是常规操作

ac:

#include<bits/stdc++.h>
#define MAXN 300005
using namespace std;
char s[MAXN]={0};
int in[MAXN]={0};
int vis[MAXN]={0};
int c[MAXN]={0};
int num[30];//26个字母的数目
vector<int> vc[MAXN];//记录边
int ans[MAXN]={0};

void topo(int n)
{
    priority_queue<int> que;
    int cnt=0;
    for(int i=0;i<26;i++)
    {
        for(int j=1;j<=num[i];j++)
            if(in[i*10000+j]==0)
                que.push(i*10000+j),vis[i*10000+j]=1;
        cnt+=num[i];
    }
    int num=0;
    while(que.size())
    {
        int u=que.top();
        ans[++num]=u;
        que.pop();
        for(int i=0;i<vc[u].size();i++)
        {
            int v=vc[u][i];
            in[v]--;
            if(in[v]==0&&vis[v]==0)
                que.push(v),vis[v]=1;
        }
    }
    if(num!=n||cnt!=n||cnt!=num)
        printf("-1\n");
    else{
        for(int i=1;i<=n;i++)
            printf("%c",ans[i]/10000+'a');
        printf("\n");
    }
}

int main()
{
    char pp[5];
    int n,m,len;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m*(m-1)/2;i++)
    {
        scanf("%s",pp+1);
        int a=pp[1]-'a';//第一个点
        int b=pp[2]-'a';//第二个点
        scanf("%d",&len);
        if(len==0)
            continue;
        scanf("%s",s+1);
        int acnt=0,bcnt=0;
        if(s[1]-'a'==a)
            c[1]=10000*(s[1]-'a')+(++acnt);
        else c[1]=10000*(s[1]-'a')+(++bcnt);
        for(int i=2;i<=len;i++)
        {
            if(s[i]-'a'==a)
                c[i]=10000*(s[i]-'a')+(++acnt);
            else
                c[i]=10000*(s[i]-'a')+(++bcnt);
            in[c[i]]++;//入度
            vc[c[i-1]].push_back(c[i]);
        }
        num[a]=acnt;//记录数目
        num[b]=bcnt;
    }
    topo(n);
    return 0;
}

https://codeforces.com/contest/1230/problem/D

题意:

有n个人,每个人有一个能力可能有(0-59种能力),b[i]是能力的权值

a[i]来描述能力,将a[i]转化为二进制,如果第i位为1,则他会第i个技能

如果一个人的能力比另外一个人强,只要存在一个技能,他会,另一个人不会,两个人可以互相都比对方强

你可以选择一些元素组成一个集合,该集合中,不存在一个人比其他任何人都强

解析:

法1:O(n^2)复杂度

选择的若干人中,每个人都至少能战胜别人一次,和被战胜一次

建图跑拓扑排序,拓扑排序删边,剩下的图就是答案

这题可以是稠密图,用链式前向星会爆内存,我这里直接用二维数组,时间换空间,用vector也行

ac:

#include<bits/stdc++.h>
#define ll long long
#define MAXN 7005
using namespace std;
ll a[MAXN],b[MAXN];
int mm[MAXN][MAXN]={0};
int n;
int in[MAXN],vis[MAXN]={0};
 
void add(int u,int v)
{
    in[v]++;
    mm[u][v]=1;
}
 
void topo()
{
    queue<int> que;
    for(int i=1;i<=n;i++)
        if(in[i]==0)
            que.push(i);
    while(que.size())
    {
        int x=que.front();
        vis[x]=1;
        que.pop();
        for(int i=1;i<=n;i++)
        {
            if(!mm[x][i])
                continue;
            in[i]--;
            if(in[i]==0){
                que.push(i);
            }
        }
    }
}
 
int main()
{
    scanf("%d",&n);
    for(ll i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    for(ll i=1;i<=n;i++)
        scanf("%d",&b[i]);
    for(ll i=1;i<=n;i++)
    {
        for(ll j=1;j<=n;j++)
        {
            if(i==j)
                continue;
            ll aa=a[i];
            ll bb=a[j];
            if((aa|bb)==aa){
                add(i,j);
            }
        }
    }
    topo();
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        if(vis[i]==0)
            ans+=b[i];
    }
    printf("%lld\n",ans);
    return 0;
}

法2:

解析:O(n)复杂度

根据性质:

在一个集合中,不能存在一个技能只有一个人会的情况

=> 首先我们选择一些元素,这些元素的a[i]相同,我们选择一些a[i]相同的元素加入集合,如果所以a[i]都唯一,呢么集合为空

-> 我们选择这些包含相同a[i]的,然后选择一些a[i]包含在呢些集合中的

集合中有:11100*2,000111*2,

可以选择10100,000110,会的技能被任一元素的a[i]包含,不能选择001100,不能被单独一个包含

用|运算可以快速取得结果, a[i]|v[i]==v[i],a[i]被v[i]包含,a[i]会的v[i]都会

ac:

#include<bits/stdc++.h>
#define ll long long
#define MAXN 10005
using namespace std;
struct node
{
    ll a,b;
    friend bool operator<(node x,node y)
    {
        return x.a<y.a;
    }
}ee[MAXN];
ll vis[MAXN];
set<ll> st;
 
int main()
{
    ll n;
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++)
        scanf("%lld",&ee[i].a);
    for(ll i=1;i<=n;i++)
        scanf("%lld",&ee[i].b);
    if(n==1)
    {
        printf("0\n");
        return 0;
    }
    sort(ee+1,ee+n+1);
    for(ll i=1;i<=n;i++)
    {
        if(ee[i].a==ee[i+1].a&&i+1<=n)
            st.insert(ee[i].a);
    }
    set<ll>::iterator it;
    ll ans=0;
    for(it=st.begin();it!=st.end();it++)
    {
        ll v=*it;
        for(int i=1;i<=n;i++)
        {
            if((v|ee[i].a)==v&&vis[i]==0)
            {
                ans+=ee[i].b;
                vis[i]=1;
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值