题解 P1386 座位安排

Posted alanallen21love28

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解 P1386 座位安排相关的知识,希望对你有一定的参考价值。

写在前头:刚考完这题(被秒)就来发题解了

:双倍经验

\(first\)

考试\(DP\)爆炸了,听\(wh\)大佬一波讲解之后醍醐灌顶,来\(luogu\)切了这题。先\(orz\)一波……

\(second\)

先明确一点,和人没有关系。

额,好像不太对。但意思差不多,人会被抽象成编号,我们关心的只是编号,和人没有关系。

再根据数据范围就可以确定状态应该是有两维。既然与人没有关系了,那么第一维自然变成了编号,至于第二维,\(yy\)一波也不难知道是编号小于等于\(i\)的人数(额,又扯到人上了,其实前面指的是与第几个人(即人的种类)无关,人数还是要的,不然不好转移)。

确定状态后就是确定转移方程了。

\(third\)

首先介绍两个要用的数组\(sum,cnt\),其中\(cnt_i\)指的是编号被强制定为\(i\)的人的数量,\(sum_i\)则是记录\(\sum_j^i cnt_j+n-m\)的值,看到这里,应该有同学瞬间反应过来我要干什么了。别急,一步步看。

大家其实发现了,\(sum_i\)记录的不单单只是一个类似\(cnt\)前缀和的东西,更重要的作用是记录当前有多少人的编号能够小于等于\(i\).

同时这也是在\(\sum cnt\)的基础上加上一个\(n-m\)的原因,加上的正是没有确定编号的人,让\(sum\)的作用能够转移方程且能判合法性。

易知:当前方案不合法时当且仅当\(\exists sum_i<i\),因为若编号能够小于等于\(i\)的人数少于\(i\)个,前面的位置必将有空位,因为是\(n\)个人对应\(n\)个位置,故肯定有人没地方坐,故不合法。

\(forth\)

知道了\(sum\)的真正意义,那么转移方程也好推了。

先把方程给出来(\(f_i,j\)表示的是当前在编号\(i\)(之后可能有不合法的情况都不管,先搞当前的\(i\)),编号小于等于\(i\)的人的数量有\(j\)的方案数):
\[f_i,j=\sum_k=cnt_i^j-i+1f_i-1,j-k* C_sum_i-1-j+k^k-cnt_i\]

一步步地看这个式子。

首先\(k\)是枚举编号为\(i\)的人的数量,很明显,下限为\(cnt_i\).那么在之前的\(i-1\)个编号中肯定搞定了剩下的\(j-k\)个人,于是有了\(f_i-1,j-k\),但除了固定的\(cnt_i\)个人编号必须为\(i\)之外,还必须找\(k-cnt_i\)个自由人使得编号为\(i\)的总人数为\(k\),而这\(k-cnt_i\)个人得由我们在编号\(i-1\)及之前的人中(即\(sum_i-1\))且还未安排的人数中(即没被搞定,被搞定的人已经得出是\(j-k\))选出,即在\(sum_i-1-(j-k)=sum_i-1-j+k\)个人中选出\(k-cnt_i\)个人,组合数得到。

但还有一个费解的地方是\(j\)\(k\)的循环范围,这个稍微想一下就可以知道了。在式子\(f_i,j\)中,很明显成立的条件是\(j>=i\),只有当小于等于当前编号\(i\)的人数大于等于\(i\)才会使前面没有空缺,也才会满足题意,用这个不等式推一下就可以看出\(j\)的下限与\(k\)的上限了。

\(fifth\)

另外还有\(j\)的上限,很明显我们使编号小于等于\(i\)的人数最多只有\(sum_i\)个,上限即为\(sum_i\).

\(i\)直接从\(1-n\)跑一遍即可.

目标:\(\exists sum_i<i\lor f_n,n\)

\(sixth\)

最后附上代码,(别忘了这题是个双倍经验:-)):

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;++i)
#define fdr(i,a,b) for(int i=a;i>=b;--i)
#define int long long
#define jinitaimei signed
inline int read()

    int x=0;
    char ch=getchar();
    for(;!isalnum(ch);ch=getchar());
    for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
    return x;

const int xx=3e2+10;
int f[xx][xx],sum[xx],cnt[xx],c[xx][xx];
inline void sol()

    memset(f,0,sizeof(f));
    memset(sum,0,sizeof(sum));
    memset(cnt,0,sizeof(cnt));
    int n=in,m=in,mod=in;
    fur(i,0,n)
    
        c[0][i]=1;
        fur(j,1,i) c[j][i]=(c[j-1][i-1]+c[j][i-1])%mod;
    
    fur(i,1,m)
    
        int x=in;x=in;
        ++cnt[x];
    
    sum[0]=n-m;
    fur(i,1,n)
    
        sum[i]=sum[i-1]+cnt[i];
        if(sum[i]<i)
        
            puts("NO");
            return;
        
    
    f[0][0]=1;
    fur(i,1,n) fur(j,i,sum[i]) fur(k,cnt[i],j-i+1) (f[i][j]+=f[i-1][j-k]*c[k-cnt[i]][sum[i-1]-j+k])%=mod;
    printf("YES %lld\n",f[n][n]);

jinitaimei main()

    int t=in;
    while(t--) sol();
    return 0;

以上是关于题解 P1386 座位安排的主要内容,如果未能解决你的问题,请参考以下文章

天梯赛题解 L1-049 天梯赛座位分配

题解 AtCoder ARC 076 F - Exhausted? (霍尔定理+线段树)

题解晚餐队列安排

CH301 任务安排2 题解报告

NOIP模拟 Problem 1 任务安排 题解

P5151 HKE与他的小朋友 题解