HDU2018多校第八场部分题目

HDU2018多校第八场部分题目

I Make ZYB Happy(hdu 6405)

题目描述
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;
} 

K Pop the Balloons(hdu 6407)

题目描述
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;
} 

你可能感兴趣的:(题解,套题总结,——后缀自动机,数据结构,dp及其优化,——状压dp)