bzoj1006_弦图最小点染色

10 篇文章 0 订阅

Description

K国是一个热衷三角形的国度,连人的交往也只喜欢三角原则.他们认为三角关系:即AB相互认识,BC相互认识,CA相互认识,是简洁高效的.为了巩固三角关系,K国禁止四边关系,五边关系等等的存在.所谓N边关系,是指N个人 A1A2...An之间仅存在N对认识关系:(A1A2)(A2A3)...(AnA1),而没有其它认识关系.比如四边关系指ABCD四个人 AB,BC,CD,DA相互认识,而AC,BD不认识.全民比赛时,为了防止做弊,规定任意一对相互认识的人不得在一队,国王相知道,最少可以分多少支队。

Input

第一行两个整数N,M。1<=N<=10000,1<=M<=1000000.表示有N个人,M对认识关系. 接下来M行每行输入一对朋友

Output

输出一个整数,最少可以分多少队

Sample Input

4 5
1 2
1 4
2 4
2 3
3 4

Sample Output

3

HINT

一种方案(1,3)(2)(4)  
---------------------------------------------------------------------------------------------------------------------------------------------
    被cena略微坑了一下,第一次交部分暴力, cena上面测的最大一组0.89s, O(m+n)优化后再测, 居然是1.5s+......
    然后作死地在bzoj上一交, 结果如下:
    
    比第一次快了不少, 可能是windows或者cena的问题, 不过我也不准备深究了。
---------------------------------------------------------------------------------------------------------------------------------------------
    了解题意后, 发现这是一道最小点染色的问题, 不过用普通方法做是一定会超时的
    首先我们要了解一个叫弦图的定义: 当图中任意长度大于3的环都至少有一个弦时,  一个无向图称为弦图
    对于这道题, 题目中提到的不存在四角、五角等关系就说明这个图是一个弦图。 我们可以用弦图的特殊性质去做, 当然, 这又涉及到许许多多的定义了。 ( 点击打开链接如果读了这篇文章可直接跳至下个分界线处)
    一大波定义即将到来......
     子图: 图G = (V, E), 则G' = (V', E'), V' ⊆V, E' E 为图G的子图。
     团: 图G的一个子图G' = (V', E' )  ,G' 为关于V', 的完全图。
     诱导子图:G = (V, E), G' = (V', E'), V' ⊆V, E' = {(u, v)|u,v∈V', (u, v) ∈E } 中, 图G'称为图G的诱导子图。
    单纯点:设N(v)表示与点v相邻的点集。一个点称为单纯点当{v} + N(v)的诱导子图为一个团。
    完美消除序列: 一个点的序列(每个点出现且恰好出现一次)v1, v2, …, vn满足vi在{vi, vi+1,…,vn}的诱导子图中为一个单纯点。
---------------------------------------------------------------------------------------------------------------------------------------------
    那么, 完美消除序列从后往前依次给每个点染色,给每个点染上可以染的最小的颜色。这时染的最大的颜色即为答案。
    求完美消除序列时, 用到了最大势算法(其实是因为LexBFS不会写QAQ), 这样一来, 整道题的框架就出来了。不过最大势算法的复杂度却是关键, 前面已经说过, 第一次提交时我用的是O(n²)  的暴力算法, 但显然有更优的, 用堆可以优化到O(nlogn), 但我更倾向于O(n+m)的链表求法。
    考虑从0到n的链表, 数字表示该链表中的点的当前势, 同时用一个数组flag和一个变量maxs记录点是否在序列中和当前最大势。 每次在最大势的链表上取出一个不再序列中的点, 若链表取完, 则maxs--, 直到取出一个点。 将该点加到序列中, 并将与之相连的不再序列中的点的势+1, 更新maxs并将之挂到相应的链表上, 这样就做到了O(n+m)的复杂度。
---------------------------------------------------------------------------------------------------------------------------------------------
CODE:
#include <cstdio>
#include <cstring>
#include <queue>
#include <cstdlib>
#define N 10000 + 10
#define M 2000000 + 10

using namespace std;

struct edge
{
    int to, next;
}e[M];
int n, m, num, ans, maxs;
int p[N], seq[N], col[N], lab[N], flag[N];
struct node
{
    int now;
    node *next;
}f[N];//链表
void add(int x, int y)
{
    e[++num].to = y;
    e[num].next = p[x];
    p[x] = num;
}
void put(int x)
{
    node *po = (struct node *)malloc(sizeof(struct node));
    po->next = f[lab[x]].next;
    po->now = x;
    f[lab[x]].next = po;
}//链表的插入
void read(int &x)
{
    x = 0;
    char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9')
    {
        x = (x << 1) + (x << 3) + c - '0';
        c = getchar();
    }
}
void init()
{
    int x, y;
    read(n), read(m);
    for (int i = 1; i <= m; ++i)
    {
        read(x), read(y);
        if (x == y) continue;
        add(x, y);
        add(y, x);
    }
}
void create()
{
    for (int i = 1; i <= n; ++i)
    f[i].next = NULL;//
    for (int i = 1; i <= n; ++i) put(i);
    maxs = 0;//初始化
    for (int i = n; i; i--)//用逆序求
    {
        node *po = f[maxs].next;//找到当前最大
        while(flag[po->now])
        {
            f[maxs].next = po = po->next;//及时删除没有用的点(漏掉的话会超时)
            while(po == NULL)
            {
                maxs--;
                po = f[maxs].next;
            }
        }
        f[maxs].next = po->next;//更新
        while(f[maxs].next == NULL) maxs--;
        int x = po->now;
        flag[x] = 1, seq[i] = x;
        for (int j = p[x]; j; j = e[j].next)
        if (!flag[e[j].to])
        {
            ++lab[e[j].to];//加势
            if (lab[e[j].to] > maxs) maxs = lab[e[j].to];
            put(e[j].to);
        }
    }
}
void paint()
{
    for (int i = 1; i <= n; ++i)
    flag[i] = -1;
    for (int i = n; i; i--)
    {
        int x = seq[i];
        for (int j = p[x]; j; j = e[j].next)
        flag[col[e[j].to]] = i;
        for (int j = 1; j <= n; ++j)
        if (flag[j] != i)
        {
            col[x] = j;
            break;
        }
        if (ans < col[x]) ans = col[x];
    }
}
void deal()
{
    create();
    paint();
    printf("%d\n", ans);
}
int main()
{
    freopen("kingdom.in", "r", stdin);
    freopen("kingdom.out", "w", stdout);
    init();
    deal();
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值