DP

某些比较烦的DP题。
实际却都是在瞎搞,正经的DP题没有几个。

T1 Parenthese sequence

然后这题我是贪心过的。
把可以不用问号匹配的直接用栈匹配掉。
然后贪心地用问号去匹配那些未被匹配的。
然后看最后剩下的问号是否在2个以上。

代码

根本找不到类似这种的写法= =
另外,这似乎感觉是错的,请勿参考

#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
#define LL long long

void Rd(int &res){
    char c;res=0;
    while((c=getchar())<48);
    do res=(res<<3)+(res<<1)+(c^48);
    while((c=getchar())>47);
}
#define M 1000004
char C[M];
int n,With[M],Stk[M],Top,Cnt,Num;
int main(){
    while(scanf("%s",C)!=EOF){
        n=strlen(C);
        if(n&1){
            puts("None");
            continue;
        }
        Top=0;
        REP(i,0,n){
            if(C[i]=='(')Stk[++Top]=i;
            else if(C[i]==')'){
                if(Top)With[Stk[Top]]=i,With[i]=Stk[Top--];
                else With[i]=-1;
            }
        }
        while(Top)With[Stk[Top--]]=-1;
        Cnt=Num=0;
        REP(i,0,n){
            if(C[i]=='?'){
                if(Cnt)Cnt--;
                else Num++;
            }
            else if(With[i]==-1){
                if(C[i]=='(')Cnt++;
                else {
                    Num--;
                    if(Num<0)break;
                }
            }
        }
        if(Cnt || Num<0)puts("None");
        else if(Num>2)puts("Many");
        else puts("Unique");
    }
    return 0;
}

T2 Rating

分析

高斯消元可以强行写掉= =
然后,先考虑只有一个账号的情况。
定义 DPi D P i 为i分到20分所需的期望比赛场数。
DP0=1+(1p)DP0+pDP1 D P 0 = 1 + ( 1 − p ) ∗ D P 0 + p ∗ D P 1
DP1=1+(1p)DP0+pDP2 D P 1 = 1 + ( 1 − p ) ∗ D P 0 + p ∗ D P 2
整理并化简可得:
DP0=1/p+DP1 D P 0 = 1 / p + D P 1
DP1=1/p+1/p2+DP2 D P 1 = 1 / p + 1 / p 2 + D P 2
然后定义 ti=DPiDP0 t i = D P i − D P 0
那么 t1=1/p,t2=1/p+1/p2 t 1 = 1 / p , t 2 = 1 / p + 1 / p 2
又由 DPi=1+(1p)DPi2+pDPi+1(i2) D P i = 1 + ( 1 − p ) ∗ D P i − 2 + p ∗ D P i + 1 ( i ≥ 2 ) ,代入
DP0=1/p+1/pti(1p)/pti2+DPi+1 D P 0 = 1 / p + 1 / p ∗ t i − ( 1 − p ) / p ∗ t i − 2 + D P i + 1
ti=1/p+1/pti1+(1p)/pti3 t i = 1 / p + 1 / p ∗ t i − 1 + ( 1 − p ) / p ∗ t i − 3
然后可知最后一定会到达 (19,20) ( 19 , 20 ) 的分数。
所以答案就是 t19+t20 t 19 + t 20

代码
#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DB double

DB P,t[24];
int main(){
    while(scanf("%lf",&P)!=EOF){
        t[1]=1/P;
        t[2]=1/P+1/P/P;
        REP(i,3,21)
            t[i]=1/P+1/P*t[i-1]-(1-P)/P*t[i-3];
        printf("%.6lf\n",t[19]+t[20]);
    }
    return 0;
}

T3 Peter’s Hobby

分析

经典的概率DP模型。
一套状态的成立概率即为所有概率的乘积。
用类似最长路的转移求最大概率的可能性即可。

代码
#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
#define LL long long

void Rd(int &res){
    char c;res=0;
    while((c=getchar())<48);
    do res=(res<<3)+(res<<1)+(c^48);
    while((c=getchar())>47);
}

