标签:main include ble 大于等于 地方 mes 基础上 inline https
写在前头:刚考完这题(被秒)就来发题解了
另:双倍经验
考试\(DP\)爆炸了,听\(wh\)大佬一波讲解之后醍醐灌顶,来\(luogu\)切了这题。先\(orz\)一波……
先明确一点,和人没有关系。
额,好像不太对。但意思差不多,人会被抽象成编号,我们关心的只是编号,和人没有关系。
再根据数据范围就可以确定状态应该是有两维。既然与人没有关系了,那么第一维自然变成了编号,至于第二维,\(yy\)一波也不难知道是编号小于等于\(i\)的人数(额,又扯到人上了,其实前面指的是与第几个人(即人的种类)无关,人数还是要的,不然不好转移)。
确定状态后就是确定转移方程了。
首先介绍两个要用的数组\(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\)个位置,故肯定有人没地方坐,故不合法。
知道了\(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\)的上限了。
另外还有\(j\)的上限,很明显我们使编号小于等于\(i\)的人数最多只有\(sum_i\)个,上限即为\(sum_i\).
\(i\)直接从\(1-n\)跑一遍即可.
目标:\(\exists sum_i<i\lor f_{n,n}\)
最后附上代码,(别忘了这题是个双倍经验:-)):
#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;
}
标签:main include ble 大于等于 地方 mes 基础上 inline https
原文地址:https://www.cnblogs.com/ALANALLEN21LOVE28/p/11313040.html