【P5196】【USACO19JAN】Cow Poetry G
前言
这是我们今天课上一道练习,结果全班就我一个过了。
看到这道题我就有了思路(
不过还是调了很久。
Solution
题意明白,不多赘述。
首先考虑对于一行诗,凑足 \(k\) 个音节有几种方案。
这个很类似于零一背包问题,和采药几乎一样。
我们设 \(f[i]\) 表示凑成 \(k\) 个音节的方案总数。
枚举最后一个单词,得到转移方程:
\[f[i]=\sum f[i-s[q]]
\]
其中 \(s[q]\) 就是某个单词的长度,只有 \(i\ge s[q]\) 时才能转移。
一行诗的情况解决,然后考虑一整首诗。
由题意,两行诗押韵只考虑最后一个字母的韵部。
那么容易想到在上面的方程上加一维,表示最后一个单词的韵部,将之改为:
\[f[i][j]=\sum f[i-s[q]][p]
\]
\(p\) 为任意的韵部。
聪明的你会发现,这一次转移是 \(n^2\) 的。
但是我们发现,\(p\) 的枚举是不必要的。
设 \(sum[i]=\sum f[i][p]\),则上面的转移方程变为 \(f[i][j]=\sum sum[i-s[q]]\)。
时间复杂度骤降为 \(O(n)\)。
然后是如何计算总方案数?很简单。
我们设 \(X_i\) 表示 字母 \(i\) 出现的次数。
则我们可以令某一个字母 \(i\) 押的韵是 \(j\),因为题目还没有要求不同字母押不同的韵,所以这些答案可以使用乘法原理和加法原理加起来。。
推一下柿子得到结果
\[\prod_{i=1}^{26}\sum_{p=1}^n f[k][p]^{X[i]}
\]
其中 \(\Pi\) 枚举字母,\(k\) 是题目给的参数,\(p\) 是任意颜色。
代码
#include <stdio.h>
typedef long long ll;
namespace root
{
#define lc(id) (id<<1)
#define rc(id) (id<<1|1)
#define lowbit(id) (id&-id)
#define repeat(times) while(times--)
static const int Buf_size=1<<25,Int_size=25;
static char F[Int_size];
static char _c;
static bool _f;
static int _x,__cnt;
static const signed int base_10=10,zero(48),nine(57),flag_signed(45);
inline int abs(const int &_x)
{
return _x<0?-_x:_x;
}
inline int max(const int &_x,const int &_y)
{
return _x>_y?_x:_y;
}
inline int min(const int &_x,const int &_y)
{
return _x<_y?_x:_y;
}
inline void swap(int &_x,int &_y)
{
static int _z;
_z=_x;
_x=_y;
_y=_z;
return;
}
#define digit() (zero<=_c&&_c<=nine)
char buf[Buf_size],*p1=buf,*p2=buf;
//#define getchar() *p1++
static char obuf[Buf_size],*p3=obuf;
#define putchar(x) (p3-obuf<Buf_size)?(*p3++=x):(fwrite(obuf,p3-obuf,1,stdout),p3=obuf,*p3++=x)
// template<typename _Tp,const bool is_signed=false>inline void read(_Tp&_x){if(!is_signed){_x=0;_c=getchar();while(!digit())_c=getchar();while(digit()){_x=_x*10+(_c^zero);_c=getchar();}return;}else{_x=0;_f=false;_c=getchar();while(!digit()){if(_c==flag_signed)_f=true;_c=getchar();}while(digit()){_x=_x*10+(_c^zero);_c=getchar();}if(_f)_x=-_x;return;}}
inline int read()
{
_x=0;
_c=getchar();
while(!digit())_c=getchar();
while(digit())
{
_x=_x*10+(_c^zero);
_c=getchar();
}
return _x;
}
template<typename _Tp>inline void write(_Tp _x)
{
if(_x<0)
{
putchar(flag_signed);
_x=-_x;
}
if(_x<base_10)
{
putchar(_x^zero);
return;
}
write(_x/base_10);
putchar(_x%base_10^zero);
}
inline void out(int x,const char end='\n')
{
if(!x)
{
putchar(zero);
putchar(end);
return;
}
if(x<0)
{
x=-x;
putchar(flag_signed);
}
__cnt=0;
while(x)
{
F[++__cnt]=x%base_10;
x/=base_10;
}
while(__cnt)
{
putchar(F[__cnt]^zero);
--__cnt;
}
putchar(end);
return;
}
}//一些函数,缺省源。
namespace solve
{
const int mod=1000000007;//记得取模
using namespace root;
long long base,ret;
int repow(int a,int b)
{
int res=1;
for(;b;b>>=1)
{
if(b&1)
res=(ll)res*a%mod;
a=(ll)a*a%mod;
}
return res;
}//快速幂
char now;
long long res=1,mid;
int sum[5005],s[5005],c[5005],t[5005],f[5005][5005];
void main()
{
// fread(buf,1,Buf_size,stdin);
register int i,j;
// freopen("poetry.in","r",stdin);
// freopen("poetry.out","w",stdout);
sum[0]=1;
register int n=read(),m=read(),k=read();
for(i=1; i<=n; ++i)
{
s[i]=read();
c[i]=read();
}
for(i=1; i<=k; ++i)
{
for(j=1; j<=n; ++j)
{
if(i>=s[j])
{
(f[i][c[j]]+=sum[i-s[j]])%=mod;
(sum[i]+=sum[i-s[j]])%=mod;
}
}//这就是转移一行内的方案
}
for(i=1; i<=m; ++i)
{
++t[getchar()-'A'];//t[]就是上面的X[]
getchar();
}
for(i=0; i<26; ++i)
{
if(t[i]==0)
continue;
mid=0;
for(j=1; j<=n; ++j)
mid+=repow(f[k][j],t[i]);
(res*=mid%mod)%=mod;
}//这就是最后那个柿子
printf("%lld",res);
return;
}
}
int main()
{
solve::main();
return 0;
}