#define M 54
double P1[4][4]={
    {0.500,0.375,0.125},
    {0.250,0.125,0.625},
    {0.250,0.375,0.375}
};
double P2[4][4]={
    {0.60,0.20,0.15,0.05},
    {0.25,0.30,0.20,0.25},
    {0.05,0.10,0.35,0.50}
};
int T,n,A[M],Case;
char C[14];
double DP[M][4];
int Pre[M][4];
int Ans[M];
void Solve(){
    memset(DP,0,sizeof(DP));
    DP[0][0]=0.63*P2[0][A[0]];
    DP[0][1]=0.17*P2[1][A[0]];
    DP[0][2]=0.20*P2[2][A[0]];
    REP(i,0,n-1) REP(j,0,3) REP(k,0,3){
        double Val=DP[i][j]*P1[j][k]*P2[k][A[i+1]];
        if(Val>DP[i+1][k]){
            DP[i+1][k]=Val;
            Pre[i+1][k]=j;
        }
    }
    int p=0,Pos=n-1;
    REP(i,1,3)if(DP[Pos][i]>DP[Pos][p])p=i;
    while(Pos>=0){
        Ans[Pos]=p;
        p=Pre[Pos--][p];
    }
}
int main(){
    Rd(T);
    while(T--){
        Rd(n);
        REP(i,0,n){
            scanf("%s",C);
            A[i]=strlen(C);
            if(A[i]==3)A[i]=0;
            else if(A[i]==6)A[i]=1;
            else if(A[i]==4)A[i]=2;
            else A[i]=3;
        }
        Solve();
        printf("Case #%d:\n",++Case);
        REP(i,0,n){
            if(Ans[i]==0)puts("Sunny");
            else if(Ans[i]==1)puts("Cloudy");
            else puts("Rainy");
        }
    }
    return 0;
}

T4 A simple greedy problem

分析

正如题目标题所说的,
这题需要贪心一部分才能DP。
注意到在每个血量只能补一刀。
定义 Wi W i 为在 i i 这个血量补到一个兵的最小消耗值。
然后用一个栈维护之前Cnt 0 0 的位置,
贪心地以栈顶来作为最小位置。
最后的转移是一个普通的背包,注意不能超出当前背包容量。

代码

#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
#define LL long long

void Rd(int &res){
    char c;res=0;
    while((c=getchar())<48);
    do res=(res<<3)+(res<<1)+(c^48);
    while((c=getchar())>47);
}

#define M 1004
#define INF 0x3f3f3f3f

int T,n,Case,A[M],Cnt[M];
int W[M],Stk[M],Top,DP[M];
int main(){
    Rd(T);
    while(T--){
        memset(Cnt,0,sizeof(Cnt));
        Rd(n);
        REP(i,0,n)Rd(A[i]),Cnt[A[i]]++;
        memset(W,63,sizeof(W));
        Top=0;
        REP(i,1,M){
            if(!Cnt[i])Stk[++Top]=i;
            else{
                while(Top && Cnt[i]>1){
                    int Pos=Stk[Top--];
                    W[Pos]=i-Pos;
                    Cnt[i]--;
                }
                W[i]=0;
            }
        }
        memset(DP,0,sizeof(DP));

        REP(i,1,M) if(W[i]!=INF)
            DREP(j,i,W[i]) chkmax(DP[j],DP[j-W[i]-1]+1);

        int Ans=0;
        REP(i,0,M)chkmax(Ans,DP[i]);

        printf("Case #%d: %d\n",++Case,Ans);
    }
    return 0;
}

T5 Centroid of a Tree

分析

这里牵扯到树的重心的一个判定性质。
树的重心的最大子树必然不超过其他子节点数目之和。
这实际上是很容易证明的。
利用这个性质,
定义DPi,j为以i为根子树有j个节点的方案数。
然后求不满足的总个数即可。
对于有两个重心的情况,特判并特殊处理。

代码
#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
#define LL long long

void Rd(int &res){
    char c;res=0;
    while((c=getchar())<48);
    do res=(res<<3)+(res<<1)+(c^48);
    while((c=getchar())>47);
}

#define M 204
#define Mod 10007
#define Add(a,b) ((a+=b)%=Mod)

