BZOJ1064【NOI2008】【假面舞会】

BZOJ1064【NOI2008】【假面舞会】

Description一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会。今年的面具都是主办方特别定制的。每个参加舞会的人都可以在入场时选择一 个自己喜欢的面具。每个面具都有一个编号,主办方会把此编号告诉拿该面具的人。为了使舞会更有神秘感,主办方把面具分为k (k≥3)类,并使用特殊的技术将每个面具的编号标在了面具上,只有戴第i 类面具的人才能看到戴第i+1 类面具的人的编号,戴第k 类面具的人能看到戴第1 类面具的人的编号。 参加舞会的人并不知道有多少类面具,但是栋栋对此却特别好奇,他想自己算出有多少类面具,于是他开始在人群中收集信息。 栋栋收集的信息都是戴第几号面具的人看到了第几号面具的编号。如戴第2号面具的人看到了第5 号面具的编号。栋栋自己也会看到一些编号,他也会根据自己的面具编号把信息补充进去。由于并不是每个人都能记住自己所看到的全部编号,因此,栋栋收集的信 息不能保证其完整性。现在请你计算,按照栋栋目前得到的信息,至多和至少有多少类面具。由于主办方已经声明了k≥3,所以你必须将这条信息也考虑进去。

Input第一行包含两个整数n, m,用一个空格分隔,n 表示主办方总共准备了多少个面具,m 表示栋栋收集了多少条信息。接下来m 行,每行为两个用空格分开的整数a, b,表示戴第a 号面具的人看到了第b 号面具的编号。相同的数对a, b 在输入文件中可能出现多次。

Output包含两个数,第一个数为最大可能的面具类数,第二个数为最小可能的面具类数。如果无法将所有的面具分为至少3 类,使得这些信息都满足,则认为栋栋收集的信息有错误,输出两个-1。

Sample Input【输入样例一】

6 51 22 33 44 13 5

【输入样例二】

3 31 22 12 3Sample Output【输出样例一】4 4

【输出样例二】-1 -1HINT100%的数据,满足n ≤ 100000, m ≤ 1000000。这是一道比较水的图论题,想法很重要,细节问题要好好考虑。应为细节问题,硬生生磨了我一个下午才写出来。

每次读入的数据x,y,将x来拿一条有向正边给y,y连一条有向负边给x(为什么?之后会解释),这样答案转化成了求环的染色方案数的gcd(最大公约数),当只为一条链时,求链的最大长度。

链表比较好处理,所以先考虑环。

首先考虑一个简单环(就是N个点由N个路径连接起来的图),如下图两种染色方法,因为每个相同颜色的点的后驱颜色一定要相同,所以只有两种方案。所以可以证明一个简单环上的染色方案数为这个简单环的边数的约束(例如长度为4,有2,4两种方法)。

再考虑非简单环的类似环,这四个点构成了一种类似环的图,可以看出黄点到黑点的两种路径相反,两两相撞,红点到两个黄点有相同的路径,所以黄点红点黑点三个点都被确定了,联系上一幅图,是不是与第二种情况神似。所以我们在边上在建一条反向的负边,是我们可以继续走下去,并计算这幅图的贡献。可以看出加上负边后,等于黄点跨过了黑点,这种情况下,我们把这个有向图转换成无向图来看待,它还是一个简单环,此时合法的类数是环上正向边个数-反向边个数的约数。如下图是一个例子,这个“简单环”有6条正向边,2条反向边,注意到一条反向边肯定和一条正向边头碰头地“抵消”掉了,把这些边和点缩成一个点,这时候这个“简单环”就会变成一个普通的有向图的简单环,变成了上面的情况。扩展到一般情况,连续的若干条反向边和连续的若干条正向边也会像头碰头一样“抵消”掉。

图转化:

所以这样的环对答案的贡献为正向边减去反向边。求解则用bfs或dfs都行

请注意

  • 这道题可能有多个联通块,这样的话就不能只一次性求解,要加上枚举每个节点,判断是否已经计算过,没计算过的让该点为出发点开始计算

  • 不能直接走正反路,应该在遇到某个点访问第二次,用现在求出的路径长减去这个点本来就有的路径长

  • 求链时,开始的节点不一定是链的头,所以要将求出的最长长度减去最短长度(大部分时负数,因为走的是反向负边)得到链的长

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 100010
#define maxm 1000010
using namespace std;
int read(){
    int x=0;char c=getchar();
    while(c<'0' || c>'9') c=getchar();
    while(c>='0' && c<='9')
        x=x*10+c-'0',c=getchar();
    return x;
}
struct w{
    int id,v;
}pnt[maxm*2];
int ans;
int team[maxn*4],dis[maxn];
int head[maxn],nxt[2*maxm],cnt,sum;
int vis[maxn];
void add(int x,int y,int z){
    nxt[++cnt]=head[x],head[x]=cnt;
    pnt[cnt]=(w){y,z};
}
int abs(int a){return a>0?a:-a;}
int gcd(int a,int b){
    if(a<b) swap(a,b);
    if(a==0) return b;
    while(b){
        int sub=a%b;
        a=b;
        b=sub;
    }
    return a;
}
void bfs(int s){
    int h=0,t=1;
    team[1]=s;vis[s]=1,dis[s]=0;//dis[]保存到该节点的路径长,注意点2
    int MAX=0,MIN=0;//注意点3
    while(h<t){
        int now=team[++h];
        for(int i=head[now];i!=-1;i=nxt[i]){
            int to=pnt[i].id;
            if(!vis[to]){
                dis[to]=dis[now]+pnt[i].v,team[++t]=to;
                MAX=max(MAX,dis[to]),MIN=min(MIN,dis[to]);
                vis[to]=1;
            }
            else{
                ans=gcd(ans,abs(dis[now]+pnt[i].v-dis[to]));
            }
        }
    }
    sum+=MAX-MIN+1;
}
int main(){
    freopen("1064.in","r",stdin);
    freopen("1064.out","w",stdout);
    memset(head,-1,sizeof(head));
    int n=read(),m=read();int s=1;
    for(int i=1;i<=m;i++){
        int x=read(),y=read();
        add(x,y,1),add(y,x,-1);
    }
    ans=0;
    for(int i=1;i<=n;i++)
        if(!vis[i]) bfs(i);//注意点1
    if(!ans)
        if (sum>=3) return printf("%d %d",sum,3),0;
    for(int i=3;i<=ans;i++)
        if(!(ans%i)) return printf("%d %d",ans,i),0;
    puts("-1 -1");
    return 0;
}

乍一看好像代码不难,不长,其实还是有一定难度的,是一道图论好题!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值