CodeCoder vs TopForces【树状数组 或 强连通分量】

17 篇文章 0 订阅
13 篇文章 0 订阅

 

题目链接:https://vjudge.net/problem/Gym-101142C

题意,给出n个人在两个网站上的排名,求一个人可能战胜多少个人。

x可能战胜y的条件,x至少有一个排名大于y。

可能战胜是可传递的,假如a可能战胜b,b可能战胜c,那么a也可能战胜c(即使c的两个排名都大于a)。

 

 

 

一开始对于可传递这一个条件没有读懂,以为需要直接可战胜才行。

于是先根据x,y从小到大排序,对于第i个人,把他可以战胜的人分为2类,第一类是x<=s[i].x的,第二类是x>s[i].x的。

对于第一类的话,肯定是i-1个(因为根据x,y排序了)。

对于第二类的话,倒序插入点建立权值树状数组,每次查询一下 [0 ,s[i].y)区间内有多少个元素就可以了。

然后wa3..........

 

之后重新读题很长一段时间,发现关系可传递。

那么对于第i个人来说,他能战胜的第一类人的数目没有发生变化,但是第二类人的数目可能增多。

例如

4

1 2

1 3

2 5

3 1

显然最后一个人可以战胜3个人,但是第一个人可以战胜最后一个人,所以第一个人也可以战胜三个人。

这里想的做法是,在找第二类人时不能用是s[i].y当做标准来找,应该用 max(s[0-i].y) 来找。

因为如果第i个人想战胜后面的人,一定是需要用y值来战胜他。而他可以借助他所战胜的第一类人里,最大的那个y值。
其实到这里已经很接近正确答案了,但是接下来的一步一直没有做出来。

我的猜想是,在【i+1,n】里,找一个尽可能靠后的小于max(s[1~i].y)的人,记录他的位置为k,那么i能战胜的人数就是k-1(减掉自己本身)。

但是这样依旧是错误的,例如:

3

1 5

2 11

3 4

3 10

对于第一个人,尽可能靠后的小于max(s[0-i].y)的人是3号,按照上面的做法那么一号能战胜的人数就是2。但是实际上一号可以通过战胜3号来战胜2号,再通过战胜2号来战胜4号,所以一号能战胜的人数为3。

比赛时想到这里已经不会做了,觉得或许可以通过建图来解决,建图的话一共1e5个点,但是没有细想如何连边,只是觉得有两个参数要处理,排序连完x后,y没反应过来该怎么弄.......

 

接下来是正确做法:

做法一:

首先排序,然后对于第i个人,预处理max(s[1~i].y)记录为s[i].my。接下来倒序遍历插入/查询树状数组,此时树状数组所维护的是区间最大值,查询y值在  1~s[i].my-1 之间的人能战胜的最多人数,用来和i-1进行比较取最大值。

练习赛时离正确算法只差这有点难逾越的一步,当时再往深里想思路就已经偏了。

这里稍微有一些dp思想

#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 ll long long
#define pci pair<char,int>
#define mp make_pair
using namespace std;
const int mod = 1e9+7;
const int N = 1e6+1000;
struct node{
    int x,y,my,id;
    bool operator <(node b) const{
        if(x!=b.x) return x<b.x;
        return y<b.y;
    }
}s[N];
int n,tree[N],ans[N];
int lb(int x) {
    return x&-x;
}
void add(int x,int data) {
    while(x<=N-500) {
        tree[x] = max(tree[x],data);
        x += lb(x);
    }
}
int que(int x) {
    int ans = 0;
    while(x>0) {
        ans = max(ans,tree[x]);
        x -= lb(x);
    }
    return ans;
}
int main() {
    freopen("codecoder.in","r",stdin);
    freopen("codecoder.out","w",stdout);
    ios::sync_with_stdio(0);
    cin>>n;
    rep(i, 1, n) {
        cin>>s[i].x>>s[i].y;
        s[i].id = i;
    }
    sort(s+1,s+n+1);
    rep(i, 1, n)
        s[i].my = max(s[i].y,s[i-1].my);
    per(i, n, 1) {
        ans[s[i].id] = max(i-1,que(s[i].my-1));
        add(s[i].y,ans[s[i].id]);
    }
    rep(i, 1, n)
        cout<<ans[i]<<endl;
    return 0;
}