int T,Case,n;
int Next[M<<1],V[M<<1],Head[M],tot;
void Add_Edge(int u,int v){
    Next[++tot]=Head[u],V[Head[u]=tot]=v;
}
#define LREP(i,A) for(int i=Head[A];i;i=Next[i])

bool Two;
int Sz[M],Fa[M],Mx[M];

void DFS(int A,int f){
    int B;
    Sz[A]=1;
    Fa[A]=f;
    Mx[A]=0;
    LREP(i,A) if((B=V[i])!=f){
        DFS(B,A);
        Sz[A]+=Sz[B];
        chkmax(Mx[A],Sz[B]);
    }
    chkmax(Mx[A],n-Sz[A]);
}
int FindRT(){
    DFS(1,0);
    int Mn=n,RT=0;

    REP(i,1,n+1) if(Mx[i]<Mn) RT=i,Mn=Mx[i];
        else if(Mx[i]==Mn && Fa[i]==RT) RT=i;

    if(!(n&1) && Mn==n/2 && Mx[Fa[RT]]==n/2)Two=1;

    return RT;
}

int DP[M][M];
int GetDP(int A,int f){
    DP[A][0]=DP[A][1]=1;
    int B;
    LREP(i,A) if((B=V[i])!=f){
        GetDP(B,A);
        DREP(i,n-1,0) if(DP[A][i]) DREP(j,n-i,0)
            Add(DP[A][i+j],DP[A][i]*DP[B][j]);
    }
}

int F[M];
void Solve(int RT){
    GetDP(RT,0);
    int Res=0,Ans=0;

    LREP(i,RT){
        memset(F,0,sizeof(F));
        F[0]=1;
        int A=V[i],B;
        LREP(j,RT)if((B=V[j])!=A){
            DREP(i,n-1,-1) if(F[i]) DREP(j,n-i,0)
                Add(F[i+j],F[i]*DP[B][j]);
        }
        REP(x,0,n+1) REP(y,0,x)
            Add(Res,DP[A][x]*F[y]);
    }
    REP(i,1,n+1)
        Add(Ans,DP[RT][i]);
    Add(Ans,Mod-Res);
    printf("Case %d: %d\n",++Case,Ans);
}
void SolveT(int RT1){
    int RT2=Fa[RT1];
    GetDP(RT1,RT2);
    GetDP(RT2,RT1);
    int Ans=0;
    REP(i,1,n+1)
        Add(Ans,DP[RT1][i]*DP[RT2][i]);
    printf("Case %d: %d\n",++Case,Ans);
}
int main(){
    Rd(T);
    while(T--){
        Rd(n);
        memset(Head,tot=0,sizeof(Head));
        REP(i,1,n){
            int u,v;
            Rd(u),Rd(v);
            Add_Edge(u,v);
            Add_Edge(v,u);
        }
        memset(DP,0,sizeof(DP));
        Two=0;
        int RT=FindRT();
        if(Two)SolveT(RT);
        else Solve(RT);
    }
    return 0;
}

T6 A simple graph problem

分析

状态定义很容易出来:
定义 DPi,j D P i , j 为根节点为 i i 的子树有j条向上路径的最小花费。
然后转移方程可以轻易地写出 O(n3) O ( n 3 ) ,总时间复杂度 O(n4) O ( n 4 )
其实利用子树大小来进行一个小剪枝就可以卡过。
如果定义 Fi,j F i , j 为在子树A中取i条路径,在子树B中取j条路径。
令W为当前访问到的边权值。
Fi,j=min(Fi+1,j+1,DPA,i+DPB,j+Wj) F i , j = m i n ( F i + 1 , j + 1 , D P A , i + D P B , j + W ∗ j )
然后 DPA,i=min(Fij,j)(ji) D P A , i = m i n ( F i − j , j ) ( j ≤ i )
这两个转移方程都是 O(n2) O ( n 2 ) 的,加上子树大小的剪枝可以轻松通过。
然后还有更好的性质没有被利用。
实际上, DPA,j D P A , j 中的j不会到达3及以上。
如果这个j到达3及以上,
则说明在某棵子树B中同样需要3及以上的路径数与其匹配。
然后这花费的路径权值为 jLenth(A,B) j ∗ L e n t h ( A , B )
将A子树中两条路径与B子树中若干个两条路径首尾相接,
可以保证投下的人数不变,路径权值和减小。
因此DP就变成了 O(32n) O ( 3 2 ∗ n ) 的。

