题意
n n n 个点排成一个环,你从某个点开始,每步操作从等概率选择四个操作之一:移到左边第二个点、 移到左边第一个点、移到右边第二个点、移到右边第一个点。当你走到任何一个点两次时你会立刻停止行走。 求操作的期望步数。答案模 p p p。 n ≤ 80 n\leq 80 n≤80。
题解
首先有
2
n
2^n
2n 的状压 DP,记 1
为到过的点。
接着假如环上有至少两个连续的 11
,与当前位置被 11
隔开的地方肯定无法到达。于是把这些 11
之间的位置设成 1
,这样状态数变少了,就跑过去了。
(小问号你是否有许多朋友?) \text{(小问号你是否有许多朋友?)} (小问号你是否有许多朋友?)
std 的 DP 方式与 DP 顺序似乎更高一些,可以在 DP 途中在 map
里删元素使得 map
变小速度变快;我的解法得用 unordered map
才跑得过去。
代码:
#include<bits/stdc++.h>
using namespace std;
int getint(){
int ans=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
ans=ans*10+c-'0';
c=getchar();
}
return ans;
}
#define ll long long
#define bs bitset<80>
int n,mod;
int inv2,inv4;
struct stat{
int pos;
bitset<80>a;
bool gg(){
return a[pos];
}
stat go(int del){
stat b;
b.a=a;b.a.set(pos);
b.pos=(pos+del+n)%n;
return b;
}
bool cut(){
#define P(x) ((x)+1<n?(x)+1:0)
for(int i=P(pos);i!=pos;i=P(i)){
if(a[i]&&a[P(i)]){
int j=P(i),r=j;
for(;j!=pos;j=P(j)){
if(a[j]&&a[P(j)])r=j;
}
for(j=P(i);j!=r;j=P(j))a[j]=1;
return 1;
}
}
return 0;
}
};
bool operator== (const stat &a,const stat &b){
return a.pos==b.pos&&hash<bs>()(a.a)==hash<bs>()(b.a);
}
struct qaq{
const size_t operator() (const stat &a)const noexcept{
return hash<bs>()(a.a)^(a.pos<<5);
}
};
ostream& operator<< (ostream &out,const stat &b){
out<<b.a.to_string()<<"["<<b.pos<<"]";
return out;
}
unordered_map<stat,ll,qaq>f;
ll calc(stat s){
//cerr<<"calc "<<s<<endl;
if(s.gg())return 0;
bool b=s.cut();
auto it=f.find(s);
if(it!=f.end()){
ll ans=it->second;
//if(!b)f.erase(it);
return ans;
}
it=f.insert(make_pair(s,
(4ll+calc(s.go(1))+calc(s.go(2))+calc(s.go(-1))+calc(s.go(-2)))*inv4%mod)).first;
//cerr<<it->first<<" "<<it->second<<endl;
return it->second;
}
int main(){
n=getint(),mod=getint();
inv2=(mod+1)>>1;
inv4=inv2*1ll*inv2%mod;
cout<<calc(stat({0,bs()}));
}