【XSY3952】简单的计数题(dp)
题面
题解
首先题意可以转化为:给你一个长度为 \(n\) 的序列 \(c\),求将 \(c\) 分成两个长度为 \(\dfrac{n}{2}\) 的相同的子序列的方案数。
考虑 dp,设 \(f(i,sta)\) 表示已经将 \(c\) 的前 \(i\) 位分成了两个子序列,其中长的子序列比短的子序列多出来的未匹配的部分为 \(sta\)(用 deque
、list
等STL容器均可记录)的方案数。不妨称这个 \(sta\) 为状态,那么只有当状态长度不大于 \(\dfrac{n}{2}\) 时,这个状态才是有用的。
转移十分简单:新加入一个元素时,考虑是继续扔到当前状态的尾部,还是和当前状态的开头匹配并删除这个开头。
这里主要讲为什么时间是对的:
时间复杂度和状态数有关。假设当前到第 \(i\) 位,一共有 \(x\) 个状态,考虑接下来的 \(c\) 的两个元素 \(a,b\):
- 如果他们相等,那么 “\(a\) 删开头扔尾部” 和 “\(b\) 删开头扔尾部” 得到的状态一样,所以新状态共 \(3x\) 种。
- 如果他们不相等,那么 “\(a\) 删开头扔尾部” 和 “\(b\) 删开头扔尾部” 两种中仅有一种是可行的,否则他们相等,矛盾。所以新状态共 \(3x\) 种。
于是此时总状态不超过 \(3^{\tfrac{n}{2}}\) 种。但还是太多了。
由于我们只需要知道 \(f(n,\empty)\),所以考虑折半:将 \(c\) 序列的前一半和后一半(要翻转)分别 dp,最后将前后两半的 dp 值合并起来统计答案。
此时状态数不超过 \(3^{\tfrac{n}{4}}\) 种。
然后题解说进一步分析(?)可以证明在 \(n=60\) 时状态数不超过 \(50000\)。
代码如下:
#include<bits/stdc++.h>
#define N 65
using namespace std;
namespace modular
{
const int mod=998244353;
const int inv2=499122177;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
}using namespace modular;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
typedef deque<int> STA;
int T,n,hn,a[N];
STA now1,now2;
map<STA,int>dp[2][N>>1];
void solve(bool opt)
{
for(int i=0;i<=hn;i++) dp[opt][i].clear();
now1.clear();
dp[opt][0][now1]=1;
for(int i=0;i<hn;i++)
{
for(map<STA,int>::iterator it=dp[opt][i].begin();it!=dp[opt][i].end();it++)
{
now1=(*it).first;
if((!now1.empty())&&now1.front()==a[i+1])
{
now2=now1;
now2.pop_front();
dp[opt][i+1][now2]=add(dp[opt][i+1][now2],dp[opt][i][now1]);
}
now2=now1;
now2.push_back(a[i+1]);
if(!now1.empty()) dp[opt][i+1][now2]=add(dp[opt][i+1][now2],dp[opt][i][now1]);
else dp[opt][i+1][now2]=add(dp[opt][i+1][now2],mul(dp[opt][i][now1],2));
}
}
}
int main()
{
T=read();
while(T--)
{
n=read();
hn=n>>1;
for(int i=1;i<=n;i++) a[i]=read();
solve(0);
reverse(a+1,a+n+1);
solve(1);
int ans=0;
for(map<STA,int>::iterator it=dp[0][hn].begin();it!=dp[0][hn].end();it++)
{
now1=(*it).first;
while(!now2.empty()) now2.pop_front();
while(!now1.empty())
{
now2.push_back(now1.back());
now1.pop_back();
}
if(!now2.empty()) ans=add(ans,mul((*it).second,mul(dp[1][hn][now2],inv2)));
else ans=add(ans,mul((*it).second,dp[1][hn][now2]));
}
printf("%d\n",ans);
}
return 0;
}
/*
5
2
1 1
2
2 2
4
1 1 2 2
6
1 2 3 4 5 6
4
1 2 2 1
*/
实测 deque
比 list
快,我直接疑惑了。