UVALive 7272 Promotions【拓扑排序】【bitset】

题目链接

https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=5324

思路

大意就是说,给你一张拓扑图,表示员工间的优劣,然后给你a,b,问能升职人数分别为a,b时,一定能升职的人有几个,还有升职人数为b时,不可能升职的人数有几个。

那么只用求出每个点的最早的可能次序,和最晚的可能次序即可。

第一种方法O(n^2),对于每个点拓扑排序一遍,在保证这个不删的情况下,看看有多少个点可以删掉,这个就是最晚的次序。然后把所有边反向,再跑一边,总点数减去答案,就是最早的次序。

代码如下,936ms险过:

//UVA Live 7272
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <functional>
#include <numeric>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <queue>
#include <deque>
#include <list>
//#include <unordered_map>
using namespace std;
#define CLR(x,y) memset((x),(y),sizeof(x))
typedef long long ll;
typedef unsigned long long ull;
//typedef __int128 lll;

const int N=5000+100;
vector<int>g[2][N];
int d[2][N];
int in[N];
int a,b,e,p;

int topo(int dir, int x)
{
    queue<int>q;
    for(int i=0 ; i<e ; ++i)
    {
        in[i]=d[dir][i];
        if(in[i]==0) q.push(i);
    }
    int cnt=0;
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        if(u==x)
        {
            if(q.empty()) return cnt;
            else
            {
                q.push(u);
                continue;
            }
        }
        cnt++;
        for(auto &v:g[dir][u])
        {
            in[v]--;
            if(in[v]==0) q.push(v);
        }
    }
    return cnt;
}
int main()
{
    while(scanf("%d%d%d%d",&a,&b,&e,&p)!=EOF)
    {
        for(auto &ite:g[0])ite.clear();
        for(auto &ite:g[1])ite.clear();
        CLR(d,0);
        for(int i=0 ; i<p ; ++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            g[0][u].push_back(v);
            g[1][v].push_back(u);
            d[0][v]++;
            d[1][u]++; 
        }
        int ans_a=0,ans_b=0,ans_c=0;
        for(int i=0 ; i<e ; ++i)
        {
            int t=topo(0,i);
            if(t<a)ans_a++;
            if(t<b)ans_b++;
            t=topo(1,i);
            t=e-t;
            if(t>b)ans_c++;
        }
        printf("%d\n%d\n%d\n",ans_a,ans_b,ans_c);
    }
    return 0;
}

第二种方法,用bitset。
记忆化dfs搜出每个点的前驱和后继的点的个数。

bitset<N>pre[N], nxt[N] ,其中pre[i] 表示点i的后继的点的集合。

pre[u]|=pre[v] ,nxt把边反向,同理。

这样pre[i].count() 就表示点i最早出现的次序。n-nxt[i].count() 就表示最晚出现的次序。

复杂度为 N+M*bitset_or
bitset每次运算的复杂度大约为N/32,故总复杂度为n^2/32

代码如下,25ms:

//UVA Live 7272
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <functional>
#include <numeric>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <queue>
#include <deque>
#include <list>
#include <bitset>
//#include <unordered_map>
using namespace std;
#define CLR(x,y) memset((x),(y),sizeof(x))
typedef long long ll;
typedef unsigned long long ull;
//typedef __int128 lll;

const int N=5000+100;
vector<int>g[2][N];
bitset<N>st[2][N];//st[0]:nxt, st[1]:pre
int a,b,e,p;
bool vis[2][N];
void dfs(int dir, int u)
{
    if(vis[dir][u])return;
    vis[dir][u]=1;
    st[dir][u].reset();
    st[dir][u][u]=1;
    for(auto &v:g[dir][u])
    {
        dfs(dir,v);
        st[dir][u]|=st[dir][v];
    }
}
int main()
{
    while(scanf("%d%d%d%d",&a,&b,&e,&p)!=EOF)
    {
        for(auto &ite:g[0])ite.clear();
        for(auto &ite:g[1])ite.clear();
        CLR(vis,0);
        for(int i=0 ; i<p ; ++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            g[0][u].push_back(v);
            g[1][v].push_back(u);
        }
        int ans_a=0,ans_b=0,ans_c=0;
        for(int i=0 ; i<e ; ++i)
        {
            dfs(1,i);
            dfs(0,i);
            int t=e-st[0][i].count();
            if(t<a)ans_a++;
            if(t<b)ans_b++;
            t=st[1][i].count();
            if(t>b)ans_c++;
        }
        printf("%d\n%d\n%d\n",ans_a,ans_b,ans_c);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值