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 ...
随机推荐
- [软工顶级理解组] Alpha阶段团队贡献分评分
评分总表 下述表格适用于前端.后端.爬虫开发者的评分,基础分数为50分,在此基础上进行增减. 类别 程度 加减分 准时性 提前完成 +0 按时完成 +0 延后完成,迟交时间一天内或未延误进度 -2 延 ...
- 了解 js 堆内存 、栈内存 。
js中的堆内存与栈内存 在js引擎中对变量的存储主要有两种位置,堆内存和栈内存. 和java中对内存的处理类似,栈内存主要用于存储各种基本类型的变量,包括Boolean.Number.String.U ...
- 【Golang详解】go语言中并发安全和锁
go语言中并发安全和锁 首先可以先看看这篇文章,对锁有些了解 [锁]详解区分 互斥锁.⾃旋锁.读写锁.乐观锁.悲观锁 Mutex-互斥锁 Mutex 的实现主要借助了 CAS 指令 + 自旋 + 信号 ...
- 单片机STM32的5个时钟源知识
众所周知STM32有5个时钟源HSI.HSE.LSI.LSE.PLL,其实他只有四个,因为从上图中可以看到PLL都是由HSI或HSE提供的. 其中,高速时钟(HSE和HSI)提供给芯片主体的主时钟.低 ...
- 【做题记录】max-min+1=len 区间计数
(来源:XJ高质量原创题) 原题地址 弱化版:CF526F Pudding Monsters 弱化版 题意:\(n\times n\) 的棋盘上有 \(n\) 颗棋子,每行每列都有且仅有一颗棋子,求出 ...
- linux Segmentation faults 段错误详解
什么是段错误 下面是来自 Answers.com 的定义: A segmentation fault (often shortened to segfault) is a particular err ...
- hdu 1166 敌兵布阵(简单线段树or树状数组)
题意: N个工兵营地,第i个营地有ai个人. 三种操作: 1.第i个营地增加x个人. 2.第i个营地减少x个人. 3.查询第i个到第j个营地的总人数. 思路: 线段树or树状数组 代码:(树状数组) ...
- 记一次 .NET 某风控管理系统 内存泄漏分析
一:背景 1. 讲故事 上个月中旬,星球里的一位朋友在微信找我,说他的程序跑着跑着内存会不断的缓慢增长并无法释放,寻求如何解决 ? 得,看样子星球还得好好弄!!! 不管怎么说,先上 windbg 说话 ...
- lvs 四层负载相关
都打开 /etc/sysctl.conf 中的 net.ip4.ip_forward=1.开启路由转发功能. 分发器 : eth0:192.168.1.66 (VIP) eth1:192.168.2. ...
- JMeter跨线程组保持登录(多线程组共享cookie)
使用__setProperty设置全局变量: 1.jmeter中创建一个登录请求,然后执行,察看结果树-->查看返回cookie信息,我的是在Response data中的 Response h ...