HNOI2015实验比较【题解】

9 篇文章 0 订阅
3 篇文章 0 订阅

前言

记得几个月前自己曾经做过一道关于一张DAG求排列的问题,现如今遇到了的是一道关于树求排列的问题.
这一个问题看似简单实际上有一个细节那就是他可以取=。
这就使得复杂度升高了.

sol

首先,最简单的插孔原理。这一个非常简单,做这道题时,蒟蒻我现推式子。
为什么要解决这个问题呢,因为最基本的,在不考虑等于的情况下,n个数插入m个数的空隙中就相当于题目中的把两组不等式合起来.
怎么推?
设序列A,B
由于发现合并后的序列C中,原本的关系顺序不能改变,也就是A1仍然在A2前面,A2仍然在A3前面,所以A1如果插入了一个位置,那么A2只能插A1后面的位置.
这个时候暴力搞法就是插入Ai时枚举Ai-1在哪个位置。但不妨这样想,先不考虑位置变化的情况,也就是默认A2可以在A1前面,那么这样的插入情况就是:
原本有m个孔,然后插入一个以后,就会变成m+1个孔,因为会新增插入的那个数左右两边的孔,这样的情况是(m+1)×(m+2)×…..×(m+n).
然后把这样的情况除以一个n的排列,就能保证A1在A2前面了.
所以式子变成

Πni=1(m+i)n! Π i = 1 n ( m + i ) n !

如果我么给上下同时乘上一个 m! m ! 那么就会有
(m+n)!n!×m! ( m + n ) ! n ! × m !

显然一个组合数.
换一种理解实际上就是合并后有m+n个数,这m+n个数中随意挑选n个位置作为f放A序列的,然后剩下的m个位置放B序列.

接着考虑有等号的情况。我们发现如果是一个序列里有等号,那么等号左右两边的相当于是一个数,然后就可以把他们缩在一起,这样等号就被消掉了。
我们如果想让两个没有等号的序列合在一起变成有等号的怎么办?
我们发现如果Ai要等与Bj,那么Ai的考虑可以插入的孔就会减少,因为他不能插在两个数中间,只能插在一个数上面,而且那个数只能允许他一个人插.
这样的理解实在没有什么好的进展。但我们知道如果枚举合并后有多少个等号就可以了。也就是说长度为n的A序列,m的B序列合并成p的C序列.
显然的,可以确定p > n, p > m,p < m + n;

然后,还是在p个位置中选出n个放A序列,这时,剩下的位置已经不足以放B序列,但是可以在已经放好A序列的位置里选出m+n-p个位置放B序列,所以说,方案数为

Cnp×Cpmnn C p n × C n p − m − n

可以看出,开头提及的只是一种特殊情况。

code

#include<bits/stdc++.h>
using namespace std;
template <class T>
inline void read(T&data){
    data=0;
    register char ch=0;
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch<='9'&&ch>='0'){
        data=(data<<3)+(data<<1)+(ch^48);
        ch=getchar();
    }
    return;
}
const int _ =101;
const int mod = 1e9+7;
int N,M;
int c[_<<1][_<<1],cnt,parent[_],to[_<<1],nxt[_],head[_],degree1[_],degree2[_],size[_],dp[_][_],g[_],Dp[_];
bool vis[_];
struct rel{
    int fa,son;
}po[_];
inline int poww(register int x,register int y){
    register int ret=1,base=x;
    for(;y;y>>=1){
        if(y&1)ret=1LL*ret*base%mod;
        base=1LL*base*base%mod;
    }
    return ret;
}
inline int getni(register int x){return poww(x,mod-2);}
inline void init(){
    c[0][0]=1;for(register int i=1;i<=2*N;++i)c[i][0]=1;
    for(register int i=1;i<=2*N;++i){
        for(register int j=1;j<=i;++j){
            c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
        }
    }
}
inline void add(register int a,register int b){to[++cnt]=b,nxt[cnt]=head[a],head[a]=cnt;}
inline int fi(register int x){return x==parent[x]?x:parent[x]=fi(parent[x]);}
void dfs(register int now){
    //cout<<now<<endl;
    vis[now]=1;
    for(register int i=head[now];i;i=nxt[i]){
        dfs(to[i]);
        if(size[now]==0){
            for(register int j=size[to[i]];j>=1;--j){
                dp[now][j]=dp[to[i]][j];
            }
            size[now]+=size[to[i]];
            continue;
        }
        for(register int j=size[now]+size[to[i]];j>=1;--j)g[j]=0;
        for(register int j=size[to[i]];j>=1;--j){
            if(dp[to[i]][j]==0)break;
            for(register int o=size[now];o>=1;--o){
                if(dp[now][o]==0)break;
                for(register int w=j+o;w>=max(j,o);--w){
                    g[w]+=1LL*dp[now][o]*dp[to[i]][j]%mod*c[w][j]%mod*c[j][o-w+j]%mod;
                    g[w]%=mod;
                }
            }
        }
        size[now]+=size[to[i]];
        for(register int j=size[now];j>=1;--j)dp[now][j]=g[j];
    }
    if(size[now]==0){size[now]=1;dp[now][1]=1;return;}
    for(register int i=size[now];i>=0;--i)dp[now][i+1]=dp[now][i];size[now]++;
    return;
}
int main(){
    register int cc=0 ;
    read(N),read(M);
    init();
    for(register int i=1;i<=N;++i)parent[i]=i;
    for(register int i=1;i<=M;++i){
        register int a,b;register char rr=0;
        read(a);
        while(rr!='='&&rr!='<')rr=getchar();
        read(b);
        if(rr=='='){
            register int fa=fi(a),fb=fi(b);parent[fb]=fa;
        }
        else 
            po[++cc].fa=a,po[cc].son=b;
    }
    for(register int i=1;i<=cc;++i)
        add(fi(po[i].fa),fi(po[i].son)),++degree1[fi(po[i].fa)],++degree2[fi(po[i].son)];
    register int ans=0,Sz=0;
    for(register int i=1;i<=N;++i){
        if(vis[fi(i)]==0&&degree2[fi(i)]==0){           
            dfs(fi(i));
            register int qio=fi(i);
            if(Sz==0){
                for(register int j=size[qio];j>=1;--j){
                    Dp[j]=dp[qio][j];
                }
                Sz+=size[qio];
                continue;
            }
            for(register int j=Sz+size[qio];j>=1;--j)g[j]=0;
            for(register int j=size[qio];j>=1;--j){
                if(dp[qio][j]==0)break;
                for(register int o=Sz;o>=1;--o){
                    if(Dp[o]==0)break;
                    for(register int w=j+o;w>=max(j,o);--w){
                        g[w]+=1LL*Dp[o]*dp[qio][j]%mod*c[w][j]%mod*c[j][o-w+j]%mod;
                        g[w]%=mod;
                    }
                }
            }
            Sz+=size[qio];
            for(register int j=Sz;j>=1;--j)
                Dp[j]=g[j];         
        }
    }
    for(register int i=1;i<=N;++i){ans=ans+Dp[i];ans%=mod;}
    printf("%d",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值