zoukankan      html  css  js  c++  java
  • 题解 P1386 【座位安排】

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

    :双倍经验

    (first)

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

    (second)

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

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

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

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

    (third)

    首先介绍两个要用的数组(sum,cnt),其中(cnt_i)指的是编号被强制定为(i)的人的数量,(sum_i)则是记录(sum_{j=1}^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<ilor 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
    ",f[n][n]);
    }
    jinitaimei main()
    {
    	int t=in;
    	while(t--) sol();
    	return 0;
    }
    
  • 相关阅读:
    vue input输入框长度限制
    vue中input输入框的模糊查询实现
    腾讯云服务器配置node环境
    axios中的this指向问题
    腾讯云服务器 ubuntu 设置允许root用户登录
    nodejs+express+mongodb写api接口的简单尝试
    通过fromdata实现上传文件
    阿姆斯特朗数
    Mac上webstorm与git仓库建立连接
    iOS学习——属性引用self.xx与_xx的区别
  • 原文地址:https://www.cnblogs.com/ALANALLEN21LOVE28/p/11313040.html
Copyright © 2011-2022 走看看