hdu4917 Permutation(拓扑排序+状压DP)

bobo has a permutation p 1,p 2,…,p n of 1,2,…,n.

Knowing m extra constraints of form p ai

思路:

最开始一直在想容斥原理,把每个限制条件不符合的情况减去,再把两个一起不符合的加上…然而根本不会实现,情况太多了。
后来看了题解,实际上可以将这一题的题意简化成求DAG的拓扑序列数量。好像瞬间简单了好多!然后因为每个联通的DAG中点数很少,可以压缩状态,每一位表示这一个点有没有在序列中。更新某个点的时候保证他的所有前驱都已经在序列中了就行了。具体我写注释里。

//请无视调试,当时调的心态很崩
#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>
#define int long long
using namespace std;
const int MOD = 1e9+7;
const int N = 41;
const int M = 22;
const int E = (1<<21);
int n, m, fa[N], tot, ans, inv[N], indgr[N];
vector<int> to[N];
vector<int> vtx[N];
bool vis[N];
int num[N], maxst, f[E], pre[N];

void INITIALIZE()
{
    inv[1] = 1;
    for (int i = 2; i < N; i++)
        inv[i] = (MOD-MOD/i)*inv[MOD%i]%MOD;
}

int C(int x, int y)
{
    // cout << x << " " << y << endl;
    int tmp = 1;
    for (int i = x; i >= x-y+1; i--)
        tmp = tmp*i%MOD;
    for (int i = y; i >= 2; i--)
        tmp = tmp*inv[i]%MOD;
    // cout << tmp << "!" << endl;
    return tmp;
}

int FIND(int u)
{
    if (fa[u] == u) return u;
    return fa[u] = FIND(fa[u]);
}

int WORK(int now)//WORK()返回一个连通块的拓扑序列方案数
{
    int nn = vtx[now].size();
    maxst = (1<<nn);
    memset(pre, 0, sizeof(pre));
    queue<int> q;
    for (int i = 0; i < nn; i++)
        if (indgr[vtx[now][i]] == 0)
            q.push(vtx[now][i]); //cout << vtx[now][i] << "!" << endl;
    while (!q.empty()){//先拓扑排序,把前驱预处理出来
        int u = q.front();
        q.pop();
        for (int i = 0; i < to[u].size(); i++){
            int v = to[u][i];
            // cout << num[u] << " " << num[v] << "!" << endl;
            indgr[v]--;
            pre[num[v]] = (pre[num[v]]|(1<<num[u]));
            if (indgr[v] == 0)
                q.push(v);
        }
    }
    // for (int i = 0; i < nn; i++)
        // cout << pre[i] << " "; cout << "@" << endl;
    memset(f, 0, sizeof(f));
    f[0] = 1;
    for (int i = 0; i < maxst; i++)//状压DP
        for (int j = 0; j < nn; j++)
            if (((~i)&(1<<j)) && (pre[j]&i) == pre[j]){//找是0的位,并保证他的前驱已经全部在序列中了
                //更新的过程可以看成已经放好了1~x-1,把x放在编号j的位子上
                int nxt = i|(1<<j);
                // cout << i << " " << nxt << "#" << endl;
                f[nxt] = (f[nxt]+f[i])%MOD;
            }
    // cout << f[maxst-1] << "^" << endl;
    return f[maxst-1];
}

main()
{
    INITIALIZE();
    while (scanf("%lld%lld", &n, &m) == 2){
        for (int i = 1; i <= n; i++)
            fa[i] = i, to[i].clear(), vtx[i].clear(), num[i] = 0;
        memset(indgr, 0, sizeof(indgr));
        for (int i = 1; i <= m; i++){//用并查集判断联通块
            int x, y;
            scanf("%lld%lld", &x, &y);
            to[x].push_back(y);
            indgr[y]++;
            fa[FIND(x)] = FIND(y);
        }
        tot = 0;
        memset(vis, 0, sizeof(vis));
        for (int i = 1; i <= n; i++)
            if (!vis[FIND(i)]){
                vis[FIND(i)] = 1;
                tot++;
                for (int j = i; j <= n; j++)
                    if (FIND(j) == FIND(i))
                        num[j] = vtx[tot].size(), vtx[tot].push_back(j);
               //vtx[]记录一个联通块中的所有点编号,再用num给同一个联通块的点再编一个号,用于状压
            }
        // cout << "tot" << tot << endl;
        // for (int i = 1; i <= n; i++) cout << num[i] << " "; cout << endl;
        ans = 1;
        for (int i = 1; i <= tot; i++)
            ans = ans*WORK(i)%MOD*C(n, vtx[i].size())%MOD, n -= vtx[i].size();
        printf("%lld\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值