【bzoj4011】【hnoi2015】落忆枫音【精妙的动态规划】

我最近越来越感觉到我弱爆了。
今天下午全机房做hnoiD2,但是我只会敲暴力……第二题看着像点分治,可是我不会写~ ~ ~
看来多做题确实是真理 ~ ~ ~
这道题精妙极了!
引用一段PoPoQQQ大神的话:
由朱刘算法的推论可知,如果除根节点外每个点都选择一条入边,由于没有环,因此一定会形成一个树形图;
因此答案就是 ni=2=2×degreei ,其中degreei表示第i个点的入度。
引用完毕
这样加了一条从x到y的边后如果继续用这个公式的话会多算一些不合法的情况,即存在环的情况。我们考虑一下怎么干掉它。
首先明显最多只会出现一个环。这个环是由原来存在的从y到x的一条路径与现在加上的从x到y的边相连接形成的。
除去这个环,剩下的还是一棵树(形图)。
那么不合法的方案实际上是许许多多环+树(仙人掌?我只是吐槽……)
Syx 的某条路径上的点的集合,那么非法方案的总数是
S2iniSdegreei
而这个是可以运用动态规划来解决的。
f[i] Syi2jn,jSdegreej
那么

f[i]=jif[j]degreei

边界为
f[y]=2indegreeidegreey

注意如果y等于1号根节点或者出现自环,那么对原来答案没有影响。
除法转化为逆元,拓扑排序进行动态规划即可。
code:

#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=100001;
const int maxm=200010;
const ll MOD=1e9+7;
ll inv[maxn];
int in[maxn],d[maxn];
ll f[maxn];
ll ans,n,m,x,y;
struct E{
    int to,next;
}e[maxm];
int tot,list[maxn];
inline void add(int a,int b){
    e[++tot]=(E){b,list[a]};list[a]=tot;
}
inline int read(){
    int x=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) x=x*10+ch-48,ch=getchar();
    return x;
}
void LinearShaker(int n){
    inv[1]=1;
    for(int i=2;i<=n;++i)
        inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
}
void toposort(){
    static int q[maxn];

    int head=0,tail=0;
    for(int i=1;i<=n;++i) if(!in[i]) q[++tail]=i;
    while(headint h=q[++head];
        for(int k=list[h];k;k=e[k].next){
            if(!--in[e[k].to]) q[++tail]=e[k].to;
        }
    }
    f[y]=ans;
    for(int i=1;i<=tail;++i){
        f[q[i]]=(f[q[i]]*inv[d[q[i]]])%MOD;
        for(int k=list[q[i]];k;k=e[k].next){
            (f[e[k].to]+=f[q[i]])%=MOD;
        }
    }
}
int main(){
    cin>>n>>m>>x>>y;
    for(int i=1;i<=m;++i){
        int u=read(),v=read();
        add(u,v);
        in[v]++;
    }
    memcpy(d,in,sizeof d);
    d[y]++;
    LinearShaker(n);
    ans=1;
    for(int i=2;i<=n;++i) ans=(ans*d[i])%MOD;
    if(y==1||x==y){
        cout<"\n";
        return 0;
    }
    toposort();
    cout<<((ans-f[x])%MOD+MOD)%MOD<<"\n";
    return 0;
}

你可能感兴趣的:(hnoi,bzoj)