UOJ Logo zhouzixuan的博客

博客

bzoj 1064: [Noi2008]假面舞会

2015-05-24 20:20:09 By zhouzixuan
【题目大意】:
    给定n个人,分别戴着k类面具(k>=3,k未知),其中戴着i类面具的人能看见第i%k+1类人的面具,给定一些人互相看到的关系,求k的最大最小值
【题目解法】:
    感觉非常神奇!!
    首先大概把图分为三种:

这个文字在图挂了的时候会显示

这个文字在图挂了的时候会显示

这个文字在图挂了的时候会显示

    1.每条边无向后为一棵树,那么就是森林中每棵树的最长链之和即为答案,最小值为3
    2.有环,然后环上点的个数一定是答案的倍数,找出环后gcd即可
    3.没有环,但是转成无向图后有环,那么我们可以发现,如果有两条链的话,那么答案为链长的差值(多条链的话以此类推)
    然后这样不好搞
    网上有种神奇的做法,就是把a可以看到b,那么连边(a,b,1) (b,a,-1),这样子我们用c[x]表示到x点的路径权值和,然后如果一个点被到达两次,那么两次的c值相减就是环的大小,并且可以把2,3情况一起考虑
    然后就是如果又有环,又有链,那么显然链就没用了...
    然后就是细节:注意枚举的点不一定是链的顶端,所以并不是所有的点c[i]都大于1!!
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=100005;
const int M=2000005;
struct edge {int x,y,next;} b[M]; bool v[N];
int n,m,tot,a[N],ansmax,ansmin,dmin,dmax,c[N];
inline long long getint()
{
    long long x=0; char c=getchar(); bool flag=false;
    while ((c!='-')&&((c<'0')||(c>'9'))) c=getchar();
    if (c=='-') flag=true,c=getchar();
    while ((c>='0')&&(c<='9')) x=x*10+(long long)(c-'0'),c=getchar();
    if (flag) return -x; else return x;
}
void addedge(int x,int y,int z)
{
    ++tot; b[tot].x=y; b[tot].y=z; b[tot].next=a[x]; a[x]=tot;
}
int gcd(int x,int y)
{
    if (y==0) return x; else return gcd(y,x%y);
}
void init()
{
    n=getint(); m=getint();
    for (int i=1; i<=m; i++)
    {
        int x=getint(),y=getint();
        addedge(x,y,1); addedge(y,x,-1);
    }
}
void dfs1(int x)
{
    v[x]=false;
    for (int p=a[x];p;p=b[p].next)
    {
        int pp=b[p].x;
        if (!v[pp]) {ansmax=gcd(ansmax,abs(c[x]+b[p].y-c[pp])); continue;}
        c[pp]=c[x]+b[p].y; dfs1(pp);
    }
}
void dfs2(int x)
{
    v[x]=false; dmax=max(dmax,c[x]); dmin=min(dmin,c[x]);
    for (int p=a[x];p;p=b[p].next)
    {
        int pp=b[p].x; if (v[pp]==false) continue;
        c[pp]=c[x]+b[p].y; dfs2(pp);
    }
}
void solve()
{
    memset(v,true,sizeof(v)); memset(c,0,sizeof(c));
    for (int i=1; i<=n; i++) if (v[i]) dfs1(i);//找环 
    if (ansmax==0) //处理链的情况 注意枚举的点不一定是链的顶端,所以并不是所有的点c[i]都大于1!! 
    {
        memset(v,true,sizeof(v)); memset(c,0,sizeof(c));
        for (int i=1; i<=n; i++)
        {
            if (!v[i]) continue; dmin=dmax=1;
            c[i]=1; dfs2(i); ansmax+=(dmax-dmin+1);
        }
        ansmin=3; //!!! 
    }
    else for (int i=3; i<=ansmax; i++) if (ansmax%i==0) {ansmin=i; break;}
    if ((ansmax<3)||(ansmin<3)) printf("-1 -1\n"); else printf("%d %d\n",ansmax,ansmin);
}
int main()
{
    init();
    solve();
    return 0;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。