bzoj4727 [POI2017]Turysta(竞赛图构造哈密顿回路)

83 篇文章 0 订阅

bzoj4727 [POI2017]Turysta

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=4727

题意:
给出一个n个点的有向图,任意两个点之间有且仅一条有向边。对于每个点v,求出从v出发的一条经过点数最多,
且没有重复经过同一个点两次以上的简单路径。

输入第一行包含一个正整数n(2<=n<=2000),表示点数。接下来n-1行,其中的第i行有i-1个数,如果第j个数是1,那么
表示有向边j->i+1,如果是0,那么表示有向边j<-i+1。

数据范围
2<=n<=2000

题解:

原图给出来是一个竞赛图。
定理
竞赛图一定存在哈密顿路径
竞赛图存在哈密顿回路 充要条件是强连通

那么每个强连通分量里的点每个点都可以走完所有的点。
那么这道题就是竞赛图构造哈密顿回路。
然后拓扑序DP。

关于竞赛图如何构造(以及上述两个定理的证明):
在竞赛图中构造哈密顿路径:
这里写图片描述
假设我们已经构造出了前i个点的哈密顿路径(最初为1),那么加入i+1个点时,会有三种情况:
1、指向首。
2、被尾指向。
这两种之间就加在首位了。
3、找到中间第一个被i+1指向的点,它前一个点与i+1与它重新连接。

已知强连通竞赛图中的哈密顿路径,如何构造哈密顿回路
这里写图片描述
这里写图片描述
首先找到最近的那个指向首的节点i,形成一个环(因为强连通所以一定存在),然后考虑把环扩大,到i+1节点。
1、指向首,就直接扩大即可。
2、指向其中的某个点,走如图的路径形成新环。
3、没有指向环,考虑下一个点。

于是就完成了构造及证明。

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N=2005;
int n,x,g[N][N],to[N][N],dp[N],low[N],dfn[N],inc=0,S[N],top=0,pl[N],cnt=0,q[N],du[N],pre[N],root[N],tot=0,V[N],nxt[N];
bool ins[N];
inline int read()
{
    int ret=0,w=1; char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-'){w=-1; ch=getchar();}
    while(ch>='0'&&ch<='9') {ret=(ret<<1)+(ret<<3)+ch-'0'; ch=getchar();}
    return ret*w;
}
void get()
{
    int head=V[1]; int tail=V[1];
    if(tot==1) {nxt[tail]=tail; return;}
    for(int j=2;j<=tot;j++)
    {
        int i=V[j];
        if(g[i][head]){nxt[i]=head; head=i; continue;}
        else if(g[tail][i]){nxt[tail]=i; tail=i; continue;}
        int x,y; for(x=nxt[head],y=head;x&&!g[i][x];y=x,x=nxt[x]);
        nxt[y]=i; nxt[i]=x;
    }
    tail=head; head=0;  
    for(int i=nxt[tail];i;i=nxt[i])
    {
        if(head)
        {
            for(int p1=head,p2=tail;;p2=p1,p1=nxt[p1])
            {
                if(g[i][p1])
                {
                    nxt[p2]=nxt[tail];
                    if(p2!=tail) nxt[tail]=head;
                    tail=i; head=p1; break;
                }
                if(p1==tail) break;
            }
        }
        else if(g[i][tail]){head=tail;tail=i;}
    }
    nxt[tail]=head;
}
void dfs(int u)
{
    inc++; dfn[u]=low[u]=inc;
    ins[u]=1; S[++top]=u;
    for(int v=1;v<=n;v++) if(g[u][v])
    {
        if(!dfn[v]) {dfs(v); low[u]=min(low[u],low[v]);}
        else if(ins[v]) low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        cnt++; root[cnt]=u; tot=0;
        while(1)
        {
            dp[cnt]++;  V[++tot]=S[top];
            pl[S[top]]=cnt; ins[S[top]]=0;
            top--; if(S[top+1]==u) break;
        }
        get();
    }
}
void print(int x)
{
    if(!x){printf("\n");return;} 
    printf("%d ",x);
    for(int i=nxt[x];i!=x;i=nxt[i]) printf("%d ",i);
    print(root[pre[pl[x]]]);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    for(int j=1;j<=i;j++)
    {
        x=read();
        if(x) g[j][i+1]=1;
        else g[i+1][j]=1;
    }
    for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i); 
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(g[i][j]&&pl[i]!=pl[j]) to[pl[i]][pl[j]]=1; 
    for(int i=1;i<=cnt;i++) for(int j=1;j<=cnt;j++) if(i!=j&&to[j][i]) du[i]++;
    int lf=1,rg=0; top=0; for(int i=1;i<=cnt;i++) if(du[i]==0) S[++rg]=i;
    while(lf<=rg)
    {
        int x=q[++top]=S[lf]; lf++;
        for(int y=1;y<=cnt;y++)
        {
            if(!to[x][y]||y==x) continue;
            du[y]--; if(du[y]==0) S[++rg]=y;
        }
    }
    for(int i=top;i>=1;i--)
    {
        int x=q[i]; int det=0;
        for(int y=1;y<=cnt;y++)
        {
            if(!to[x][y]||x==y) continue;
            if(det<dp[y]){det=dp[y]; pre[x]=y;}
        }
        dp[x]+=det; 
    }
    for(int i=1;i<=n;i++) {printf("%d ",dp[pl[i]]); print(i);}
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值