GDSOI 2016 T2 星际穿越

Description

有n个人在排队。他们会按顺序选择自己喜欢的点a[x]。如果a[x]已经被选择了,那么他会选择f[a[x]],如果f[a[x]]已经被选择了,则选择f[f[a[x]]]…保证所有人都有点选择。求选择的点本质不同的排列的方案数。
n<=10^6

Solution

我们把x向f[x]连边,那么原图就是一个环加内向树的模型。
如果只是一棵树的话,那么我们从叶子节点开始,设size[x]表示一开始选择x的人的个数,那么我们把答案*size[x],并且把size[f[x]]+=size[x]-1,就是留下一个人在这个点。
多了一条边怎么办?
因为题目保证所有人都有点选择,所以环上面肯定有一条边是不需要的。
那么我们找到这条边,把换破开,按树来做。
如何找边?
可以发现,如果这条边要删掉,那么走了一圈之后这条边就不会有人经过了。
也就是说这个点是人数-需求的最小值的点。
设g[i]表示从任意点开始,到i中size[x]-1的和,那么最小的g[i]的i便是那条边的起点。
注意,有多个联通快。
建议使用bfs,小心爆栈!

Code

#include
#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 1000005
#define mo 1000000007
using namespace std;
typedef long long ll;
bool bz[N];
int f[N],r[N],q[N],n,x,mi,id,k,l;
ll ans,size[N];
int main() {
    freopen("interstellar.in","r",stdin);
    freopen("interstellar.out","w",stdout);
    scanf("%d",&n);ans=1;mi=0x7fffffff;
    fo(i,1,n) scanf("%d",&x),size[x]++;
    fo(i,1,n) scanf("%d",&f[i]),r[f[i]]++;
    fo(i,1,n) if (!r[i]) q[++q[0]]=i;
    while (l<q[0]) {
        r[f[q[++l]]]--;bz[q[l]]=1;
        if (!r[f[q[l]]]) q[++q[0]]=f[q[l]];
    }
    fo(i,1,q[0]) ans=ans*size[q[i]]%mo,size[f[q[i]]]+=size[q[i]]-1;
    fo(i,1,n) if (!bz[i]) {
        k=0;mi=0x7fffffff;id=0;
        for(x=i;!bz[x];x=f[x]) {
            bz[x]=1;k+=size[x]-1;
            if (mi>k) mi=k,id=x;
        }
        for(x=f[id];x!=id;x=f[x]) {
            ans=ans*size[x]%mo;
            size[f[x]]+=size[x]-1;
        }
    }
    printf("%lld",ans);
}

你可能感兴趣的:(树形dp,其他dp,暴力,GDSOI2016,T2,星际穿越,bfs,dp)