1. 程式人生 > 實用技巧 >【Luogu】 CF107C Arrangement 題解

【Luogu】 CF107C Arrangement 題解

網上的題解大都不是很明白(註釋都沒有),我這裡參考了一下這篇題解:點這裡,同時加入了很多註釋方便大家理解。

首先,\(n\)很小,容易想到狀壓。

我們可以依次確定排列中每一位應該坐哪個人。那麼,我們只需要知道某一位選輩分為\(j\)的人的方案數。

\(f_{i,j}\)表示當前狀態的方案數,其中\(i\)是一個二進位制數,值為\(1\)的位表示對應的座位已經安排給了輩分為\(1 \sim cnt_i\)的人(\(1\)為最老,\(cnt_i\)表示\(i\)在二進位制下\(1\)的位數),\(j\)表示第\(x\)個座位確定由輩分為\(j\)的人來坐。

最終結果是\(f_{{2^n-1},y}\)

\(y \in [1,n]\)

如何轉移呢?我們可以列舉輩分\(cnt_i+1\)的人坐哪個位置,進行轉移即可。(具體見程式碼中的註釋)

Code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int BIT=(1<<17),M=110,N=25;
int n,m,cnt[BIT],a[M];
ll f[BIT][N],b[N],p[N],year;
//f[i][j]
//i中1的位表示對應的座位已經安排給了輩分為1~cnt[i]的人(1為最老)
//j表示第x位確定安排給輩分為j的人坐
//b[i]表示輩分為i的人在哪個位置

//輩分為1~n,1為最老,n為最年輕
void solve(int x){
    memset(f,0,sizeof(f));
    f[0][0]=1;int maxb=(1<<n);
    //按輩分從老到年輕依次安排座位
    for(int i=0;i<maxb;i++){
        for(int j=0;j<=n;j++){
            int y=cnt[i]+1;
            if(b[y]){//若輩分為cnt[i]+1的人已經安排了座位
                //安排過的座位不滿足這一個座位的要求,continue
                if((a[b[y]]&i)!=a[b[y]]) continue;
                //當前座位已經被佔用了,也continue
                if((1<<(b[y]-1)&i)==(1<<b[y]-1)) continue;
                //如果輩分為cnt[i]+1的人剛好坐在x位置上,那麼j=cnt[i]+1
                if(b[y]==x) f[i|(1<<b[y]-1)][y]+=f[i][j];
                //否則繼續保持原來的j
                else f[i|(1<<b[y]-1)][j]+=f[i][j];
            }
            else{
                for(int k=1;k<=n;k++){//列舉輩分為cnt[y]+1的人應該放哪個位置
                    if(!(i&(1<<k-1))){
                        if((a[k]&i)!=a[k]) continue;
                        //與上面類似的轉移
                        if(k==x) f[i|(1<<k-1)][y]+=f[i][j];
                        else f[i|(1<<k-1)][j]+=f[i][j];
                    }
                }
            }
        }
    }
}

int main(){
    scanf("%d%lld%d",&n,&year,&m);year-=2000;
    //cnt[i]表示i在二進位制下1的個數
    for(int i=1;i<BIT;i++) cnt[i]=cnt[i^(i&(-i))]+1;
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y);
        a[y]|=(1<<x-1);
        //a[i]是一個二進位制數,其中1的位表示 這一位對應的
        //座位上坐的人 的輩分 必須比 i座位上坐的人 的輩分 大
    }
    for(int i=1;i<=n;i++){
        solve(i);ll sum=0;
        for(int j=1;j<=n;j++){
            if(!b[j]&&sum+f[(1<<n)-1][j]>=year){
                //剛好加上這個位置坐輩分為j的人的方案數可以大過year
                b[j]=i;p[i]=j;
                year-=sum;break;
            }
            sum+=f[(1<<n)-1][j];
        }
        if(!p[i]){//這一位無法確定,無解
            puts("The times have changed");
            return 0;
        }
    }
    for(int i=1;i<=n;i++) printf("%lld ",p[i]);
    puts("");
    return 0;
}