洛谷2999 [USACO10NOV]巧克力牛奶Chocolate Milk (必经点)

题意

给定一棵有向树,求有向树的必经点。

题解1

一种对于所有有向图都是用的方法,求出从S到x的方案数f[x],和从T到x的方案数g[x]。对于一个点如果有f[x]*g[x]=f[T],那么x是必经点,
对于必经边的求法也提一下,同上求出f[x]和g[x],一条边(x,y)如果有f[x]*g[x]=f[T],则这条边是必经边。
实际上f和g是相当大的数字,我们可以采用取模的方案来存,如果你喜欢高精度我也不会拦你,多取几个模数更稳,详见代码。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll P=10007,Q=1000000007;
const int MAXN=100010;

inline void write(int x)
{
    if(x>9) write(x/10);
    putchar(x%10|48);
}

int n,S,T;
int head,tail,q[MAXN];

int ru[MAXN],chu[MAXN];bool beg[MAXN];
struct E{int y,next;}e[MAXN*2],Fe[MAXN*2];int len=0,last[MAXN],Flast[MAXN];
void ins(int x,int y)
{
    ru[y]++;chu[x]++;
    e[++len]=(E){y,last[x]};last[x]=len;
    Fe[len]=(E){x,Flast[y]};Flast[y]=len;
}

ll fp[MAXN],fq[MAXN],gp[MAXN],gq[MAXN];
void bfs1()
{
    head=0,tail=1;q[0]=S;
    fp[S]=fq[S]=1;
    while(head<tail)
    {
        int x=q[head++];
        for(int k=last[x];k;k=e[k].next)
        {
            int y=e[k].y;
            fp[y]=(fp[y]+fp[x])%P;
            fq[y]=(fq[y]+fq[x])%Q;
            ru[y]--;
            if(!ru[y]) q[tail++]=y;
        }
    }
}
void bfs2()
{
    head=0,tail=1;q[0]=T;
    gp[T]=gq[T]=1;
    while(head<tail)
    {
        int x=q[head++];
        for(int k=Flast[x];k;k=Fe[k].next)
        {
            int y=Fe[k].y;
            gp[y]=(gp[y]+gp[x])%P;
            gq[y]=(gq[y]+gq[x])%Q;
            chu[y]--;
            if(!chu[y]) q[tail++]=y;
        }
    }
}

int main()
{
    scanf("%d",&n);S=0,T=n+1;
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        ins(x,y);
    }
    for(int i=1;i<=n;i++)
        if(!ru[i]) ins(S,i),beg[i]=true;
        else if(!chu[i]) ins(i,T);
    
    bfs1();
    bfs2();
    
    for(int i=1;i<=n;i++) if(!beg[i])
        if(fp[i]*gp[i]%P==fp[T] && fq[i]*gq[i]%Q==fq[T]) write(i),puts("");
    return 0;
}

题解2

题目的隐含条件它给出来的点标是拓扑序,而且又是一棵树,那么我们有更加special的做法来应对。
可以发现,这些点应该是连续的一段,所以可以从出度入度来考虑这个问题。用两个指针标记一下。下面这个代码不是我的...

小结

如果想要大幅提速一定要把握好题目特性。

代码

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=100001;
int n,i,x,y,s,t,rd[maxn],cd[maxn],next[maxn];
inline int read()
{
    int x=0;
    char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
int main()
{
    n=read();
    for(i=1;i<n;i++)
    {
        x=read();
        y=read();
        next[x]=y;
        ++cd[x];
        ++rd[y];
    }
    s=t=-1;
    for(i=1;i<=n;i++)
    {
        if (t==i&&(s<0||rd[i]>1)) s=i;
        if (cd[i]!=1) break;
        if (next[i]>t) t=next[i];
    }
    for(i=s;i<=n;i=next[i])
    {
        printf("%d\n",i);
        if (cd[i]!=1) break;
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值