九月学习记录(数学)

已经停课这么久了,现在才开始写日记会不会晚了点……
不管怎么说,这几天的学习不是很有计划,特别是到了数学板块,每天都在划水 效率都非常低。每天还是稍微写个总结,文字或多或少。希望能坚持下去。

9.24

如上文所说,这几天都在搞数学,主要的学习资料:

上午在机房三,网卡的一批。不过反倒让我认真看了看数学(平时都看不进去)。

打了个线段树的题,发现暴力修改也能过?只是要维护区间最大值,当其为1时return,因为1不用开根。

求ax+by=c的整数解——扩欧模板(ax+by=gcd(a,b)时的一组解,求ax+by=c按c/gcd比例放大或缩小):

void euclid(int a,int b,int &x,int &y)
{
	if(b==0)
	{
		x=1,y=0;
		gcd=a;
		return;
	}
	euclid(b,a%b,y,x);
	y-=a/b*x;
}
Luogu P1516 青蛙的约会

一看题就感觉是拓欧题目(废话么,本来就搜索的是“拓欧”标签)但刚拿起笔手玩x,y却发现并非ax+by=c的线性不定方程形式,还是得看题解……发现要对数据进行转化。
设跳了k步,当青蛙A追上B(B追上A是一样的)时,A跳的长度为km,b跳的长度为kn,跳跃距离之差为km-kn,等于二者出发点距离y-x。并且因为地球是圆的 跳的路径首尾相接且长度为L,故 km-kn=y-x(mod L)
转换可得 (x-y)+k(m-n)=0(mod L)。因为是在模L的意义下,所以我们设 (x-y)+k(m-n)=t×L(t∈Z)
推出:

t×L+k(m-n)=(x-y)

再来对比一下

ax+by=c

L、m-n、x-y已知,而我们要求的是k。只要按照上文euclid模板打即可。
但是,我们要求他们第一次碰面,即最小的k。
当c不为gcd(a,b)的倍数时,无正整数解,直接输出“Impossible”。
这里我们需要学习,知道了一组解,如何求得全部解。直接上公式我不会证明

x’=x+k(b/gcd(a,b))
y’=y-k(a/gcd(a,b))

知道一组解x,y,可通过加减k(a/gcd(a,b))求出其他所有x’、y’。
要求最小的正整数解k(可看做x),直接通过对取模k(b/gcd(a,b))即可。
还有一点,对k(b/gcd(a,b))取模时k(b/gcd(a,b))要取绝对值。

9.25

期望在国庆前搞完数学

Luogu P3811 【模板】乘法逆元

参考zjp_shadow大佬的题解。

求解逆元的方式
  • 拓展欧几里得

利用拓欧求解线性同余方程 a ∗ x ≡ c ( mod b ) 的c=1的情况。我们就可以转化为解 a * x + b * y = 1这个方程的形式。即a * x - c ≡ 0 —> a * x - c ≡ k * b
c = ax-kb

  • 快速幂

利用费马小定理
若p为素数,a为正整数,且a、p互质。 则有a^(p-1 ) ≡ 1 ( mod p )。
即a * a ^ ( p - 2 ) = 1,故a的逆元为a ^ ( p - 2 ),用快速幂求解

  • 线性算法

用于求一连串数字对于mod p的逆元。
首先知道1逆元为1。要求 i 对于模 p 的逆元,设其为 x,即 i * x = 1 (mod p)。

  1. 设 p =k * i + r。则 k = ⌊ p / i ⌋(向下取整),r = p % i。
  2. 已知 k * i + r ≡ 0 (mod p)
  3. 两边同乘 i -1 r -1 即 k * r -1 + i -1 ≡ 0 。
  4. 故 i -1≡ -k * r -1
  5. 又 k = ⌊ p / i ⌋,r = p % i
  6. 所以 i -1≡ -⌊ p / i ⌋ * (p % i)-1

注意因为要求正整数逆元,所以要去掉 -⌊ p / i ⌋ * (p % i)-1 式子的负号,直接乘上 p * (p % i)-1,变成 (p -⌊ p / i ⌋ ) * (p % i)-1 ,模 p 后结果不变。

代码

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll maxn=3e6+5;
ll n,p;
ll inv[maxn];

