题目描述
http://acm.hdu.edu.cn/showproblem.php?pid=6405
题解
一眼后缀自动机裸题,但是不会用。
把所有串建个广义后缀自动机,具体操作就是在建后缀自动机的时候,对于每个新串从根节点开始跑,如果遇到完全相同的节点那就不开新点,再抄个sam板子就好了。或者也可以先建棵trie,再在trie上建sam。
对于sam上的每个节点,它表示的字符串长度有一个范围。对于这个节点,它的答案就是由这个节点和它right集合里面的点所决定,就是这些节点权值的乘积。
right集合就是这个节点再fail树的子数上所有的叶子结点。
所以我们对于sam上的每个点先把right集合的乘积搞出来,再根据长度搞一个前缀和数组,标记一下,从前往后扫一遍,询问的时候O(1)就好了。
代码
#include
#define ll long long
#define mod 1000000007
#define N 600010
#define D 27
using namespace std;
int n,cnt=1,Q,maxlen,f[N],fa[N],ch[N][D],h[N];
int g[N],val[N],flag[N],ans[N];char str[N];
vector<char>s[N];
void add(int &a,int b){a+=b;if(a>=mod)a-=mod;}
int Pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=(ll)res*a%mod;
a=(ll)a*a%mod;b>>=1;
}
return res;
}
class SAM
{
public:
int build(int x,int c)
{
int nx=++cnt;f[nx]=f[x]+1;g[nx]=1;
while(x&&!ch[x][c])ch[x][c]=nx,x=fa[x];
if(!x)fa[nx]=1;
else
{
int p=ch[x][c];
if(f[p]==f[x]+1)fa[nx]=p;
else
{
int np=++cnt;f[np]=f[x]+1;g[np]=1;
memcpy(ch[np],ch[p],sizeof(ch[p]));
fa[np]=fa[p];fa[nx]=fa[p]=np;
while(x&&ch[x][c]==p)ch[x][c]=np,x=fa[x];
}
}
return nx;
}
void mark(int x,int tp,int val)
{
for(;x;x=fa[x])
{
if(flag[x]==tp)return;
g[x]=(ll)g[x]*val%mod;flag[x]=tp;
}
}
}S;
int main()
{
int pos,x,len;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf(" %s",str);len=strlen(str);pos=1;
for(int j=0;j'a'];
if(x&&f[x]==j+1)pos=x;
else pos=S.build(pos,str[j]-'a');
}
}
for(int i=1;i<=n;i++)
{
scanf("%d",&h[i]);pos=1;
for(int j=0;j!=s[i].size();j++)
pos=ch[pos][s[i][j]-'a'],S.mark(pos,i,h[i]);
}
for(int i=2;i<=cnt;i++)
{
add(val[f[fa[i]]+1],g[i]);
add(val[f[i]+1],mod-g[i]);
maxlen=max(maxlen,f[i]);
}
for(int i=1,res=0,num=1,sum=0;i<=maxlen;i++)
{
add(val[i],val[i-1]);add(sum,val[i]);
num=(ll)num*26%mod;add(res,num);
ans[i]=(ll)sum*Pow(res,mod-2)%mod;
}
scanf("%d",&Q);
while(Q--)
{
scanf("%d",&x);x=min(x,maxlen);
printf("%d\n",ans[x]);
}
return 0;
}
题目描述
http://acm.hdu.edu.cn/showproblem.php?pid=6407
题解
一眼状压,f[i][s]表示前i行列被扎的状态为s的方案数。
但是对于某些行,你可以选择先不扎,然后在下面的行再扎掉。
所以如果只记扎了谁,你就不知道哪一列要被扎,哪一列不用被扎。
所以s要搞成3^n,0表示这列是空的,1表示这列还有剩且没被扎,2表示这列被扎了。
看了题解,还有一钟写法,会快一点。
先爆搜会被扎的列的集合,再进行dp。对于每一行,如果这行存在被扎的集合之外那么这行必须被扎一个气球。
代码
#include
#define ll long long
#define mo 1000000000
#define N 13
#define M 21
#define P 4100
using namespace std;
int T,n,m,k,S,tot,l[50];ll f[M][P],ans[M],fac[M];
vector<int>t[M];
void work(ll a,ll b)
{
if(!a||!b){printf("0\n");return;}
__int128 res=(__int128)a*b;int tot=0;
while(res)l[++tot]=res%10,res/=10;
for(int i=tot;i;i--)printf("%d",l[i]);
printf("\n");
}
int main()
{
char ch;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&m,&n,&k);S=(1<1;
fac[0]=1;for(int i=1;i<=k;i++)fac[i]=fac[i-1]*i;
memset(ans,0,sizeof(ans));
for(int i=1;i<=n;i++)t[i].clear();
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
scanf(" %c",&ch);
if(ch=='Q')t[j].push_back(i-1);
}
for(int x=0;x<=S;x++)
{
f[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int y=x;;y=(y-1)&x)
{
if(!f[i-1][y]){if(!y)break;continue;}
int flag=0;
for(int j=0;j!=t[i].size();j++)
if(!((x>>t[i][j])&1)){flag=1;break;}
if(!flag)f[i][y]+=f[i-1][y];
for(int j=0;j!=t[i].size();j++)
{
if((y>>t[i][j])&1)continue;
if((x>>t[i][j])&1)f[i][y|(1<1][y];
}
f[i-1][y]=0;if(!y)break;
}
}
int cnt=0;
for(int p=x;p;p-=p&-p)cnt++;
ans[cnt]+=f[n][x];
for(int y=x;;y=(y-1)&x){f[n][y]=0;if(!y)break;}
}
for(int i=1;i<=k;++i)work(ans[i],fac[i]);
}
return 0;
}