题解:P17351 [ECNA 2025] Polyomino Tiling
lailai0916 · · 题解
题意简述
给定多连块的边界方向串,方向分别为上、右、下、左。记
对边界串的每个循环移位,统计将它写成
解题思路
设边界串长度为
固定一个循环起点,将对应字符串的前后两半记为
因此,需要把
记
考虑
所以,原问题的一次合法划分,对应将
定义
当
当前循环起点的答案就是
回文树节点 nxt[v] 保存。定义相邻长度差
记 sl[v] 指向当前组之后的第一个节点。若 dif[v]==dif[nxt[v]],当前节点与它的后缀链接在同一组,继承后者的等差链接;否则,等差链接直接指向后缀链接。代码中的 dif 保存
在前缀长度为
若当前组还有其他节点,则加入已经保存的 g[nxt[v]][j]。这里复用的是较早前缀的组和,而非当前前缀中重新计算的值。组内回文以 nxt[v] 对应的组,与当前组除去最短回文后的部分一一对应。前缀长度和所取回文长度同时减少
等差链接的遍历仅访问每一组的最长节点,保证 g[nxt[v]] 保留上述所需的历史组和。若相邻长度差不同,当前组仅有一个节点,不加入这项。将每组得到的
这里有一个与偶数切口有关的实现细节:即使当前前缀长度为奇数,也必须更新各组的 g,因为后续偶数位置可能复用这些历史组和。仅最终写入 f 时限制前缀长度为偶数,不能跳过奇数位置的整个回文树过程,也不能每读入一个字符就清空 g。
题面还保证边界行走除首尾外不重复坐标,所以不同循环起点得到的方向串两两不同。若存在小于
枚举循环起点时还能减少一半计算。若某个移位的分解为
代码把上、右、下、左依次编码为 a[0]=-1 作为越界匹配的哨兵。每个循环起点重新建立交织串和回文树,清空该轮的状态与组和,避免不同移位之间共享历史。
回文后缀的周期性质保证等差组数量为 int;汇总全部移位的答案使用 long long。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=10005;
int a[N],b[N],ch[N][4],len[N],nxt[N],dif[N],sl[N],f[N][4],g[N][4];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
string s;
cin>>n>>s;
for(int i=0;i<n;i++)b[i]=string("urdl").find(s[i]);
ll ans=0;
for(int i=0;i<n/2;i++)
{
for(int j=0;j<n/2;j++)
{
a[j*2+1]=b[(i+j)%n];
a[j*2+2]=b[(i+j+n/2)%n]^2;
}
memset(ch,0,sizeof ch);
memset(f,0,sizeof f);
memset(g,0,sizeof g);
a[0]=-1;
len[0]=0;
len[1]=-1;
nxt[0]=1;
nxt[1]=0;
dif[0]=dif[1]=0;
sl[0]=sl[1]=0;
f[0][0]=1;
int cnt=1,last=0;
for(int j=1;j<=n;j++)
{
int u=last;
while(a[j-len[u]-1]!=a[j])u=nxt[u];
if(!ch[u][a[j]])
{
cnt++;
ch[u][a[j]]=cnt;
len[cnt]=len[u]+2;
int v=nxt[u];
while(a[j-len[v]-1]!=a[j])v=nxt[v];
nxt[cnt]=len[cnt]==1?0:ch[v][a[j]];
dif[cnt]=len[cnt]-len[nxt[cnt]];
sl[cnt]=dif[cnt]==dif[nxt[cnt]]?sl[nxt[cnt]]:nxt[cnt];
}
last=ch[u][a[j]];
for(int k=last;len[k]>0;k=sl[k])
{
int pos=j-len[sl[k]]-dif[k];
for(int i=1;i<=3;i++)
{
g[k][i]=f[pos][i-1];
if(dif[k]==dif[nxt[k]])g[k][i]+=g[nxt[k]][i];
if(j%2==0)f[j][i]+=g[k][i];
}
}
}
ans+=f[n][2]+f[n][3];
}
cout<<ans*2<<'\n';
return 0;
}