HDU 6131 Loop nest 状压DP

Loop nest

Problem Description

There are m sets Pi,Qi,∀i(1≤i≤m),Pi,Qi⊆{1…i−1}. There are nested loops with m layers, and for the jth layer, the loop variable is ij, the lower bound equals max{ik(k∈Pj)}(especially, when Pj is empty set, it means the lower bound equals 1), the upper bound equals min{ik(k∈Qj)}(especially, when Qj is empty set, it means the upper bound equals n). HazelFan want to know how many times the loop body will be executed, module p.

Input

The first line contains a positive integer T(1≤T≤5), denoting the number of test cases.
For each test case:
The first line contains three positive integers m,n,p(1≤m≤15,1≤n≤109,2≤p≤109+7).
The next m lines, the ith line contains several nonnegative integers describing the sets Pi,Qi. The first integer is Ai, denoting the size of Pi, then next Ai integers denotes the elements of Pi. The next integer is Bi, denoting the size of Qi, then next Bi integers denotes the elements of Qi.

Output

For each test case:
A single line contains a nonnegative integer, denoting the answer.

Sample Input

2
2 10 233
0 0
1 1 0
6 10 987654321
0 0
1 1 0
0 0
1 3 0
0 1 4
1 2 2 1 2

Sample Output

55
3850

Source

2017 Multi-University Training Contest - Team 7


题意有些难懂……看了好久

题目大意:
现在有m层循环,第k层的循环变量是ik。每一层循环变量的值都可能与前面的循环变量有关。具体来讲,每一层循环都有两个用来刻画对ik约束的集合Pk,Qk。ik的初值为,每层循环都是for(ik=max(ix,x∈Pk);ik<=min(iy,y∈Qk);ik++)的形式。(也就是说循环的初值和结束条件会动态变化)。要求出整个程序循环的次数。


n的范围很大,模拟是会TLE的。而注意到m的范围极小,再加上题目本身就与集合扯上关系,考虑状压DP。

这道题的想法值得记录。首先,直觉告诉我们想要推得走,至少要知道循环变量的大小关系,毕竟要满足P,Q两个集合的约束。状态压缩里,0/1通常只能表示选或者是不选两种状态,那么怎么表示大小关系呢?用枚举的顺序来表示!

直接上题解。根据这些循环变量的大小关系,将大小相同的归为同一个集合。定义f[i][s]表示把s表示的循环变量归为i个集合的方案总数,那么最终的答案显然是 mk=1(nk)f[k][U],U ∑ k = 1 m ( k n ) f [ k ] [ U ] , U 为 全 集

考虑状态转移。我们规定每次转移加入的集合的元素比原有集合所有元素都大,那么有状态转移方程:

f[i][s]=f[i1][t]tsts f [ i ] [ s ] = ∑ f [ i − 1 ] [ t ] , 其 中 非 空 集 合 t 为 s 的 子 集 , 且 t 与 s 中 的 其 余 元 素 满 足 约 束

具体来讲是什么约束?就是s中所有元素在P,Q集合内都体现为不大于t中的所有元素。

注意p有可能不是质数,处理组合数时需要记录分子,每次用添加的分母暴力约分。


#include<stdio.h>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;

int M,En,lim[20],ok[40000];
ll N,mod,Ans,f[20][40000],C[20],fac[20];

ll gcd(ll a,ll b){
    if(!b)return a;
    return gcd(b,a%b);
}

void Clear(){
    Ans=0;
    memset(f,0,sizeof(f));f[0][0]=1;
    memset(lim,0,sizeof(lim));
    memset(ok,0,sizeof(ok));
    memset(fac,0,sizeof(fac));
}

int main(){
int T,i,j,k,t,x;ll tmp,d;scanf("%d",&T);
while(T--){
    Clear();
    scanf("%d%lld%lld",&M,&N,&mod);
    En=(1<<M)-1;
    for(i=1;i<=M;i++){
        scanf("%d",&k);while(k--)scanf("%d",&x),lim[i]|=1<<x-1;
        scanf("%d",&k);while(k--)scanf("%d",&x),lim[x]|=1<<i-1;
    }//lim[i]记录不能大于i的元素集合
    for(i=0;i<=En;i++)
    for(j=1;j<=M;j++)if(i>>j-1&1)ok[i]|=lim[j];
    for(i=0;i<=En;i++)
    for(t=i;t;t=i&(t-1))if((i&ok[t])==ok[t]){
        for(j=1;j<=M;j++){
            f[j][i]+=f[j-1][i^t];
            f[j][i]-=f[j][i]<mod?0:mod;
        }
    }
    C[1]=N%mod;fac[++fac[0]]=N;
    for(i=1;i<M;i++){
        fac[++fac[0]]=N-i;tmp=i+1;
        for(j=1;j<=fac[0];j++){
            d=gcd(fac[j],tmp);
            tmp/=d;fac[j]/=d;
        }//暴力约分
        C[i+1]=1;
        for(j=1;j<=fac[0];j++)C[i+1]=C[i+1]*fac[j]%mod;
    }
    for(i=1;i<=M;i++)Ans+=f[i][En]*C[i]%mod,Ans-=Ans<mod?0:mod;
    printf("%lld\n",Ans);
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值