ZR979B. 【十联测 Day 9】唯一睿酱
ZR979B. 【十联测 Day 9】唯一睿酱
题目大意:
给定一个数组\(r_i\),表明对于第\(i\)个数来说,他是\([max(1,i - r_i),min(n,i+r_i)]\)中最大的,求有多少\(1-n\)的排列的\(r _i\)与给定的相同
\(n \le 5000\)
部分分及解题步骤
\(n \le 10\)
枚举全排列即可
\(n \le 17\)
首先这个数据范围要考虑状压DP,有关排列计数的题目,一般又要利用题目中给出的特殊性质,所以并没有所谓的套路化思维可言
所以,因为这道题和排列的大小关系有关,所以我们考虑状压DP的时候从小往大地向里面加数,设\(f_S\)表示现在\(S\)这些位置的数已经被填上了,同时满足条件的方案数
转移就枚举最后一次填了哪一个数
由于我们是从小往大去填,对于对于所有的状态为\(1\)的地方,都是比这个数要小的,同理\(0\)就是要大的,我们就可以轻松的计算出这个位置填最大值是否合法,进而转移
时间复杂度:\(O(2^nn^2)\)
\(n\le 500\)
注意上一段中我加深的部分,我们状压DP的本质是判断最大值在这个位置是否合法,这也在启示我们多项式时间复杂度的做法:
枚举最大值,类似区间DP的合并
设\(f_{l,r}\)表示\([l,r]\)这些位置的满足条件的合法排列个数,注意这个地方的所谓的排列个数实际上是\(1\)到\((r - l + 1)\)的一个排列,因为只关心大小关系的时候,排列的子集也可以看做一个排列,在合并的时候通过组合计数合成一个大的排列即可,那么我们就枚举最大值的位置\(k\)来进行转移,那么首先这个\(k\)应该满足$k - r_k = l \(或者\)k + r_k = r\(,否则就不可能成为最大值,其次还要满足\)\forall i \in [l,k - 1],i+r_i < k\(和\)\forall i\in [k + 1,r],i -r_i>k$如果左右两个区间存在一个比他更大的数,也显然不能转移
接下来想如何合并两个区间
你刚才说过,排列的子集也是一个排列,所以我们就可以得到的方程
首先,\(k\)应该满足上述条件,\(\binom{r - l}{k - l}\)的意思是一共有\(r - l\)个数(因为最大值的位置是固定的),选择\(k - l\)个放在左边,注意边界处理即可
\(n \le 5000\)
首先,我们可以发现,一个\(k\)最多也只可能会对\(k - r_k\)和\(k+r_k\)产生贡献,所以我们直接
设\(G_l\)表示能对\(l\)产生贡献的点,直接转移即可省掉枚举最大值的位置优化到\(O(n ^2)\)
总结
枚举最大值最小值可能是解题关键
首先排列的子集看做排列这一点在只关系大小关系的技术问题上十分耐用
在区间DP的时候,有时候\(f_{i,i - 1}\)的值要仔细斟酌
代码
40opts:状压DP
#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#include<set>
#include<map>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = (1 << 17) + 50;
const int mod = 1e9 + 7;
int n,m;
int f[N];
int r[N];
inline int read(){
int v = 0,c = 1;char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') c = -1;
ch = getchar();
}
while(isdigit(ch)){
v = v * 10 + ch - 48;
ch = getchar();
}
return v * c;
}
inline int count(int x){
int res = 0;
while(x){
if(x & 1) res++;
x >>= 1;
}
return res;
}
inline int get(int x,int pos){
int maxx = 0x3f3f3f3f;
int now = pos - 1;
while(now >= 0 && (x & (1 << now))) now--;
maxx = min(maxx,pos - now - 1);
now = pos + 1;
while(now < n && (x & (1 << now))) now++;
return min(maxx,now - pos - 1);
}
inline int mo(int x){
if(x >= mod) x -= mod;
return x;
}
int main(){
n = read();
for(int i = 1;i <= n;++i) r[i] = read();
f[0] = 1;
for(int i = 1;i < (1 << n);++i){
for(int j = 0;j < n;++j){
if(!(i & (1 << j))) continue;
if(get(i,j) == r[j + 1]) f[i] = mo(f[i] + f[i ^ (1 << j)]);
}
}
printf("%d\n",f[(1 << n) - 1]);
return 0;
}
80opt:n^3区间DP
#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#include<set>
#include<map>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 5005;
const LL mod = 1e9 + 7;
LL f[N][N];
LL C[N][N];
int n;
int mr[N][N],ml[N][N];
int a[N];
inline int read(){
int v = 0,c = 1;char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') c = -1;
ch = getchar();
}
while(isdigit(ch)){
v = v * 10 + ch - 48;
ch = getchar();
}
return v * c;
}
inline int mo(LL x){
if(x >= mod) x -= mod;
return x;
}
inline LL dfs(int l,int r){
if(l >= r) return 1;
if(f[l][r] != -1) return f[l][r];
f[l][r] = 0;
for(int k = l;k <= r;++k){
if((k - a[k] == l || k + a[k] == r) && mr[l][k - 1] < k && ml[k + 1][r] > k){
f[l][r] = mo(f[l][r] + dfs(l,k - 1) * dfs(k + 1,r) % mod * C[r - l][k - l] % mod);
}
}
return f[l][r];
}
int main(){
memset(f,-1,sizeof(f));
n = read();
for(int i = 1;i <= n;++i) a[i] = read();
for(int i = 0;i <= n;++i){
C[i][0] = 1;
for(int j = 1;j <= i;++j) C[i][j] = mo(C[i - 1][j] + C[i - 1][j - 1]);
}
for(int i = 1;i <= n;++i){
ml[i][i - 1] = n + 1;
mr[i][i - 1] = 0;
for(int j = i;j <= n;++j){
ml[i][j] = min(j - a[j],ml[i][j - 1]);
mr[i][j] = max(j + a[j],mr[i][j - 1]);
}
}
ml[n + 1][n] = n + 1;
printf("%lld\n",dfs(1,n));
return 0;
}
100opt n^2区间DP
#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#include<set>
#include<map>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 5005;
const LL mod = 1e9 + 7;
LL f[N][N];
int C[N][N];
int n;
int mr[N][N],ml[N][N];
int a[N];
vector <int> Gl[N << 1],Gr[N << 1];
inline int read(){
int v = 0,c = 1;char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') c = -1;
ch = getchar();
}
while(isdigit(ch)){
v = v * 10 + ch - 48;
ch = getchar();
}
return v * c;
}
inline int mo(LL x){
if(x >= mod) x -= mod;
return x;
}
inline LL dfs(int l,int r){
if(l >= r) return 1;
if(f[l][r] != -1) return f[l][r];
f[l][r] = 0;
int to = lower_bound(Gl[l].begin(),Gl[l].end(),l) - Gl[l].begin();
for(int k = to;k < (int)Gl[l].size();++k){
if(Gl[l][k] > r) break;
int i = Gl[l][k];
if(mr[l][i - 1] < i && ml[i + 1][r] > i) f[l][r] = mo(f[l][r] + dfs(l,i - 1) * dfs(i + 1,r) % mod * C[r - l][i - l] % mod);
}
to = lower_bound(Gr[r].begin(),Gr[r].end(),l) - Gr[r].begin();
for(int k = to;k < (int)Gr[r].size();++k){
if(Gr[r][k] > r) break;
int i = Gr[r][k];if(i - a[i] == l) continue;
if(mr[l][i - 1] < i && ml[i + 1][r] > i) f[l][r] = mo(f[l][r] + dfs(l,i - 1) * dfs(i + 1,r) % mod * C[r - l][i - l] % mod);
}
return f[l][r];
}
int main(){
memset(f,-1,sizeof(f));
n = read();
for(int i = 1;i <= n;++i) a[i] = read();
for(int i = 0;i <= n;++i){
C[i][0] = 1;
for(int j = 1;j <= i;++j) C[i][j] = mo(C[i - 1][j] + C[i - 1][j - 1]);
}
for(int i = 1;i <= n;++i){
ml[i][i - 1] = n + 1;
mr[i][i - 1] = 0;
for(int j = i;j <= n;++j){
ml[i][j] = min(j - a[j],ml[i][j - 1]);
mr[i][j] = max(j + a[j],mr[i][j - 1]);
}
}
ml[n + 1][n] = n + 1;
for(int i = 1;i <= n;++i){
Gl[i - a[i]].push_back(i);
Gr[i + a[i]].push_back(i);
}
for(int i = 1;i <= 2 * n;++i){
sort(Gl[i].begin(),Gl[i].end());
sort(Gr[i].begin(),Gr[i].end());
}
printf("%lld\n",dfs(1,n));
return 0;
}