代码

其实这个没有 O(33n) O ( 3 3 ∗ n ) 的跑得快= =。

#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
#define LL long long
#define INF 0x3f3f3f3f

void Rd(int &res){
    char c;res=0;
    while((c=getchar())<48);
    do res=(res<<3)+(res<<1)+(c^48);
    while((c=getchar())>47);
}

#define M 544
int T,Case;
int n,K;
int Next[M],V[M],W[M],Head[M],tot;
void Add_Edge(int u,int v,int w){
    Next[++tot]=Head[u],V[Head[u]=tot]=v,W[tot]=w;
}
#define LREP(i,A) for(int i=Head[A];i;i=Next[i])
int DP[M][3],F[4][4];
int Len(int P[M<<1]){
    int x=n;
    while(x>0 && P[x]==INF)x--;
    return x;
}
void DFS(int A,int f){
    int B,w;
    DP[A][0]=DP[A][1]=0;
    LREP(i,A)if((B=V[i])!=f){
        DFS(B,A);
        w=W[i];

        REP(p,0,3) F[p+1][3]=INF;
        REP(q,0,3) F[3][q+1]=INF;

        DREP(p,2,-1) DREP(q,2,0)
            F[p][q]=min(F[p+1][q+1]+K,DP[A][p]+DP[B][q]+q*w);

        DREP(p,2,-1)
            F[p][0]=F[p+1][1]+K;

        memset(DP[A],63,sizeof(DP[A]));

        REP(j,0,3) REP(k,0,j+1)
            chkmin(DP[A][j],F[k][j-k]);
    }
}
int main(){
    Rd(T);
    while(T--){
        memset(Head,tot=0,sizeof(Head));
        Rd(n),Rd(K);
        REP(i,1,n){
            int u,v,w;
            Rd(u),Rd(v),Rd(w);
            Add_Edge(u,v,w);
        }
        memset(DP,63,sizeof(DP));
        DFS(0,-1);
        int Ans=INF;
        REP(i,0,3)chkmin(Ans,DP[0][i]+i*K);
        printf("Case #%d: %d\n",++Case,Ans);
    }
    return 0;
}

T7 Tree

分析

由于“没有自环”这个条件,
可知某些集合不能为某些点。
考虑暴力,枚举排列来计算节点的归属。
可以发现只有满足所有归属均不属于那个集合时的方案才是成立的。
每个节点的出度为1,即每个节点均在这些集合中只出现了一次。
所以节点的编号没有意义,只有集合的大小有意义。
那么就能容斥来解决这个问题。
定义 Fi F i 为有i个集合不满足要求的方案数, F0=1 F 0 = 1
Aj A j 为第j个集合的大小
状态转移方程为 Fi=Fi+Fi1Aj F i = F i + F i − 1 ∗ A j ,用类似背包的转移即可。
容斥计算,有 Ans=ni=0(i%2?1:1)Fi(ni)! A n s = ∑ i = 0 n ( i % 2 ? − 1 : 1 ) ∗ F i ∗ ( n − i ) !
最后还有一个问题,这是一个有向图,因此当某个集合中两个元素的集合大小均为0时,交换是不会产生新的结果的。
所以令p为 Aj==0 A j == 0 的数目,将答案除以 p! p ! 即可。

代码
#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
#define LL long long

void Rd(int &res){
    char c;res=0;
    while((c=getchar())<48);
    do res=(res<<3)+(res<<1)+(c^48);
    while((c=getchar())>47);
}

