嘘~ 正在从服务器偷取页面 . . .

CF421D Bug in Code 题解


CF421D Bug in Code 题解

题目链接:CF421D Bug in Code

题意

最近,在 “低聚果糖” 代码公司发现了一个严重的 bug!”低聚果糖” 公司的负责人 cxy 想找到罪魁祸首并惩罚他。为了这个,她组合了一场会议,主题是:谁写出了 bug?会议上一共有 $n$ 个程序员,其中每个人都说:“我知道,一定是 $x$ 或 $y$ 之一!”

cxy 决定选择两名嫌疑人并请到他的办公室。自然地,她需要考虑这 $n$ 个人的观点。她想要选择这样两个人 $u, v$,满足 $n$ 个人的至少 $p$ 个人同意。一个人同意指的是,他认为 (是嫌犯) 的两个人中,至少有一个是 $u$ 或 $v$。cxy 想知道,一共有多少种方案选择两名嫌疑人?

输入格式

第一行包含两个非负整数 $n, p~(3 \leq n \leq 3 \times 10^5, 0 \leq p \leq n)$ ,表示 “低聚果糖” 公司中程序员的数量和需要同意的人的最小数目。

接下来的 $n$ 行,每行包含两个整数 $x_i, y_i~(1 \leq x_i, y_i \leq n)$,保证 $x_i, y_i, i$ 互不相同,表示第 $i$ 个程序员认为的嫌犯编号。

输出格式

输出一行一个整数,表示选择两名嫌疑人的总方案数。注意,选择的两个人是无序的,即 $(1, 2)$ 和 $(2, 1)$ 被看作是同一种方案。

如果某个人认为 $(x,y)$ 为嫌犯,则连一条 $(x,y)$ 的无向边。

则题目转化为找到有多少对 $(u,v)$ 满足 $d(u) + d(v) \ge p$ ,其中 $d(u)$ 表示 $u$ 的度数。

具体地,移项得 $d(u) \ge p-d(v)$ 。

我们可以把结点按 $d$ 排序,然后二分算出每个 $u$ 能够产生贡献的 $v$ 的个数。

但是事实上,这么算会多算答案,因为 $(u,v)$ 间的重边只能算一次,而重边会导致 $d(u),d(v)$ 变化。

我们假设 $(u,v)$ 间有 $k$ 条重边,则多计算的点对 $(u,v)$ 满足

考虑怎么找到这样的点对。直接 $O(n^2)$ 枚举肯定是不行滴。

注意到这个边数仅为 $O(n)$ ,因此实际上被算重的点对是很少的,即 $O(n)$ 的。

考虑把所有边去重以后,在计算二分计算答案后,枚举与 $u$ 相邻的所有点,并去掉所有满足 $(1)$ 式的 $v$ 的贡献。

这样的复杂度仍是 $O(n \log n)$ 的,因为只有 $n$ 条边,每个结点至多被访问 $2$ 次。

然后就是一些细节问题了

比如 $(u,u)$ 是不合法的,要特判掉,还有最后答案要的是无序点对数,所以答案要除以 $2$ 。

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(3e5+15))
#define M ((int)(6e5+15))

int n,p,pos,ans,o[N],in[N],head[N];
struct Edge{int u,v,w=1,next;} e[M];
bool operator<(Edge a,Edge b)
{ return a.u == b.u ? a.v < b.v : a.u < b.u; }
bool operator==(Edge a,Edge b)
{ return a.u == b.u && a.v == b.v; }
int cmp(int a,int b) { return in[a] > in[b]; }
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> n >> p;
    for(int i=1,u,v; i<=n; i++)
    {
        cin >> u >> v;
        e[i * 2 - 1] = {u,v};
        e[i * 2] = {v,u};
        ++in[u]; ++in[v]; o[i]=i;
    }
    sort(o+1, o+1+n, cmp);
    sort(e+1, e+2*n+1);
    for(int i=1; i<=2*n; i++)
        e[i] == e[i-1] ? (void)++e[pos].w : (void)(e[++pos]=e[i]);
    for(int i=1; i<=pos; i++) e[i].next=head[e[i].u], head[e[i].u]=i;
    for(int u=1; u<=n; u++)
    {
        in[0] = p-in[u];
        int r = upper_bound(o+1, o+1+n, 0, cmp) - o - 1;
        r -= (in[u]*2 >= p);
        for(int i=head[u]; i; i=e[i].next)
        {
            int v = e[i].v;
            r -= (in[u] + in[v] >= p && in[u] + in[v] - e[i].w < p);
        }
        ans += r;
    }
    cout << (ans/2) << '\n';
    return 0;
}

参考文献

[1] https://yhx-12243.github.io/OI-transit/records/cf421D.html


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录