B.Tree Burning
最后一定存在一个分界点,满足在分界点两边的点反复横跳(点多的一边会先走几步),记录所有点顺时针/逆时针到原点的前缀和,枚举分界点取 即可。
*C.Coloring Torus
直接构造 的矩阵,第 行填 。
,发现构造奇数行 ,偶数行 一定合法(若 ,直接 )。所以 (强制有偶数行)
D.Inversion Sum
期望的DP十分套路:
设
表示
的概率,每次操作涉及到的转移是
的。
求出总期望再乘上 即可。
*E.Less than 3
考虑可以被翻转的
需要满足的条件,要么左右都是
,要么有且只有一边与一个
相邻。
发现原序列中存在的所有连续的
(或连续的
)的这些段永远存在(不可能
变成
),且可能会在原来的若干段中分割出新的不同的子段(如
,多了一个
段)
接着需要一步神仙转化:
在
之间加一个红色隔板,
之间加一个蓝色隔板,序列就是一堆红蓝相间的隔板,且设两端都有无穷个隔板。
每次取反相当于把某个隔板移动一位,且隔板间不能互相跨越,即相对位置保持不变。
所以知道了最终态的每个隔板的位置后,所有初始隔板是和其一一对应的,最小的移动代价就是对应隔板的位置之差。
枚举在序列开头/末尾多插了 个隔板的代价,取 即可。复杂度
*F.Permutation and Minimum
巧妙的值域上DP
不考虑 且 的位置,剩下的为 的情况。
由于 的数都会被选用,考虑值域序列 , 和 的匹配类似于括号序列:
- 对于 ,处理时忽略它们的位置关系,相当于一个括号匹配,最后乘上 对数的阶乘
- 对于 ,相当于其中一个括号是带标号的,分别考虑 为左,右括号的情况即可。
具体来说,倒序DP(正序似乎也可以,不过倒着更好理解),设 表示完值域 ,还有 个普通右括号, 个带标号右括号未匹配的方案数,具体转移见代码。
注意这里并非传统意义上的括号匹配——所有右括号是等价的(包括有标号的),同一个左括号( )匹配不同右括号的情况是等价的( 一样),所以实际上DP的是左括号的不同分配方式。
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
typedef long long ll;
int n,a[305],ans,m,num,frc=1;
int f[2][305][305],typ[605];
inline void ad(int &x,int y){x+=y;if(x>=mod) x-=mod;}
int main(){
int i,j,k,pr=0;
scanf("%d",&n);m=n+n;
for(i=1;i<=m;++i) scanf("%d",&a[i]);
for(i=1;i<=m;i+=2)
if((a[i]==-1)||(a[i+1]==-1)){
num+=(((~a[i])||(~a[i+1]))^1);
if(~a[i]) typ[a[i]]=1;if(~a[i+1]) typ[a[i+1]]=1;
}else typ[a[i]]=typ[a[i+1]]=2;
for(i=1;i<=num;++i) frc=(ll)frc*i%mod;
f[0][0][0]=1;num=0;
for(i=m;i;--i) if(typ[i]!=2){
pr^=1;memset(f[pr],0,sizeof(f[pr]));
for(j=0;j<=num;++j)
for(k=0;j+k<=num;++k) if(f[pr^1][j][k]){
if(typ[i]){
ad(f[pr][j][k+1],f[pr^1][j][k]);
if(j) ad(f[pr][j-1][k],(ll)f[pr^1][j][k]%mod);
}else{
ad(f[pr][j+1][k],f[pr^1][j][k]);
if(k) ad(f[pr][j][k-1],(ll)k*f[pr^1][j][k]%mod);
if(j) ad(f[pr][j-1][k],f[pr^1][j][k]);
}
}
num=min(num+1,m>>1);
}
printf("%d",(ll)frc*f[pr][0][0]%mod);
}