#define M 1004
#define Mod 1000000007
int n,A[M],F[M],Fac[M],Ans,Case;
int Pow(int x,int p){
    int Res=1,k=x;
    while(p){
        if(p&1)Res=1ll*Res*k%Mod;
        k=1ll*k*k%Mod,p>>=1;
    }
    return Res;
}
int main(){
    Fac[0]=1;
    REP(i,1,M)Fac[i]=1ll*Fac[i-1]*i%Mod;
    while(1){
        Rd(n);
        if(!n)break;
        int p=0;
        REP(i,0,n){
            int x;
            Rd(A[i]);
            REP(j,0,A[i])Rd(x);
            p+=!A[i];
        }
        memset(F,0,sizeof(F));
        F[0]=1;
        REP(i,0,n) if(A[i]) DREP(j,n,0)
            F[j]=(F[j]+1ll*F[j-1]*A[i])%Mod;

        Ans=Fac[n];
        REP(i,1,n+1)
            Ans=(Ans+(i&1?-1ll:1ll)*Fac[n-i]*F[i])%Mod;
        (Ans+=Mod)%=Mod;

        Ans=1ll*Ans*Pow(Fac[p],Mod-2)%Mod;
        printf("Case #%d: %d\n",++Case,Ans);
    }
    return 0;
}

然后这好像还是个卷积式,用NTT或许能写更大的数据。
模数比较烦,然后常数也是个问题,
将模数改为 998244353 998244353 后,NTT还是比暴力要慢= =

代码

如果有谁想卡常可以试试233
当前这份代码跑 n=104 n = 10 4 要10s
然而暴力只用0.5s

#include<bits/stdc++.h>
using namespace std;

#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
#define LL long long
#define DB double

void Rd(int &res){
    char c;res=0;
    while((c=getchar())<48);
    do res=(res<<3)+(res<<1)+(c^48);
    while((c=getchar())>47);
}

#define M 66444
#define Mm 134444
#define Mod 998244353

int Pow(int x,int p){
    int Res=1;
    for(;p;x=1ll*x*x%Mod,p>>=1)if(p&1)Res=1ll*Res*x%Mod;
    return Res;
}
int Rev[20][Mm];
void Init_Rev(int Bit){
    REP(i,0,1<<Bit)
        Rev[Bit][i]=(Rev[Bit][i>>1]>>1)|((i&1)<<(Bit-1));
}
struct ntt{
    int X[Mm],Y[Mm];
    int n,m,Bit,s;
    void NTT(int *A,int n,int p){
        REP(i,0,n)if(i<Rev[Bit][i])swap(A[i],A[Rev[Bit][i]]);
        for(int i=1;i<n;i<<=1){
            int wn=Pow(3,(Mod-1)/i/2);
            if(p)wn=Pow(wn,Mod-2);
            for(int j=0;j<n;j+=i<<1){
                int w=1;
                REP(k,j,j+i){
                    int x=A[k],y=1ll*A[k+i]*w%Mod;
                    A[k]=(x+y)%Mod,A[k+i]=(x+Mod-y)%Mod;
                    w=1ll*wn*w%Mod;
                }
            }
        }
        if(p){
            int Val=Pow(n,Mod-2);
            REP(i,0,n)A[i]=1ll*A[i]*Val%Mod;
        }
    }
    void Solve(){
        while((1<<Bit)<n+m-1)Bit++;
        s=1<<Bit;

        REP(i,n,s)X[i]=0;
        REP(i,m,s)Y[i]=0;

        NTT(X,s,0);
        NTT(Y,s,0);
        REP(i,0,s)X[i]=1ll*X[i]*Y[i]%Mod;
        NTT(X,s,1);
    }
}ntt;
int n,Ans,Fac[M],A[M];
vector<int> F;
vector<int>Find(int l,int r){
    vector<int>Res,Left,Right;
    if(l==r){
        Res.resize(4);
        Res[0]=1;
        Res[1]=A[l];
        return Res;
    }
    int mid=l+r>>1;
    Left=Find(l,mid);
    Right=Find(mid+1,r);

    ntt.n=mid-l+2;
    ntt.m=r-mid+1;

    REP(i,0,ntt.n)ntt.X[i]=Left[i];
    REP(i,0,ntt.m)ntt.Y[i]=Right[i];

    ntt.Solve();

    Res.resize(ntt.s+4);
    REP(i,0,ntt.s)Res[i]=ntt.X[i];

    return Res;
}
int main(){
    Fac[0]=1;
    REP(i,1,M)Fac[i]=1ll*Fac[i-1]*i%Mod;
    REP(i,1,18)Init_Rev(i);

    Rd(n);
    int p=0;
    REP(i,0,n){
        int x;
        Rd(A[i]);
        REP(j,0,A[i])Rd(x);
        p+=!A[i];
    }
    F=Find(0,n-1);

    Ans=Fac[n];
    REP(i,1,n+1)
        Ans=(Ans+(i&1?-1ll:1ll)*Fac[n-i]*F[i])%Mod;
    (Ans+=Mod)%=Mod;

    Ans=1ll*Ans*Pow(Fac[p],Mod-2)%Mod;
    printf("%d\n",Ans);
    return 0;
}
2018/9/15 updata

