ARC138F 题解
DaiRuiChen007 · · 题解
Problem Link
题目大意
给定
n 个点(1,a_1)\sim (n,a_n) ,a 是1\sim n 排列,你要对这个点集进行若干次操作,得到一个序列。如果
n=1 ,序列中只含有这个点,否则选择如下两种操作之一:
- 选择一个
x ,把i<x 的点操作得到的序列和x\le i 的点操作得到的序列拼起来。- 选择一个
y ,把a_i<y 的点操作得到的序列和y\le a_i 的点操作得到的序列拼起来。求有能得到多少种不同的序列。
数据范围:
n\le 30 。
思路分析
计数操作序列是容易的,直接
但我们无法把操作序列和结果序列建立对应,考虑对一个若干等价的操作序列定义代表元。
对于一组结果序列,我们定义其代表元为每次操作贪心地取字典序最小的一个时的操作序列,其中所有操作的字典序按如下顺序排列:
考虑
那么我们 dp 时枚举最小操作
不妨容斥,设
同理如果
如果想让得到的结果序列相同,则
注意我们枚举的操作一定是非平凡操作,即
不断递归子问题即可,可以用记忆化搜索实现,用状压记录状态比较好写。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=1e9+7;
int n,a[35];
unordered_map <int,ll> Q;
ll dp(int s) {
if(Q.count(s)) return Q[s];
if(__builtin_popcount(s)<=1) return 1;
vector <int> xi,yi;
for(int i=0;i<n;++i) if(s>>i&1) xi.push_back(i),yi.push_back(i);
sort(yi.begin(),yi.end(),[&](int i,int j){ return a[i]<a[j]; });
int k=xi.size();
vector <int> st;
for(int i=0,xt=0,yt=0;i<k-1;++i) {
st.push_back(xt|=1<<xi[i]);
st.push_back(yt|=1<<yi[i]);
}
vector <ll> f(2*k-2);
ll ans=0;
for(int i=0;i<2*k-2;++i) {
f[i]=dp(st[i]);
for(int j=0;j<i;++j) if((st[j]&st[i])==st[j]) f[i]=(f[i]-f[j]*dp(st[i]^st[j]))%MOD;
if(f[i]<0) f[i]+=MOD;
ans=(ans+f[i]*dp(s^st[i]))%MOD;
}
return Q[s]=ans;
}
signed main() {
scanf("%d",&n);
for(int i=0;i<n;++i) scanf("%d",&a[i]);
printf("%lld\n",dp((1<<n)-1));
return 0;
}