做法二:

      建图缩点dfs

      建图连边的方法:先以x为第一关键字排序依次连一遍,再以y为第一关键字排序连一遍。

      比起做法一要好想一些。

#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 ll long long
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define all(x) (x).begin(),(x).end()
using namespace std;
const int mod = 1e9+7;
const int N = 1e6+1000;
int n;
bool s_flag=0;
struct node{
    int x,y,my,id;
    bool operator <(node b) const{
        if(s_flag) {
            if(x!=b.x) return x<b.x;
            return y<b.y;
        }
        else {
            if(y!=b.y) return y<b.y;
            return x<b.x;
        }
    }
}s[N];
struct Edge{
    int v,nxt;
}edge[3*N];
int head[N],tot;
int low[N],dfn[N],Stack[N],belong[N]; //belong[x]表示x所属的强连通分量的编号
int idx,top;
int scc;  //scc表示强连通分量的个数
bool instack[N];
int num[N];  //num[x]表示第x个强连通分量内所包含的点的个数
void addedge(int u,int v){
    edge[tot].v = v;
    edge[tot].nxt = head[u];
    head[u] = tot++;
}
void tarjan(int u) {
    int v;
    low[u] = dfn[u] = ++idx;
    Stack[top++] = u;
    instack[u] = 1;
    for(int i = head[u]; i != -1; i = edge[i].nxt) {
        int v = edge[i].v;
        if(!dfn[v]) {
            tarjan(v);
            if(low[u]>low[v]) low[u] = low[v];
        }
        else if(instack[v]&&low[u]>dfn[v]) low[u] = dfn[v];
    }
    if(low[u]==dfn[u]) {
        scc++;
        do {
            v = Stack[--top];
            instack[v] = 0;
            belong[v] = scc;
            num[scc]++;
        }
        while(v!=u);
    }
}
void solve(int n) {
    memset(dfn,0,sizeof(dfn));
    memset(instack,0,sizeof(instack));
    memset(num,0,sizeof(num));
    idx = scc = top = 0;
    rep(i, 1, n)
        if(!dfn[i]) tarjan(i);
}
void init() {
    tot = 0;
    memset(head,-1,sizeof(head));
}
//以下是拓扑部分
vector<pii>road;
vector<int>nxt[N];
int ans[N];  
bool vis[N];
int dfs(int u) {
    if(ans[u]!=0) return ans[u];
    ans[u] = num[u];
    for(auto v:nxt[u])
        ans[u] += dfs(v);
    return ans[u];
}
int main() {
    freopen("codecoder.in","r",stdin);
    freopen("codecoder.out","w",stdout);
    ios::sync_with_stdio(0);
    cin>>n;
    rep(i, 1, n) {
        cin>>s[i].x>>s[i].y;
        s[i].id = i;
    }
    init();
    sort(s+1,s+n+1);
    rep(i, 1, n-1) {
        addedge(s[i+1].id,s[i].id);
        road.pb(mp(s[i+1].id,s[i].id));
    }
    s_flag = 1;
    sort(s+1,s+n+1);
    rep(i, 1, n-1) {
        addedge(s[i+1].id,s[i].id);
        road.pb(mp(s[i+1].id,s[i].id));
    }
    solve(n);
    memset(vis,0,sizeof(vis));         //初始化
    rep(i, 1, scc) nxt[i].clear();
    for(auto x:road) {                     //缩点重构图
        int u = x.first;
        int v = x.second;

        int uu = belong[u];
        int vv = belong[v];
        if(uu!=vv)
            nxt[uu].pb(vv);
    }
    rep(i, 1, scc) {                    //去重边
        sort(all(nxt[i]));
        nxt[i].erase(unique(all(nxt[i])),nxt[i].end());
    }
    rep(i, 1, n)
        cout<<dfs(belong[i])-1<<endl;

    return 0;
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值