int main()
{
	scanf("%lld%lld",&n,&p);
	inv[1]=1;
	for(ll i=2;i<=n;i++) inv[i]=(p-p/i)%p*inv[p%i]%p;
	for(ll i=1;i<=n;i++) printf("%lld\n",inv[i]);
	return 0;
}
Luogu P5431 【模板】乘法逆元2

我可能做了个假逆元题……
不需要挨个求出每个 a[i] 的逆元,它们不连续且值太大,不易存储。所以……
我们直接通分!
答案的分母即为所有 a[i] 值乘积。要求分子,需维护出每个数之前的 a[i] 乘积,即 pre[i] = a[1] * a[2] * ……a[i-1],和该数之后的 a[i] 乘积,即suf[i]=a[i+1] * a[i+2] * ……* a[n]。通分后的该数分子即为pre[i-1] * suf[i+1] * k^i。分母可直接求单个逆元,与线性算法思想相同,不过是用递归形式

ll inv(ll x)
{
	if(x==1) return 1;
	else return ((mod-mod/x)%mod*inv(mod%x)%mod)%mod;
}

完整代码:

#include<bits/stdc++.h>
using namespace std;

const int maxn=5e6+100;
typedef long long ll;
ll sum,ans,tot=1;
int n,mod,k;
int w[maxn],pre[maxn],suf[maxn];

int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
	while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

ll qpow(int x,int p)
{
	ll r=1;
	while(p)
	{
		if(p&1) r=(r%mod)*(x%mod)%mod;
		x=(x%mod)*(x%mod)%mod;
		p>>=1;
	}
	return r;
}

ll inv(ll x)
{
	if(x==1) return 1;
	else return ((mod-mod/x)%mod*inv(mod%x)%mod)%mod;
}

int main()
{
	//freopen("input.txt","r",stdin);
	n=read(),mod=read(),k=read();
	for(int i=suf[n+1]=pre[0]=1;i<=n;++i)
    pre[i]=(ll)pre[i-1]*(w[i]=read())%mod;
    for(int i=n;i;--i)
    suf[i]=(ll)suf[i+1]*w[i]%mod;
	for(int t=k,i=1;i<=n;++i,t=(ll)t*k%mod)
	{
		sum=(sum+(ll)t*pre[i-1]%mod*suf[i+1]%mod)%mod;
	}
	sum=(sum%mod*(ll)inv(pre[n])%mod)%mod;
	printf("%lld",sum%mod);
	return 0;
}

乘法逆元适用于形如 a/b ≡ x (mod p)

1.线性同余方程 a ≡ bx (mod p)

2.转化为 ab-1 ≡ x (mod p)

费马小定理: p 为质数 b-1 ≡ bp-2 (mod p)

欧拉定理: b,p 互质 b-1 ≡bϕp-1 (mod p)

乘方算式:底数对p取模,指数对 φp 取模

Poj 1845 Sumdiv

约数和公式:
设 x = p1c1 * p2c2 * ……* pncn,则其约数和为

(1+p11+p12+……p1c1) * (1+p21+p12+……p2c2) *……(1+pn1+pn2+……pncn)。

发现 (1+pi1+pi2+……pici) 是等比数列,可直接用公式得出该数列和(pici+1-1) / (pi-1)。在本题中求的是ab约数和对9901取模,故每个约数的指数要乘以b,其等比数列和稍作变动即可。

需要注意的是,因为等比数列求和时要用到 pi-1 的逆元,而求逆元需要pi-1与9901互质。所以要先判断pi-1是否与9901互质,若是,则直接求逆元。若不是,则可以得到 pi%9901=1,即(1+pi1+pi2+……pici)=(1+11+12+……1ci)=ci+1。

以后要特别注意上面一点,不能直接不假思索地求逆元。而且可以根据该数的形式(如本题的pi-1)进行特判。

代码:

#include <cassert>
#include <cctype>
#include <cerrno>
#include <cfloat>
#include <ciso646>
#include <climits>
#include <clocale>
#include <cmath>
#include <csetjmp>
#include <csignal>
#include <cstdarg>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <algorithm>
#include <bitset>
#include <complex>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <locale>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <typeinfo>
#include <utility>
#include <valarray>
#include <vector>
using namespace std;

typedef long long ll;
const ll maxn=1e4+5;
const ll mod=9901;
ll a,b,cnt,tot,ans=1;
ll prim[maxn],p[maxn],c[maxn];

