题意:
有2n张牌,一次操作为:
- 从堆顶拿一张牌
- 如果牌堆空了直接去步骤3。否则猜测下一张牌是否比本牌大,猜对继续,猜错去步骤3。
- 结束操作。
现在你的策略是,如果本张牌是1~n就猜下张大,否则猜小。询问2n张牌的所有排列,使用该策略,总共能摸到多少张牌,答案对m取模。
注意:就算没有猜对,拿到的牌也不会放回去。
思路:
从1到n的数叫小数,以0表示,n+1到2n的数叫大数,用1表示。
由于每次最后一张牌永远能拿到
,所以答案先加一个2n的阶乘(即所有排列方式)。
注意到,持续摸牌的阶段,摸到的牌一定是小数递增序列和大数递减序列交替
dp[ i ][ j ][ 0/1 ]表示拿了i张小数,j张大数,当前位置是小数递增/大数递减。
转移时枚举交替位置
,小数由大数转移而来,枚举连续的小数k,由于连续的k个牌必须是递增
的,从n-(i+k)个剩余可选的数里选k个,必须有序
应用组合数
即 d p [ i ] [ j ] [ 0 ] = d p [ i − k ] [ j ] [ 1 ] ∗ C ( n − i + k , k ) dp[ i ][ j ][ 0 ] = dp[ i - k ][ j ][ 1 ]*C(n-i+k,k) dp[i][j][0]=dp[i−k][j][1]∗C(n−i+k,k)
在一轮dp中,我们的i+j是确定的,在dp时计算答案,剩余未摸的牌摸不到,所以怎么排序都可以,求一个全排列即可。
注意m不一定为质数,所以求组合数的时候应用杨辉三角
求,不能用逆元。
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
const int mod = 998244353;
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 3050;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
int C[N][N];
int dp[N][N][2];
int fac[N];
int n,m;
void init(){
fac[0]=1;
for(int i=1;i<=2*n;++i){
fac[i]=fac[i-1]*i%m;
}
for(int i=0;i<=2*n;++i){
C[i][0]=C[i][i]=1;
if(i==0)continue;
for(ll j=1;j<=i-1;j++) {
C[i][j]=(C[i-1][j]+C[i-1][j-1])%m;
}
}
for(int i=0;i<=n;++i){
for(int j=0;j<=n;++j){
dp[i][j][0]=dp[i][j][1]=0;
}
}
}
void work() {
cin>>n>>m;
init();
ll ans=fac[2*n]%m;
dp[0][0][0]=dp[0][0][1]=1;//大小数都不选的情况只有1个
for(int i=0;i<=n;++i){
for(int j=0;j<=n;++j){
for(int k=1;k<=i;++k){//选了i-k个小数,j个大数,从大数到小数转移
dp[i][j][0]=(dp[i][j][0]+dp[i-k][j][1]*C[n-i+k][k])%m;
}
for(int k=1;k<=j;++k){//选了j-k个小数,i个大数,从小数到大数转移
dp[i][j][1]=(dp[i][j][1]+dp[i][j-k][0]*C[n-j+k][k])%m;
}
if((i==0&&j==0)||(i==n&&j==n))continue;//当全没拿或者全拿完时都无法更新答案
ans=(ans+(dp[i][j][0]+dp[i][j][1])*fac[2*n-i-j])%m;
}
}
cout<<ans<<'\n';
}
signed main() {
io;
int t=1;
cin >> t;
while (t--) {
work();
}
return 0;
}
E-Koraidon, Miraidon and DFS Shortest Path
题意:
给定一个有向联通图,询问该图是否满足从1开始无论怎样dfs,每个点获得的dis都始终是一个。
思路:
支配树
。钦定s为入口,若y为x的支配点,那么从s走到x的每一条路径都会经过y,即y是x的必经之路。
求支配点:
朴素
方法为O(n^3)dfs,试删去每个点查看其余哪个点不再能到达,即求得支配点。
进阶
方法为O(n^2)数据流迭代法,一个点的支配点的点集为它所有父结点的支配点集的交集
和它本身,根据该结论将每个结点上的支配点集不断迭代直至答案不变即可。为了提高效率,我们希望每轮迭代时,当前迭代的结点的所有父结点尽可能都已经执行完了这次迭代,因此我们要用拓扑序求。DAG上的特例:
由上述进阶法结论可知,u的支配点一定是其所有父结点在支配树上的公共祖先,那么显然u的
直接支配点
是所有父结点在支配树上的LCA
。
在本题中,我们可以把1作为起始点,bfs出每个点的最小深度,bfs时忽略所有的
返祖边
和横向边
,得到一个DAG分层图
,bfs出队顺序即为分层图拓扑序
(但不是原图拓扑序),顺便lca一下,得到每个点的支配点集
的路径,再dfs检验:,对于每一条有向边连接的两点( u → v u \to v u→v)有三种情况:
- dep[u]=dep[v],说明该边是一条
横向边
,从1走到uv的路显然不是一条,那么走u的路去v,v将额外获得一个dep[u]+1的深度,显然dfs会出现不同结果。- dep[u]<dep[v],至少在u点中,v不会出现问题。
- dep[u]>dep[v],说明该边是一条
返祖边
,如果v是u的支配点,说明出现有向环,走到u再走到v必经过过v,成立;如果v不是u的支配点,当从不经过v走到u的路径走到v时,v会分别获得一个大于u一个小于u的深度,显然错误。
AC代码
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
const int mod = 998244353;
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 5e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
vector<int>e[N];
int dep1[N],dep2[N];//dep1原图深度,dep2分层图深度
int f[N][20];//倍增支配点
vector<int>fa[N];//fa[i][j],j是i的一个父亲
int n,m;
bool vis[N],ok=1;
int lca(int u,int v){//v>u
if(dep2[u]>dep2[v]){
swap(u,v);
}
while (dep2[v] > dep2[u]) {
v = f[v][__lg(dep2[v] - dep2[u])];
}
if(u==v)return u;
for(int i=19;i>=0;--i){
if(f[u][i]!=f[v][i]){
u=f[u][i];
v=f[v][i];
}
}return f[u][0];
}
void bfs(){
bool ok=0;
queue<int>q;
q.push(1);dep1[1]=dep2[1]=1;
while(!q.empty()){
int t=q.front();q.pop();
if(t!=1){
f[t][0]=fa[t][0];
for(auto x:fa[t]){
f[t][0]=lca(f[t][0],x);
}
dep2[t]=dep2[f[t][0]]+1;
for (int i=1;i<20;++i) {
f[t][i] = f[f[t][i - 1]][i - 1];
}
}
for(auto x:e[t]){
if(dep1[x]==0||dep1[x]>dep1[t]){//未访问或者dep2[x]-1=dep2[t]即下一层,成(非有向)环。
fa[x].pb(t);
}if(!dep1[x]){
dep1[x]=dep1[t]+1;
q.push(x);
}
}
}
}
void dfs(int t){
vis[t]=1;
for(auto x:e[t]){
if(dep1[x]==dep1[t]){//横向边,说明x点存在至少两种深度
ok=0;
}else if(dep1[x]>dep1[t]){//x在t的下层
if(!vis[x]) dfs(x);
}else{//x在t的上层
if(lca(x,t)!=x){//如果x不是t的一个支配点,说明x存在两种可被遍历的方式,深度分别大于和小于t的深度
ok=0;
}
}
}
}
void work() {
cin>>n>>m;
ok=1;
for(int i=1;i<=n;++i){
e[i].clear();
fa[i].clear();
dep1[i]=dep2[i]=vis[i]=0;
for(int j=0;j<20;++j){
f[i][j]=0;
}
}
for(int i=1;i<=m;++i){
int u,v;cin>>u>>v;
if(u==v)continue;
e[u].pb(v);
}
bfs();dfs(1);
if(ok)cout<<"Yes\n";
else cout<<"No\n";
}
signed main() {
io;
int t=1;
cin >> t;
while (t--) {
work();
}
return 0;
}
只会DAG不会别的支配树QaQ