[CF1515E]Phoenix and Computers
壹、题目描述 ¶
贰、题解 ¶
题解注意到了我们最后开电脑地序列构成是 "一段手动开 + 一个自动开 + 一段手动开 + 一个自动开 + ... + 一个自动开 + 一段手动开",如果你手动开一个长度为 \(k\) 的电脑序列,方案数为 \(2^{k-1}\),然后,题解就设置状态 \(f_{i,j}\) 表示前 \(i\) 个电脑,前 \(i-1\) 个手动开,第 \(i\) 个自动开的方案数,然后进行转移。
更详细的解说看这里。
更想写的是连续段 \(\tt DP\),或者说插头 \(\tt DP\)?定义状态 \(f_{i,j,k}\) 表示有 \(i\) 个灯已经亮了(不管手动还是自动),有 \(j\) 个亮着的灯的段,有 \(k\) 个段靠墙了,那么最后的答案就是 \(f_{n,1,2}\),对于转移,我们讨论一下它可能放的位置即可。注意每种情况点亮的灯的个数。
值得一提的是题解复杂度为 \(\mathcal O(n^3)\),然而连续段 \(\tt DP\) 的复杂度仅仅 \(\mathcal O(n^2)\)!
叁、参考代码 ¶
这是插头 \(\tt DP\) 的代码。
#include<cstdio>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define Endl putchar('\n')
#define mp(a, b) make_pair(a, b)
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define fep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
typedef long long ll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
const int maxn=400;
int n, mod;
int f[maxn+5][maxn+5][3];
inline void add(int& x, int delta){ x=(x+delta)%mod; }
signed main(){
n=readin(1), mod=readin(1);
// turn on the first list "manually"
f[1][1][0]=1; f[1][1][1]=2;
rep(i, 1, n-1){
rep(j, 1, i) rep(k, 0, min(2, j)){
// have nothing to do with any segment
add(f[i+1][j+1][k], 1ll*f[i][j][k]*(j+1-k)%mod);
// stand alone on the edge
if(k<2) add(f[i+1][j+1][k+1], 1ll*f[i][j][k]*(2-k)%mod);
// stand by a segment
// just close
add(f[i+1][j][k], 1ll*f[i][j][k]*((j<<1)-k)%mod);
// pull another light into the water
add(f[i+2][j][k], 1ll*f[i][j][k]*((j<<1)-k)%mod);
// connect the two segments
// two closed light
add(f[i+2][j-1][k], 1ll*f[i][j][k]*(j-1)*2%mod);
// three closed light
add(f[i+3][j-1][k], 1ll*f[i][j][k]*(j-1)%mod);
// connect an edge with a segment
if(k<2) add(f[i+1][j][k+1], 1ll*f[i][j][k]*(2-k)%mod);
// pull another light into the water
if(k<2) add(f[i+2][j][k+1], 1ll*f[i][j][k]*(2-k)%mod);
}
}
printf("%d\n", f[n][1][2]);
return 0;
}
肆、用到 の Trick ¶
为什么遇到一段一段的这种题没有往插头 \(\tt DP\) 想呢?可能是题做少了吧,也有可能是自己太菜了......
这里有一道之前做过的插头 \(\tt DP\) 的题:传送门。