事实上并不是非常需要卡常…这个写法改改就可以用了。

New Code
#include<bits/stdc++.h>
using namespace std;

#define Uni All Right
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;++i)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;--i)
#define LREP(i,a) for(int i=Head[a];i;i=Next[i])
#define LL long long
#define Mod 1004535809

void Rd(int &x){
    static char c;x=0;
    while((c=getchar())<48);
    do x=(x<<3)+(x<<1)+(c^48); 
    while((c=getchar())>47);
}

static const int M=160004;
int n,m;
int Rev[M],W[M];
int Pool[M*40],*Allc=Pool;
int Pow(int k,int p){
    int x=1;
    for(;p;k=(LL)k*k%Mod,p>>=1)if(p&1)x=(LL)x*k%Mod;
    return x;
}
void DFT(int *A,int s,int p){
    REP(i,0,s)if(i<Rev[i])swap(A[i],A[Rev[i]]);
    W[0]=1;
    for(int i=1,pi=2;i<s;i<<=1,pi<<=1){
        int w=Pow(3,(Mod-1)/pi);
        if(p)w=Pow(w,Mod-2);
        for(int j=i-2;j>=0;j-=2)W[j+1]=(LL)(W[j]=W[j>>1])*w%Mod;
        for(int j=0;j<s;j+=pi){
            int *l=A+j,*r=A+j+i;
            REP(k,0,i){
                LL Tmp=(LL)r[k]*W[k];
                r[k]=(l[k]-Tmp)%Mod;
                l[k]=(l[k]+Tmp)%Mod;
            }
        }
    }
    if(p){
        LL inv=Pow(s,Mod-2);
        REP(i,0,s)A[i]=A[i]*inv%Mod;
    }
}
int *GetNew(int n){
    int *p=Allc;Allc+=n;
    return p;
}
struct Poly{
    int *V,n;
    Poly operator *(const Poly &_){
        Poly __;
        __.V=GetNew(__.n=n+_.n-1);

        static int a[M],b[M],s,t;
        for(t=0,s=1;s<n+_.n-1;++t,s<<=1);
        REP(i,0,s){
            a[i]=i<n?V[i]:0;
            b[i]=i<_.n?_.V[i]:0;
            Rev[i]=(Rev[i>>1]>>1)|((i&1)<<t-1);
        }
        DFT(a,s,0),DFT(b,s,0);
        REP(i,0,s)a[i]=(LL)a[i]*b[i]%Mod;
        DFT(a,s,1);
        REP(i,0,n+_.n-1)__.V[i]=a[i];

        return __;
    }
};
int Q[M],A[M];
Poly Gets(int l,int r){
    if(l==r){
        Poly _;
        _.V=GetNew(_.n=2);
        _.V[0]=1,_.V[1]=Q[l];
        return _;
    }
    int mid=l+r>>1;
    return Gets(l,mid)*Gets(mid+1,r);
}
int Fac[M];
int main(){
    Fac[0]=1;
    REP(i,1,M)Fac[i]=1ll*Fac[i-1]*i%Mod;

    Rd(n);
    int p=0;
    REP(i,0,n){
        int x;
        Rd(A[i]);
        REP(j,0,A[i])Rd(x);
        if(A[i])Q[++p]=A[i];
    }
    Poly Tmp=Gets(1,p);
    int *F=Tmp.V;

    int Ans=Fac[n];
    REP(i,1,Tmp.n)
        Ans=(Ans+(i&1?-1ll:1ll)*Fac[n-i]*F[i])%Mod;

    Ans=(LL)(Ans+Mod)*Pow(Fac[n-p],Mod-2)%Mod;
    printf("%d\n",Ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值