Codeforces 题目传送门 & 洛谷题目传送门

神仙题 %%%%%%%%%%%%%

u1s1 感觉这道题风格很省选(

下记 \(m=|s|\),首先探讨 \(n+m\) 为偶数的情形。

首先考虑一个暴力的 DP,我们设 \(dp_{i,l,r}\) 表示当前考虑了回文串的前 \(i\) 个字符和后 \(i\) 个字符,前 \(i\) 个字符恰好匹配了 \(s\) 的 \([1,l-1]\) 区间中的字符,后 \(i\) 个字符恰好匹配了 \(s\) 的 \([r+1,m]\) 区间中的字符的方案数,再记 \(g_i\) 为当前考虑了回文串的前 \(i\) 个字符和后 \(i\) 个字符。

考虑转移,分四种情况:

  1. \(r-l\le 1\) 且 \(s_l=s_r\),那么如果我们在 \(i+1\) 位置填上 \(s_l\) 那么就匹配结束了,即 \(g_{i+1}\leftarrow dp_{i,l,r}\),否则还是只能完全匹配 \([1,l-1]\) 和 \([r+1,n]\) 中的字符,即 \(dp_{i+1,l,r}\leftarrow 25·dp_{i,l,r}\)。
  2. \(r-l\ge 2\) 且 \(s_l=s_r\),那么如果我们在从左往右数和从右往左数第 \(i+1\) 的位置填上 \(s_l\) 就可以匹配到 \([1,l]\) 和 \([r,m]\),即 \(dp_{i+1,l+1,r-1}\leftarrow dp_{i,l,r}\),否则还是有 \(dp_{i+1,l,r}\leftarrow 25·dp_{i,l,r}\)
  3. \(s_l\ne s_r\),那么如果我们在从左往右数和从右往左数第 \(i+1\) 的位置填 \(s_l\),左端点就可以多匹配一位,即 \(dp_{i+1,l+1,r}\leftarrow dp_{i,l,r}\),同理如果我们填 \(s_r\),右端点就可以多匹配一位,即 \(dp_{i+1,l,r-1}\leftarrow dp_{i,l,r}\),否则还是只能完全匹配 \([1,l-1]\) 和 \([r+1,n]\) 中的字符,即 \(dp_{i+1,l,r}\leftarrow 24·dp_{i,l,r}\)。
  4. \(g\) 的转移:显然由于我们已经匹配结束了,剩下的位置填什么都行,即 \(g_{i+1}\leftarrow 26·g_i\)。

最终答案即为 \(g_{(n+m)/2}\)

这样暴力是 \(nm^2\) 的,如果强行把 \((l,r)\) 看作一个二元组套矩乘,那可以实现 \(m^6\log n\) 的优秀复杂度,一脸过不去。

我们考虑优化,敏感一些的同学应该可以注意到这个 DP 转移方程有点像 DAG 上的 DP,事实上,如果我们把所有二元组 \((l,r)\) 以及 \(g_i\)(下记为目标点)分别看作一个点,对于形如 \(dp_{i+1,x}\leftarrow z·dp_{i,y}\) 的状态转移方程式,我们就从 \(y\) 到 \(x\) 连 \(z\) 条边,那么答案就是 \((1,m)\) 表示的点到目标点的经过 \(\dfrac{n+m}{2}\) 条边的路径条数。

我们考虑探究一下这张图长啥样,比方说 \(s=\text{abaac}\),那么建出图来就长这样(这里借用了 xht37 大佬题解里的图):

不难发现,该图是由若干个自环即若干条有向边组成了,对于每个状态 \((l,r)\) 都有一个自环连向自己,如果去掉这些自环,那形成的图显然是一个 DAG(\((l,r)\) 能到达 \((l',r')\) 当且仅当 \([l',r']\subseteq[l,r]\),这显然构成了一个偏序关系),也就是说从源点 \((1,m)\) 到目标点,经过的非自环边的个数最多只有 \(m\)(每次经过一条边,区间长度缩小 \(1\) 或 \(2\),因此至少缩小 \(m\) 次就能到达目标点)

并且还可以发现除了目标点上有个 \(26\) 的自环之外,自环的类型只有 \(2\) 种,那就是权值为 \(25\) 的环和权值为 \(24\) 的环,我们不妨称存在权值为 \(25\) 的自环连向自己的点为“\(25\) 点”,其余的点称为“\(24\) 点”。考虑观察这个路径个数有什么性质,我们不妨称去掉自环边后路径相同的路径为等价类,考虑对每个等价类计算一遍方案数,容易发现等价类具体先经过权值为多少的自环,后经过权值为多少的自环并不重要,我们只关心它经过了多少权值为 \(24,25,26\) 的自环,即两个等价类的路径的个数相同当且仅当这两个等价类的路径上 \(24,25,26\) 的自环的个数相同。

显然权值为 \(26\) 的自环就是废话,因为从起始点到目标点总共只会经过一个 \(26\) 的自环。而经过的 \(25\) 的自环和 \(24\) 的自环个数有一个性质,那就是如果我们记经过的 \(25\) 的自环个数为 \(c_{25}\),经过的 \(24\) 的自环个数为 \(c_{24}\),则 \(c_{25}=\lceil\dfrac{s-c_{24}}{2}\rceil\),道理很简单,对于一个 \(25\) 点 \([l,r]\),每当我们从该点走向一个别的点时,区间长度会缩短 \(2\)(当然 \(l=r\) 时只会缩短 \(1\)),而对于一个 \(24\) 点 \([l,r]\) 则会缩短 \(1\),因此 \(c_{25}=\lceil\dfrac{s-c_{24}}{2}\rceil\),故我们只需对于每个 \(i\) 求出经过 \(i\) 个 \(24\) 点的等价类个数,然后对每个 \(i\) 跑一遍矩阵快速幂即可。

这个东西怎么求呢?考虑记忆化搜索,记 \(f_{x,l,r}\) 为以状态 \([l,r]\) 为结尾的经过 \(x+[(l,r)\text{是否为}24\text{点}]\) 个 \(24\) 点的路径个数,转移很容易,如果 \(s_{l-1}\ne s_r\) 那 \((l-1,r)\) 为 \(24\) 点并且存在 \((l-1,r)\to (l,r)\) 的边,\(f_{x,l,r}\leftarrow f_{x,l,r}\),\((l,r+1)\) 也同理,如果 \(s_{l-1}=s_{r+1}\),那 \((l-1,r+1)\) 为 \(25\) 点并且存在 \((l-1,r+1)\to(l+r)\) 的边,\(f_{x,l,r}\leftarrow f_{x,l-1,r+1}\),记忆化搜索即可,边界条件 \(f_{0,1,m}=1\)。

这样复杂度是 \(m^4\log n\) 的,还是无法通过,继续优化。

这题复杂度瓶颈在于对于每个 \(x\) 都跑一遍矩阵优化,考虑对状态进行压缩,我们在上面建一排 \(m\) 个 \(24\) 点,在下面建一排 \(\lceil\dfrac{m}{2}\rceil\) 个 \(25\) 点,对于上面一排的第 \(i\) 个点,连一条指向第二排第 \(\lceil\dfrac{m}{2}\rceil-\lceil\dfrac{m-i}{2}\rceil\) 个节点,权值为经过 \(i\) 个路径的等价类个数,然后在相邻的 \(24\) 点之间连权值为 \(1\) 的边,在相邻的 \(25\) 点之间连权值为 \(1\) 的边,然后每个 \(24\) 点上都套个权值为 \(24\) 的自环,每个 \(25\) 点上套个权值为 \(25\) 的自环,最后连一条从最右边的 \(25\) 点向目标点,权值为 \(1\) 的边,再在目标点上套个 \(26\) 的自环即可。这里还是借一张 xht37 神仙题解里的图:

这样状态数降低到了 \(\mathcal O(m)\),跑一遍矩乘即可,复杂度 \(m^3\log n\)。

这仅仅只是 \(n+m\) 为偶数的情况,最后考虑 \(n+m\) 为奇数的情况有什么不同。记 \(p=\dfrac{n+m-1}{2}\),唯一的不同就是最后一步,\(dp_{p,i,i+1}\)(\(s_i=s_{i+1}\)),无法转移到 \(g_{p+1}\)。正难则反,我们考虑拿总方案数减去这部分方案数,我们只需保留最后一步从 \((i,i+1)\) 转移的链,并去掉目标点上 \(26\) 的自环即可。

最后,此题还存在卡常数的问题。这里有一个小技巧,那就是由于我们只在 \((i,j)(i<j)\) 的状态之间连边,得到的矩阵是一个下三角矩阵(我代码里使用的是先矩阵、后向量的乘法,因此对于存在边的 \((i,j)\) 我是令 \(j\) 为行号 \(i\) 为列号,因此得到的是上三角矩阵,不过差不多辣),因此我们在矩阵乘法时候只需枚举 \(1\le i\le k\le j\) 的 \((i,j,k)\),这样常数可以减小到原来的 \(\dfrac{1}{6}\)

u1s1 这题题解码死我了

话说这场的 D1C D E 我是不是都做过了啊,u1s1 这场的 C 我都不会

const int MAXN=200;
const int MAXM=300;
const int MOD=1e4+7;
int n,m,k;char s[MAXN+5];
struct mat{
ll a[MAXM+5][MAXM+5];
mat(){memset(a,0,sizeof(a));}
mat operator *(const mat &rhs){
mat res;
for(int i=1;i<=k;i++) for(int l=1;l<=i;l++) for(int j=1;j<=l;j++)
res.a[i][j]+=a[i][l]*rhs.a[l][j];
for(int i=1;i<=k;i++) for(int j=1;j<=k;j++) res.a[i][j]%=MOD;
return res;
}
};
int dp[MAXN+5][MAXN+5][MAXN+5];
int dfs(int x,int l,int r){
if(x<0) return 0;
if(~dp[x][l][r]) return dp[x][l][r];
if(l==1&&r==m) return dp[x][l][r]=(!x);
dp[x][l][r]=0;
if(l!=1&&s[l-1]!=s[r]) dp[x][l][r]=(dp[x][l][r]+dfs(x-1,l-1,r))%MOD;
if(r!=m&&s[l]!=s[r+1]) dp[x][l][r]=(dp[x][l][r]+dfs(x-1,l,r+1))%MOD;
if(l!=1&&r!=m&&s[l-1]==s[r+1]) dp[x][l][r]=(dp[x][l][r]+dfs(x,l-1,r+1))%MOD;
return dp[x][l][r];
}
int main(){
scanf("%s%d",s+1,&n);m=strlen(s+1);k=m+((m+1)>>1);
memset(dp,-1,sizeof(dp));mat st,trs,res;
for(int i=0;i<m;i++){
int cnt=0;
for(int j=1;j<=m;j++){
cnt=(cnt+dfs(i,j,j))%MOD;
if((j^m)&&!(s[j]^s[j+1])) cnt=(cnt+dfs(i,j,j+1))%MOD;
}
// printf("%d %d\n",i,cnt);
if(i) trs.a[k-((m-i+1)>>1)][i]=cnt;
else st.a[m][1]=cnt;
}
for(int i=2;i<m;i++) trs.a[i][i-1]=1;
if(m>1) st.a[1][1]=1;
for(int i=m;i<k;i++) trs.a[i+1][i]=1;
for(int i=1;i<m;i++) trs.a[i][i]=24;
for(int i=m;i<k;i++) trs.a[i][i]=25;
trs.a[k][k]=26;
for(int i=1;i<=k;i++) res.a[i][i]=1;
// for(int i=1;i<=k;i++) for(int j=1;j<=k;j++) printf("%d%c",trs.a[i][j],(j==k)?'\n':' ');
int stp=(n+m+1)>>1;
for(;stp;stp>>=1,trs=trs*trs) if(stp&1) res=res*trs;
// for(int i=1;i<=k;i++) for(int j=1;j<=k;j++) printf("%d%c",res.a[i][j],(j==k)?'\n':' ');
int sum=0;for(int i=1;i<=k;i++) sum=(sum+st.a[i][1]*res.a[k][i])%MOD;
if(!((n+m)&1)) return printf("%d\n",sum),0;
int ans=sum;
memset(res.a,0,sizeof(res.a));memset(trs.a,0,sizeof(trs.a));
memset(st.a,0,sizeof(st.a));
for(int i=0;i<m;i++){
int cnt=0;
for(int j=1;j<=m;j++){
if((j^m)&&!(s[j]^s[j+1])) cnt=(cnt+dfs(i,j,j+1))%MOD;
}
if(i) trs.a[k-((m-i+1)>>1)][i]=cnt;
else st.a[m][1]=cnt;
}
for(int i=2;i<m;i++) trs.a[i][i-1]=1;
if(m>1) st.a[1][1]=1;
for(int i=m;i<k;i++) trs.a[i+1][i]=1;
for(int i=1;i<m;i++) trs.a[i][i]=24;
for(int i=m;i<k;i++) trs.a[i][i]=25;
for(int i=1;i<=k;i++) res.a[i][i]=1;
stp=(n+m+1)>>1;
for(;stp;stp>>=1,trs=trs*trs) if(stp&1) res=res*trs;
sum=0;for(int i=1;i<=k;i++) sum=(sum+st.a[i][1]*res.a[k][i])%MOD;
printf("%d\n",(ans-sum+MOD)%MOD);
return 0;
}

Codeforces 506E - Mr. Kitayuta's Gift(神仙矩阵乘法)的更多相关文章

  1. Codeforces 506E Mr. Kitayuta's Gift (矩阵乘法,动态规划)

    描述: 给出一个单词,在单词中插入若干字符使其为回文串,求回文串的个数(|s|<=200,n<=10^9) 这道题超神奇,不可多得的一道好题 首先可以搞出一个dp[l][r][i]表示回文 ...

  2. Codeforces 505A Mr. Kitayuta's Gift 暴力

    A. Mr. Kitayuta's Gift time limit per test 1 second memory limit per test 256 megabytes input standa ...

  3. codeforces 505A. Mr. Kitayuta's Gift 解题报告

    题目链接:http://codeforces.com/problemset/problem/505/A 题目意思:给出一个长度不大于10的小写英文字符串 s,问是否能通过在字符串的某个位置插入一个字母 ...

  4. 水题 Codeforces Round #286 (Div. 2) A Mr. Kitayuta's Gift

    题目传送门 /* 水题:vector容器实现插入操作,暴力进行判断是否为回文串 */ #include <cstdio> #include <iostream> #includ ...

  5. 【CF506E】Mr. Kitayuta's Gift dp转有限状态自动机+矩阵乘法

    [CF506E]Mr. Kitayuta's Gift 题意:给你一个字符串s,你需要在s中插入n个字符(小写字母),每个字符可以被插在任意位置.问可以得到多少种本质不同的字符串,使得这个串是回文的. ...

  6. codeforces Round 286# problem A. Mr. Kitayuta's Gift

    Mr. Kitayuta has kindly given you a string s consisting of lowercase English letters. You are asked ...

  7. CodeForces 505B Mr. Kitayuta's Colorful Graph

    Mr. Kitayuta's Colorful Graph Time Limit:1000MS     Memory Limit:262144KB     64bit IO Format:%I64d ...

  8. codeforces 505B Mr. Kitayuta's Colorful Graph(水题)

    转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud Mr. Kitayuta's Colorful Graph Mr. Kitayut ...

  9. [Codeforces 505C]Mr. Kitayuta, the Treasure Hunter

    Description The Shuseki Islands are an archipelago of 30001 small islands in the Yutampo Sea. The is ...

随机推荐

  1. django 1.11.16之环境搭建

    django版本:django1.11.16  windows环境 python 3.6.3    !!!可先安装虚拟环境在进行环境搭建  1.安装django:pip install django= ...

  2. Java正则中"\\\\"表示普通反斜杠

    Java中"\"用于转义字符,"\\"表示普通无转义功能的反斜杠. 如果将字符串当做正则表达式来解析,那么"\\"也有了特殊意义,它与其后的 ...

  3. mybatis中的#和$的区别 以及 防止sql注入

    声明:这是转载的. mybatis中的#和$的区别 1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.如:order by #user_id#,如果传入的值是111,那么解析成sq ...

  4. Scrum Meeting 0501

    零.说明 日期:2021-5-1 任务:简要汇报两日内已完成任务,计划后两日完成任务 一.进度情况 组员 负责 两日内已完成的任务 后两日计划完成的任务 qsy PM&前端 整装待发,准备冲刺 ...

  5. oo第三单元学习总结

    OO第三单元小结 一.JML语言理论基础及工具链梳理 在本单元我们学习了JML语言的一些基础知识,能够让我们看懂简单的JML规格并写出对应代码, 主要用到的知识点有:   1.requires 该子句 ...

  6. STL模板

    目录 栈stack 队列queue 列表List 集合set 映射map 多重映射multimap 对pair 元组tuple 容器containers 算法algorithms 仿函数/函数对象fu ...

  7. udev 使用方法

    原文地址 http://blog.163.com/againinput4@yeah/blog/static/122764271200962305339483/ 最近有在研究SD卡设备节点自动创建及挂载 ...

  8. 七. Go并发编程--sync.Once

    一.序 单从库名大概就能猜出其作用.sync.Once使用起来很简单, 下面是一个简单的使用案例 package main import ( "fmt" "sync&qu ...

  9. 攻防世界 WEB 高手进阶区 unserialize3 Writeup

    攻防世界 WEB 高手进阶区 unserialize3 Writeup 题目介绍 题目考点 PHP反序列化 __wakeup漏洞 Writeup 题名 unserialize 是反序列化函数名 了解一 ...

  10. stom消费kafka消息速度慢的问题

    原来代码如下 KafkaSpoutConfig<String, String> kafkaSpoutConfig = KafkaSpoutConfig.builder(kafka_serv ...