ll qpow(ll x,ll t)
{
	ll r=1;
	while(t)
	{
		if(t&1) r=r%mod*x%mod;
		x=x%mod*x%mod;
		t>>=1;
	}
	return r;
}

ll inv(ll x)
{
	if(x==1) return 1;
	return (mod-mod/x)%mod*inv(mod%x)%mod;
}

void div(ll x)
{
	tot=0;
	for(ll i=2;i*i<=x;i++)
	{
		if(x%i==0)
		{
			p[++tot]=i;c[tot]=0;
			while(x%i==0) ++c[tot],x/=i;
		}
	}
	if(x>1) p[++tot]=x,c[tot]=1;
}

int main()
{
	//freopen("input.txt","r",stdin);
	scanf("%lld%lld",&a,&b);
	div(a);
	for(ll i=1;i<=tot;i++)
	{
		if((p[i]-1)%mod==0)
		{
			ans=ans%mod*(b*c[i]+1)%mod;
			continue;
		}
		ll tmp=qpow(p[i],b*c[i]+1)%mod;
		tmp=(tmp-1+mod)%mod;
		tmp=tmp*qpow(p[i]-1,mod-2)%mod;
		ans=ans%mod*tmp%mod;
	}
	printf("%lld",ans%mod);
	return 0;
}

9.26

今天考试,日常爆炸……
死磕第一题,靠一个不知道什么思想的辣鸡程序得到了大众分20,第二题打了个暴力10分,第三题没来得及看……错过暴力20分。
不知为何,我的思路总是跟大家不一样不一样,经常想到什么就一直搞下去,没有什么道理和依据,也不知道用了什么思想。结果费力不讨好。下午讲解完后发现自己完全没必要这样,明明第一题的暴力思想很简单,自己却没想到——也许自己再多考虑一会儿就能想出来吧……不过放马后炮没什么意义,谁让我一直抓住一个点不放呢。第三题居然错过了暴力,考试策略太失败。

简单放一下三道题题意,没有代码因为不会正解

区间

给定一个 1 到 n 的排列 P,求有多少个由连续正整数组成的数字区间 [l,r],能用 P 中两个不相交的区间内的数字恰好拼成。两个区间内的数都要用上。

最大价值

n 个物品,每个物品有两个属性 a i ,b i,从中选出 k 个物品,并将它们排序使得价值最大,价值定义为所有选出物品的贡献。第 i 个物品被放在第 j 个位置会贡献a i · (j − 1) + b i,求 k 分别为 1,2,··· ,n 时能得到的最大价值
n ≤ 3 × 10

距离和

给定一棵 n 个点的树,树上每点有一个颜色,树上每条边长度为 1. 对于每个点,求它到所有颜色最远点的距离的和是多少

水了一天,没打题

9.27

上午写的博客没保存,心态炸了……

Luogu P4777 【模板】扩展中国剩余定理(EXCRT)

同样的,对于这个方程组
在这里插入图片描述
当 ai 不互质,就无法使用中国剩余定理(简称 crt),这时就需要excrt。
对于前两个方程组
x0 ≡ b1 (mod a1)
x0 ≡ b2 (mod a2)

转化为
x0 = k1 * a1 + b1
x0 = k2 * a2 + b2

联立得
k1 * a1 + b1 = k2 * a2 + b2
移项
k1 * a1 - k2 * a2 = b2 - b1
类比
ax + by = z
可用扩欧求解出k1,k2,从而推出x0的值。

已知该方程组的通解为
x = x0 + k * lcm(a1,a2)
所以原方程组
在这里插入图片描述
的解x与x0对于lcm(a1,a2)同余
x ≡ x0 (mod lcm(a1,a2))
再将其与下面的方程组联立得到
在这里插入图片描述
一直这样解下去直到第n次解出最终的解x。

本题运算时因为可能溢出,所以单独用了一个乘法函数,与快速幂类似理解。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=1e5+5;
ll n,x,y,gcd,lcm;
ll a[maxn],b[maxn];

void exgcd(ll fa,ll fb,ll &x,ll &y)
{
	if(fb==0)
	{
		gcd=fa;
		x=1,y=0;
		return;
	}
	exgcd(fb,fa%fb,y,x);
	y-=fa/fb*x;
}

