codeforces 107C Arrangement (状压dp)

题意:

给出n个人和n个座位,给出m对限制,每对限制要求ai这个人的座位要排在bi这个人的前面。现在要求满足条件的第y-2001大的字典序的座位排列。

题解:
题目挺好的,处理方法很特别。对于这样的题目,我们首先想想暴力,那就是枚举从1开始的各种满足条件的序列,其实在枚举的时候我们可以这样优化,首先枚举第一个作为排的人,然后以排好的人为基准,往下继续排计算出对应的排列数,如果排列数大于想在的y,那么这位排的人是合法的,如果小于则y减去得到的排列数。排第二位的人,重复刚才的操作。

对于排列数的计算可以用dp,状压dp在O(2^n)算出来,时间完全够。


#include<iostream>
#include<math.h>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<vector>
#include<map>
#include<set>
using namespace std;
#define B(x) (1<<(x))
typedef long long ll;
const int oo=0x3f3f3f3f;
const ll OO=1LL<<61;
const ll MOD=10007;
const int maxn=100000+5;
ll dp[B(16)+6];///表示在某个i之前的位子确定人后,状态为s时排列的个数
int ans[20],g[20],mark[20],full;

int main()
{
    int n,m,a,b,f;
    ll y;
    while(scanf("%d %I64d %d",&n,&y,&m)!=EOF)
    {
        memset(g,0,sizeof g);
        memset(ans,-1,sizeof ans);
        memset(mark,0,sizeof mark);
        for(int i=0;i<m;i++)
        {
            scanf("%d %d",&a,&b);
            g[--b]|=B(--a);
        }
        f=1;
        y-=2001;
        full=B(n)-1;
        for(int i=0;i<n&&f;i++)
        {
            for(ans[i]=0;;ans[i]++)
            if(!mark[ans[i]])
            {
                if(ans[i]>=n) { f=0; puts("The times have changed"); break; }
                memset(dp,0,sizeof dp);
                dp[0]=1;
                for(int s=0;s<=full;s++)
                if(dp[s])
                {
                    int cnt=__builtin_popcount(s);
                    for(int k=0;k<n;k++)
                    if(!(s&B(k)))
                    if((ans[k]==-1||ans[k]==cnt)&&((s&g[k])==g[k]))
                        dp[s|B(k)]+=dp[s];
                }
                if(y>=dp[full])
                    y-=dp[full];
                else
                {
                    mark[ans[i]]=1;
                    break;
                }
            }
        }
        if(!f)continue;
        for(int i=0;i<n;i++)
            printf("%d%c",ans[i]+1,i+1==n?'\n':' ');
    }
    return 0;
}




你可能感兴趣的:(codeforces 107C Arrangement (状压dp))