码迷,mamicode.com
首页 > 其他好文 > 详细

题解 P1386 【座位安排】

时间:2019-08-07 09:36:58      阅读:111      评论:0      收藏:0      [点我收藏+]

标签:main   include   ble   大于等于   地方   mes   基础上   inline   https   

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

:双倍经验

\(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+1}f_{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 【座位安排】

标签:main   include   ble   大于等于   地方   mes   基础上   inline   https   

原文地址:https://www.cnblogs.com/ALANALLEN21LOVE28/p/11313040.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!