ll mul(ll fa,ll p,ll mod)
{
	ll re=0;
	while(p)
	{
		if(p&1) re=(re+fa)%mod;
		fa=(fa+fa)%mod;
		p>>=1;
	}
	return re%mod;
}

void excrt()
{
	ll la=a[1],lb=b[1];
	for(ll i=2;i<=n;i++)
	{
        ll aa=la,bb=a[i],c=(b[i]-lb%bb+bb)%bb;
        exgcd(aa,bb,x,y);
        ll bg=bb/gcd;//bg是为了求出最小的正整数解
        if(c%gcd!=0) return; //判断是否无解,然而这题其实不用
        x=mul(x,c/gcd,bg);
        lb+=x*la;//更新前k个方程组的答案
        la*=bg;//la为前k个a的lcm
        lb=(lb%la+la)%la;
	}
	printf("%lld",(lb%la+la)%la);
}

int main()
{
	//freopen("input.txt","r",stdin);
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i],&b[i]);
	excrt();
	return 0;
}

(因为没保存,重打了一遍QAQ)

北上广深/拔山盖世算法 好吧人家叫BSGS算法,全称Baby Step,Giant Step
用于求高次同余方程中 ax ≡ b(mod p) 的x。当然《算法竞赛进阶指南》上说还有一种情况是 xa ≡ b,但这超出了我们的探讨范围,学有余力的读者可以查阅“源根”“阶”“指标”等相关资料

x = i * t - j (0<= j < t,0<= i <=t),
ai*t-j ≡ b
两边同乘 ajai*t = b * aj
利用哈希,以等式右边的b * aj为下标存储所有的 j,再枚举等式左边的i,计算出ai*t,哈希查找是否有先前b * aj存储的 j 值,这表明二者相等,即可用i,j,t计算出x值并退出循环。
等等,t都不知道该怎么算?好吧,其实t的值取⌈√p⌉(根号p向上取整),可以看出该算法实际运用了分块的思想。

Luogu P2485 [SDOI2011]计算器

数学综合题……开long long !

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
ll t,k,y,z,x,a,b,p,gcd;

ll qpow(ll x,ll t,ll mod)
{
	ll r=1;
	while(t)
	{
		if(t&1) r=(r*x)%mod;
		x=(x%mod)*(x%mod)%mod;
		t>>=1;
	}
	return r%mod;
}

void exgcd(ll a,ll b,ll &x,ll &y)
{
	if(b==0)
	{
		gcd=a;
		x=1,y=0;
		return;
	}
	exgcd(b,a%b,y,x);
	y-=(a/b)*x;
}

void bsgs()
{
	map<ll,ll>m;m.clear();b%=p;
	ll t=(ll)sqrt(p)+1;
	for(ll val,j=0;j<t;j++)
	{
		val=(ll)b*qpow(a,j,p)%p;
		m[val]=j;
	}
	a=qpow(a,t,p);
	if(a==0)
	{
		if(b==0) printf("1\n");
		else printf("Orz, I cannot find x!\n");
		return;
	}
	for(ll val,j,i=0;i<=t;i++)
	{
		val=qpow(a,i,p);
		//if(m.find(val)==m.end()) continue;
		//j=m[val];
		j = m.find(val) == m.end() ? -1 : m[val];
		if(j>=0&&i*t-j>=0) //为什么找到一个hash符合要求就直接输出?因为j<t,所以即使后面还有符合的i+k,那么该情况下的(i+k)*t+j1也大于当前的i*t+j2
		{
			printf("%d\n",i*t-j);
			return;
		}
	}
	printf("Orz, I cannot find x!\n");
}

int main()
{
	//freopen("input.txt","r",stdin);
	scanf("%lld%lld",&t,&k);
	while(t--)
	{
		scanf("%lld%lld%lld",&a,&b,&p);
		if(k==1) printf("%lld\n",qpow(a,b,p));
		else if(k==2) 
		{
			exgcd(a,p,x,y);
			if(b%gcd)  
			{
				printf("Orz, I cannot find x!\n");
				continue;
			}
			ll mod=p/gcd;
			while(x<0) x+=mod;
			printf("%lld\n",((x*b/gcd)%mod+mod)%mod);
		}
		else if(k==3) bsgs();
	}
	return 0;
}

9.28

