OIFC NOI2022 day27
高维游走 travel
做法一(DP 套 DP)
比较自然,但笔者没想到。
假设第 \(i\) 维最远走到 \(s_i\),则往返的方案数为
有贡献的必要条件是上式 \(\mod2=1\)。根据 Lucas 定理,\(\binom{n}{m}\mod2=1\) 当且仅当 \(m\subseteq n\)。所以上式 \(\mod2=1\) 当且仅当 \(\forall i\neq j,s_i\cap s_j=\varnothing\) 且 \(\forall i,s_i\subseteq t_i\cap t_0\),故考虑拆位。
考虑用数位 DP 判断一个固定疲劳度 \(x\) 的方案数奇偶性。设 \(dp_{i,j}\in\{0,1\}\) 表示从低位往高位扫,现在在第 \(i\) 位,进 \(j\) 位,方案数 \(\mod 2\) 的值,使用归纳不难证明 \(j\in[0,m)\)。
考虑 DP 套 DP。(如果对这个技巧熟练的话可以直接设出,但笔者不熟练,所以绕一下)考虑抽象为 DFA,设 \((i,S)\) 表示在第 \(i\) 位,方案数为奇数的进位集合为 \(S\),终止状态为 \((\log_2V,S),0\in S\),转移就是上面的 DP。
现在可以设计外层 DP,设 \(f_{i,S}\) 表示到可以到达状态 \((i,S)\) 的疲劳度数量(只保留 \(0\sim i\) 位),转移就是沿着上述自动机转移,如果上述自动机有一个 \((i,S)\rightarrow (i',S')\) 的转移,则这个 DP 有转移 \(f_{i,S}\rightarrow f_{i',S'}\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef complex<double> comp;
typedef unsigned int uint;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int M=15,Lg=40;
int m;
ll t[M],f[Lg][1<<10];
void __INIT__(){}
void __SOLVE__(){
scanf("%d",&m);
for(int i=0;i<=m;i++) scanf("%lld",&t[i]);
int lg=63^__builtin_clzll(m*t[0]);
for(int i=0;i<=lg+1;i++) for(int s=0;s<(1<<m);s++) f[i][s]=0;
f[0][1]=1;
for(int i=0;i<=lg;i++){
for(int s=0;s<(1<<m);s++){
int s0=0,s1=0;
for(int i=0;i<m;i++) s0|=((s>>(i<<1)&1)<<i),s1|=((s>>((i<<1)+1)&1)<<i);
for(int k=0;k<2;k++){
int ss=0;
for(int j=0;j<=m;j++){
if(j&&(!(t[0]>>i&1)||!(t[j]>>i&1))) continue;
int tmp;
if(k==(j&1)) tmp=(s0<<(j>>1));
else tmp=(s1<<((j+(j&1))>>1));
ss^=tmp;
}
f[i+1][ss]+=f[i][s];
}
}
}
// for(int i=0;i<=lg+1;i++){
// for(int s=0;s<(1<<m);s++) printf("%lld ",f[i][s]);
// printf("\n");
// }
ll ans=0;
for(int s=1;s<(1<<m);s+=2) ans+=f[lg+1][s];
printf("%lld\n",ans);
}
int main(){
#ifndef JZQ
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
#endif
int T=1;
scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
做法二(多项式)
不是很自然,笔者差点想到了。
与上面类似地拆位,考虑用生成函数固定疲劳度。第 \(k\) 位的生成函数
对于固定的疲劳度 \(x\),
直接做显然不太好做,类似 Bostan-Mori 的思想,考虑分奇偶函数。设 \(F_0(z)=F_{0,0}(z^2)+zF_{0,1}(z^2)\),则 \(n\) 的规模减半。
笔者当时以为奇偶两部分可以合并处理,但不行。 考虑把 \(F\) 状压,设 \(dp_{i,f}\) 表示保留下标 \(\ge i\) 的多项式,再乘上 \(f\) 的 \(popcount\)。随便 DP 一下就行了。
比赛 match
关键词:LCT。

浙公网安备 33010602011771号