Codeforces 506E - Mr. Kitayuta's Gift(神仙矩阵乘法)
神仙题 %%%%%%%%%%%%%
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\) 个字符。
考虑转移,分四种情况:
- \(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}\)。
- \(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}\)
- \(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}\)。
- \(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(神仙矩阵乘法)的更多相关文章
- Codeforces 506E Mr. Kitayuta's Gift (矩阵乘法,动态规划)
描述: 给出一个单词,在单词中插入若干字符使其为回文串,求回文串的个数(|s|<=200,n<=10^9) 这道题超神奇,不可多得的一道好题 首先可以搞出一个dp[l][r][i]表示回文 ...
- 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 ...
- codeforces 505A. Mr. Kitayuta's Gift 解题报告
题目链接:http://codeforces.com/problemset/problem/505/A 题目意思:给出一个长度不大于10的小写英文字符串 s,问是否能通过在字符串的某个位置插入一个字母 ...
- 水题 Codeforces Round #286 (Div. 2) A Mr. Kitayuta's Gift
题目传送门 /* 水题:vector容器实现插入操作,暴力进行判断是否为回文串 */ #include <cstdio> #include <iostream> #includ ...
- 【CF506E】Mr. Kitayuta's Gift dp转有限状态自动机+矩阵乘法
[CF506E]Mr. Kitayuta's Gift 题意:给你一个字符串s,你需要在s中插入n个字符(小写字母),每个字符可以被插在任意位置.问可以得到多少种本质不同的字符串,使得这个串是回文的. ...
- 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 ...
- CodeForces 505B Mr. Kitayuta's Colorful Graph
Mr. Kitayuta's Colorful Graph Time Limit:1000MS Memory Limit:262144KB 64bit IO Format:%I64d ...
- codeforces 505B Mr. Kitayuta's Colorful Graph(水题)
转载请注明出处: http://www.cnblogs.com/fraud/ ——by fraud Mr. Kitayuta's Colorful Graph Mr. Kitayut ...
- [Codeforces 505C]Mr. Kitayuta, the Treasure Hunter
Description The Shuseki Islands are an archipelago of 30001 small islands in the Yutampo Sea. The is ...
随机推荐
- ubuntu修改软件源的方法
最快方法--替换法 刚安装好的ubutun,打开source.list后,用vim替换的方法将所有的us提付出替换为 cn,然后保存退出,更新即可. # vim /etc/apt/source.lis ...
- from athletelist import AthleteList出现红色下滑波浪线警告
问题:from athletelist import AthleteList出现红色下滑波浪线警告 经过个人网上搜索了解,这个问题是因为python找不到相关的.py文件,无法导入athletelis ...
- 【UE4 材质】一些小功能
利用材质实现物体自转 物体外轮廓高亮 使用postprocess+custom depth(防遮挡) https://www.tomlooman.com/soft-outlines-in-ue4/ h ...
- JVM:内存模型
JVM:内存模型 说明:这是看了 bilibili 上 黑马程序员 的课程 JVM完整教程 后做的笔记 1. java 内存模型 很多人将[java 内存结构]与[java 内存模型]傻傻分不清,[j ...
- UltraSoft - Beta - Scrum Meeting 12
Date: May 28th, 2020. Scrum 情况汇报 进度情况 组员 负责 今日进度 q2l PM.后端 会议记录修复了课程中心导入作业时出现重复的问题完成了消息中心界面的交互 Liuzh ...
- C/C++中浮点数输出精度的问题
本文使用C++语言书写,对于C的小伙伴们,如果编译不通过的话--就说明C里面没有这个内容,可以跳过 通常来说,我们书写程序主要只用整形变量 (signed/unsigned) (long/long l ...
- 实验 1: SDN拓扑实践
(图片和文档是自己写的,因为在CSDN也写了,所以会有自己的水印) 一.实验目的 能够使用源码安装Mininet: 能够使用Mininet的可视化工具生成拓扑: 能够使用Mininet的命令行生成特定 ...
- 字典树(Trie)
终于学会字典树了,真开心(然后就滚过来写总结了). 首先,字典树到底是个什么东西呢?请看下面这段话: 字典树,常被用来保存与查找大量的字符串,它利用了字符串之间的公共前缀来节约时间,但它的空间花费较大 ...
- Shell 脚本批量添加用户和用户密码
#!/bin/bash#批量添加用户 设置密码for i in `seq 1 10`do if ! id user$i &> /dev/null then useradd user$i ...
- istio基础详解
1.Istio介绍? 官方文档:https://istio.io/docs/concepts/what-is-istio/ 中文官方文档:https://istio.io/zh/docs/concep ...