上午写的又没保存,我无fuck说
昨晚考了 Comet OJ - 模拟赛测试 Day1,不是很重视,随便打了打就干别的去了,结果连水题也没做起。其实自己正是需要这些比赛经验,以后还是要多打打oj上的比赛。
顺便说一句,我的考试排名 250二百五 似乎暗示着什么

开始搞概率期望了

216. Rainbow的信号

看了半天(“半天”可能是真的,从上午看到下午,看完题目看题解)题意都没搞明白,果真是弟弟行为。

按位计算答案。枚举二进制下的每个数位,数的大小不超过109,所以最多枚举到30位。
对每一位,枚举1到n每个数,把当前枚举的第k个数当做选取范围的右端点r,利用先前维护的值来更新答案。
设当前的枚举的数位为k,当前枚举的是第r个数,当前第r个数的数位的值为v(0或1)
首先知道:l = r 的情况概率为 1 / n2,其他情况均为 2 / n2(因为有( l , r ) , ( r , l )两种选法,当r>l时二者交换),在加入答案时注意乘以2。

当前数位的值v为1时

因为有l = r 的情况,所以xor,and,or的答案都要加上该数位的值(若是第3数位则值为100即十进制下的4)除以n2的概率(设为pos)。
对于 or 和,l 取 r 前面的任意值,[ l, r ]的或(or)值都为1,共有(r-1)种情况,对答案 ansor 贡献(r-1)* pos。
对于 and 和,我们用 last[v]表示上个v出现的位置,只有当前数位的值为1时才能加入答案,因为如果 [ l, r ] 区间有一个值为0则and值立刻变成0了。而 l 的取值范围的数位 k 的值必须为1,这时候就需要用到 last 数组,l 可以选择的区间即为 [ last[0]+1,r ] 。

当前数位的值v为0时

对于 or 和,l 可以取的区间中的数位值必须有一个 1 ,这样异或后的值才会为一,因为last[1] 到 r 之间的数位值都是0,所以该区间不能取,可取的区间为 [1,last[1] ]。

xor 和的讨论有点麻烦,单独列出来。
对于某个数位上,1到n各个数在该数位的数位值,当前为第 r 个数:
在这里插入图片描述
以1为边界将区间分段,为方便理解我将它们编个号:
在这里插入图片描述
当l取1、3、5区间时,[ l , r ] 数位值的xor和为0;l取2、4、6区间时,[ l , r ] 数位值的xor和为1。
发现了吗,每个区间都只有一个1,异或0对xor和的值无影响;而每异或一次1,xor和的值就会由1变0或由0变1,所以我们用c1表示基数区间包含的值的个数,c2表示偶数区间包含的值的个数。
当前数位值为0,l 有c2个可能取值使 [ l , r ] 的xor和为1;当前数位值为1,l 有c1个可能取值使 [ l , r ] 的xor和为1。
c1,c2的维护可通过看下面的代码理解

代码:

#include<bits/stdc++.h>
using namespace std;

int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
	while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

const int maxn=1e5+5;
int n,c1,c2;
int w[maxn],p[maxn],last[2];
double ansxor,ansand,ansor;

void solve(int k)
{
	c1=c2=0;
	last[0]=last[1]=0;
	double pos=(double)(1<<k)/n/n;//当前数位的实际值/n方的概率
	for(int r=1;r<=n;r++)
	{
		int v=((w[r]>>k)&1);
		if(v)
		{
			ansxor+=pos;
			ansand+=pos;
			ansor+=pos;
		}
		if(v)
		{
			ansor+=pos*(r-1)*2;
			ansand+=pos*(r-1-last[0])*2;
			ansxor+=pos*c1*2;
		}
		else 
		{
			ansor+=pos*last[1]*2;
			ansxor+=pos*c2*2;
		}
		++c1;
		if(v) swap(c1,c2); 
		last[v]=r;
	}
}

int main()
{
	//freopen("input.txt","r",stdin);
	n=read();
	for(int i=1;i<=n;i++) w[i]=read();
	for(int i=0;i<=30;i++) solve(i);
	printf("%.3lf %.3lf %.3lf",ansxor,ansand,ansor);
	return 0;
}

今晚又考了 Comet OJ - 模拟赛测试 Day2 ,出题人竟然是暑假里给我们补过课的lch1475369大佬。
昨晚考试的 250名,仿佛隐隐预示着什么……
今晚认真答题,但……实力不够,认真也无济于事。还没考完,我就放弃治疗,来写博客了。

