给一个只由数字构成的字符串s(|s|<=2e6)
求s的所有本质不同字符串之和%1e9+7,
每一个不同字符串的贡献为其十进制下的值
https://blog.csdn.net/sodacoco/article/details/83240305(Hash相关)
https://blog.csdn.net/dllpXFire/article/details/82314916
https://blog.csdn.net/qq_37025443/article/details/82415078?utm_source=blogxgwz9
https://blog.csdn.net/qq_36424540/article/details/82817740
考虑到每个回文串都会在Manacher的过程中被遍历到,
那么我们每得到一个回文串,就判断该回文串是否已经被加入Hash里了即可
已经加入了就不计入其贡献,否则将其加入Hash并统计其贡献
P(这里取2e6+7)进制下mod2^64与10进制下mod(1e9+7)的搭配,冲突性很小,能卡过
子串Hash值小技巧:利用前缀和,[1,r]与[1,l-1]左对齐,求[l,r]的Hash值
子串Sum值小技巧同上,都是之前没有好好留意的写法
然后Hash用的就是建图那里的链式前向星类似的写法,开三个数组,开散列链表Hash
后续:
学了回文树(又名回文自动机),发现这玩意比后缀自动机简单多了……
所以就是第一次插入本质不同回文串,也就是新创建节点的时候,加上那个数的贡献即可……
魔改Manacher,之前一直写Manacher的板子题
魔改Hash,之前一直只总结Hash的板子
Hash还是比较正常,利用了一个前缀和对其作差求子串的Hash值
剩下的就是p进制下的开散列,与链式前向星一致
Manacher相关下标还是比较重要的啊,好好debug
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=2e6+10;
const int p=2e6+7;//p进制下 Hash链式前向星 开散列
const int mod=1e9+7;
char w[maxn],t[maxn<<1];
int R[maxn<<1],tot;
//R[i]: 以i为中心的包含i和井号的最长回文半径 减1为回文串长
int head[maxn<<1],Next[maxn<<1],cnt;//链式
ull link[maxn<<1];//该点是否存在映射值
ull base[maxn<<1],Hash[maxn<<1];//p进制下 mod(2^64) Hash值的前缀和
ll base10[maxn<<1],sum[maxn<<1];//10进制下 mod(1e9+7) 数字值的前缀和
ll res;
bool add(ull x)
{
ull u=x%p;//P为hash的模数
for(int i=head[u];i;i=Next[i])
if(link[i]==x)return 1;
Next[++cnt]=head[u];
head[u]=cnt;
link[cnt]=x;
return 0;
}
ll solve(int l,int r)
{
ull x=Hash[r]-Hash[l-1]*base[r-l+1];//[l,r]的Hash值 利用前缀和 头对齐之后作差得Hash值
if(add(x))return 0;
ll res=(sum[r]-sum[l-1]*base10[(r-l+2)/2]%mod+mod)%mod;//答案只在mod(1e9+7)下进行
//(r-l+1)/2是串长的一半向下取整 [(r-l+1)+1]/2是串长的一半向上取整
//对于一个1#2#1的串,其数字的数量应为串长一半向上取整
return res;
}
int init1(char w[],char t[])//原串:w 新串:t
{
tot=0;
memset(t,0,sizeof t);
memset(R,0,sizeof R);
int len=strlen(w);
t[0]='@';
for(int i=0;i='0'&&t[i]<='9')sum[i]=(sum[i-1]*10+t[i]-'0')%mod;
else sum[i]=sum[i-1];
}
}
void manacher(char t[],int len)
{
int r=0,pos=0;
//int ans=0;
res=0;
for(int i=1;i<=len;++i)
{
if(t[i]!='#')(res+=solve(i,i))%=mod;//[i,i]的贡献
if(r>i)R[i]=min(r-i,R[2*pos-i]);
else R[i]=1;
while(t[i-R[i]]==t[i+R[i]])
{
if(t[i+R[i]]!='#')(res+=solve(i-R[i],i+R[i]))%=mod;//[i-R[i],i+R[i]]的贡献
R[i]++;
}
if(R[i]+i>r)
{
pos=i;//最右位置对应的回文中心
r=R[i]+i;//最右位置
}
//ans=max(ans,R[i]);
}
//return ans-1;
}
int main()
{
while(~scanf("%s",w))
{
int a=init1(w,t);
init2(t,a);
manacher(t,a);
printf("%lld\n",res);
}
return 0;
}
#include
using namespace std;
typedef long long ll;
const int MAXN = 2e6+5 ;
const int N = 10 ;
const int mod=1e9+7;
char s[MAXN];
int len;
ll ten[MAXN],ans;//ten[i]表示1后面i个0
struct Palindromic_Tree {
int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点
int cnt[MAXN] ;//cnt[i]表示编号为i的节点表示的回文字符串在整个字符串中出现了多少次。
//cnt[i]建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的
int num[MAXN] ;//num[i]表示节点i表示的回文字符串中,有多少个本质不同的字符串(包括本身)
ll v[MAXN];//每个点代表的实际十进制的值
int len[MAXN] ;//len[i]表示节点i表示的回文串的长度
int S[MAXN] ;//存放添加的字符
int last ;//指向上一个字符所在的节点,方便下一次add
int n ;//字符数组指针
int p ;//节点指针
int newnode ( int l ) {//新建节点
for ( int i = 0 ; i < N ; ++ i ) next[p][i] = 0 ;
cnt[p] = 0 ;
num[p] = 0 ;
len[p] = l ;
return p ++ ;
}
void init () {//初始化
p = 0 ;
newnode ( 0 ) ;
newnode ( -1 ) ;
last = 0 ;
n = 0 ;
S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判
fail[0] = 1 ;
}
int get_fail ( int x ) {//和KMP一样,失配后找一个尽量最长的
while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ;
return x ;
}
void add ( int c ) {
c -= '0';
S[++ n] = c ;
int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置
if ( !next[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
int now = newnode ( len[cur] + 2 ) ;//新建节点
if(len[now]==1)v[now]=c;
else if(len[now]==2)v[now]=ten[1]*c+c;
else v[now]=((c*ten[len[cur]]+v[cur])*ten[1]+c)%mod;//前后各补一个c
ans=(ans+v[now])%mod;
fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转
next[cur][c] = now ;
num[now] = num[fail[now]] + 1 ;
}
last = next[cur][c] ;
cnt[last] ++ ;
}
void count()//如果不count向上汇总 每个节点代表的回文串 当且仅当完全相同时才会被记次数
{
for ( int i = p - 1 ; i >= 0 ; -- i )
cnt[fail[i]] += cnt[i] ;
//fail[i]累加i的cnt,因为如果fail[v]=u,则u一定是套在v内的子回文串!
//所以 u自己出现了一次 在v中出现了一次 归回去能统计出u出现的次数
}
}a;
int main()
{
ten[0]=1;
for(int i=1;i