[POI2010]CHO-Hamsters

## 首先声明一下,也许本篇题解由于各种原因常数过大,但是由于字符串的长度没有给,在理论复杂度上是最优的O(n^2*|s|+n^3 log m)

这道题目很妙,当时只有@wlzhouzhuan 巨佬一个做出来。

现在想要是那个时候A这道题该有多爽啊(~~好吧2010年我才3岁。。。~~)

首先看到题面,我第一想法是dp不可做,为何?我想:有可能不是所有的串首尾相接呀,可能一个串含在另外两个合并起来的大串里呀!。

其实不是的,比如说aaab abc bcc
答案是6

既然都是首尾相接,那就好办了,我们用string_hash求出任意两个字符串要连接起来的最少长度(记为dis[i][j]),用这个跑dp就行了

dp式子为:f[i][j]:串中已经有了i个字符串,最后一个为j的最小长度。
f[i][j]=min(f[i][j],f[i-1][k]+dis[k][j]);

到这里就有60分了(~~够了够了~~),可是与AK的bmh201708巨佬差距还是不小。

所以我们要进行优化,算法的瓶颈在m上。

但是您有没有发现,这个式子相当于每次**只转移一条边**的floyd???

没错哦,先改写成floyd的式子:f[i][j]=min(f[i][j],f[i][k]+f[k][j])

机房里大多数人放弃在这里,她们想不到了。其实这就相当于把字符串看成点,只经过k条边的最短路,这有个东西叫倍增floyd,请读者自行百度

然后还要连一个超级源,不然不知到从哪个串开始,枚举浪费时间。对于超级源来说,出边连的是对应点的字符串长度,入边连inf(相当于不能走,为何不能走?因为走一步就相当于浪费了一条边(走到了空点上),可是必须得走满呀!!!)

还有一个细节:m在矩乘时要减一,因为快速幂res的“1”不知道(是邻接矩阵吗?不是,应为邻接矩阵自己对自己连边是不花费的,可是这里一个点重复两次要花费呀!!!所以“1”就变成底数,然后m--,保证是m次方)

### 再啰嗦一句,要自己去**写**,不要上其他题解的当,只有自己真正去写,才知道其他写题解的都是假的。。。。。。比如说wlzhouzhuan这位巨佬,他是真的强,写的也好,可是时间复杂度对不上就很尴尬了。。。

详细实现见代码:

#include<bits/stdc++.h>
using namespace std; typedef long long ll; typedef unsigned long long ull;//自然溢出法 const int N=1005;//不给数据范围。。。 const ll d=1003; const ll inf=1e15;//不要太小,其实1e15就够了 int n; ll m; char s[N][10001];//不给数据范围。。。 ll dis[N][N]; ull po[10001]; ull hasa[10001],hasb[10001]; struct ju{ ll a[N][N]; }; ju mul(ju a,ju b){//矩阵乘法,见倍增floyd ju x; int i,j,k; for(i=0;i<=n;i++)for(j=0;j<=n;j++)x.a[i][j]=inf; for(k=0;k<=n;k++){//做floyd for(i=0;i<=n;i++){ for(j=0;j<=n;j++){ x.a[i][j]=min(x.a[i][j],a.a[i][k]+b.a[k][j]); } } } return x; } int len[N]; ull chsuc(int x,int p){//后缀哈希值 return hasa[len[x]]-hasa[p-1]*po[len[x]-p+1]; } ll did(int x,int y,int same){//算dis int i,j; memset(hasa,0,sizeof(hasa)); memset(hasb,0,sizeof(hasb)); for(i=1;i<=len[x];i++)hasa[i]=hasa[i-1]*d+s[x][i]; for(i=1;i<=len[y];i++)hasb[i]=hasb[i-1]*d+s[y][i]; for(i=len[y]-same;i>=1;i--){//注意,当两串相同时不可以一个当两个用,最多匹配到倒数第二位 if(len[x]<i)continue;//i枚举的是b的前缀与a的后缀的重复字符数,显然不能超过len[x] ull h1=hasb[i],h2=chsuc(x,len[x]-i+1); if(h1==h2){ return len[y]-i; } } return len[y]; } int main(){ cin>>n>>m; int i,j; for(i=1;i<=n;i++)scanf("%s",s[i]+1),len[i]=strlen(s[i]+1); po[0]=1; for(i=1;i<=10000;i++)po[i]=po[i-1]*d;//奇怪吗?多写string_hash for(i=0;i<=n;i++){//超级源为0点 for(j=0;j<=n;j++){ if(j==0)dis[i][j]=inf; else if(i==0)dis[i][j]=len[j];//build a super start else dis[i][j]=did(i,j,i==j); } } ju hhh,res; for(i=0;i<=n;i++)for(j=0;j<=n;j++)hhh.a[i][j]=dis[i][j]; res=hhh; m--;//m可以为0哦! if(m==-1){ puts("0"); return 0; } while(m){//快速幂 if(m&1)res=mul(res,hhh); hhh=mul(hhh,hhh); m>>=1; } ll ans=inf; for(i=1;i<=n;i++)ans=min(ans,res.a[0][i]); cout<<ans<<endl; return 0; }

  

 

转载于:https://www.cnblogs.com/zbsakioi/p/11395360.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值