9.29

昨晚的考试花了太多的时间在T2上面,对字符串的处理一直是我的短板,最后还是没搞出来,指针,重载运算符,map,hash什么的一通乱搞,结果又不会调,当场gg。日常没时间打第三题。
今天把T2的暴力65分打了一下,暴枚移动方案,逐行判断单调性。

#include<bits/stdc++.h>
using namespace std;

int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}

int l,r,ans,li,lj,pi[25],pj[25];
int m[25][25],vl[25],vr[25];

bool judge()
{
	li=lj=0;
	memset(pi,0,sizeof(pi));
	memset(pj,0,sizeof(pj));
	for(int i=1;i<=l;i++)
	{
		if(vl[i]) continue;
		lj=0;
		for(int j=1;j<=r;j++)
		{
			if(vr[j]) continue;
			if(li)
			{
				if(pi[j]==2&&m[li][j]<m[i][j]) return 0;
				if(pi[j]==1&&m[li][j]>m[i][j]) return 0;
				if(li!=i) pi[j]=m[li][j]<m[i][j] ? 1:2;
			}
			if(lj) 
			{
				if(pj[i]==2&&m[i][lj]<m[i][j]) return 0;
				if(pj[i]==1&&m[i][lj]>m[i][j]) return 0;
				if(lj!=j) pj[i]=m[i][lj]<m[i][j] ? 1:2;
			}
			lj=j;
		}
		li=i;
	}
	return 1;
}

void dfs(int cl,int cr,int sl,int sr)
{
	if(cr>r) 
	{
		if(judge()) ++ans;
		return;
	}
	if(cl<=l)
	{
		dfs(cl+1,cr,sl,sr);
		if(sl<l-1)
		{
			vl[cl]=1;
			dfs(cl+1,cr,sl+1,sr);
			vl[cl]=0;
		}
	}
	else
	{
		dfs(cl,cr+1,sl,sr);
		if(sr<r-1)
		{
			vr[cr]=1;
			dfs(cl,cr+1,sl,sr+1);
			vr[cr]=0;
		}
	}
}

int main()
{
	//freopen("input.txt","r",stdin);
	l=read(),r=read();
	for(int i=1;i<=l;i++)
		for(int j=1;j<=r;j++) m[i][j]=read();
	dfs(1,1,0,0);
	printf("%d",ans);
    return 0;
}

第二题用trie树

#include<bits/stdc++.h>
using namespace std;

typedef unsigned long long ll;
const int maxn=1e6+5;
const int mod=1e9+7;
int n,k,cnt;
int siz[maxn*26],ch[maxn][30],fac[maxn];
int ok[maxn*26];
char s[maxn],c[maxn];

void ins(char s[]){
	ll len=strlen(s+1),rt=1;
	for(ll i=1;i<=len;i++){
		ll x=s[i]-'a'+1;
		++siz[rt];
		if(!ch[rt][x]) ch[rt][x]=++cnt;
		rt=ch[rt][x];
	}
	ok[rt]=1,++siz[rt];
}

void solve(){
	ll len=strlen(c+1),rt=1,ans=0;
	for(ll i=1;i<=len;i++){
		ll x=c[i]-'a'+1,ord=0;
		--siz[rt];
		for(ll j=1;j<x;j++) ord=(ord+siz[ch[rt][j]])%mod;
		ans=(ans+ord*fac[k]%mod)%mod;
		rt=ch[rt][x];
		if(ok[rt]){
			--k;
			--siz[rt];
			rt=1;
		}
	}
	printf("%d",(ans+1)%mod);
}

int main(){
	//freopen("input.txt","r",stdin);
	scanf("%d%d",&n,&k);
	fac[1]=cnt=1;
	for(ll i=2,j=n-k+1;j<=n-1;i++,j++){
		fac[i]=1ll*fac[i-1]*j%mod;
	}
	for(ll i=1;i<=n;i++){
		scanf("%s",s+1);
		ins(s);
	}
	scanf("%s",c+1);
	solve();
	return 0;
}

下午颓颓颓……
晚上颓颓颓……

9.30

来到成都外国语学校,开始七天补课时光 国庆七天乐

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值