2021 Xinjiang Provincial Collegiate Programming Contest(A,B)


写在最前面,单纯的想吐槽一下这场的题面读起来是真的绕

A. chino with string

题意:
m ( 1 ≤ m ≤ 200 ) m(1\leq m\leq 200) m(1m200)个长度和不超过 200 200 200的字符串,第 i i i个字符串的贡献为 v i v_i vi,求长度为 n ( 1 ≤ n ≤ 1 0 9 ) n(1\leq n\leq 10^9) n(1n109)的字符串,所有子串能够组成的最大贡献是多少。
思路:
经典的 A C AC AC自动机题目
类比 [JSOI2007]文本生成器,快速可以写出动态规划
在这里插入图片描述
但是由于 n n n的数据范围过大,时间复杂度无法实现
所以需要用到矩阵快速幂
这里先介绍矩阵的一种变形,正常的矩阵运算是 ( A B ) i j = ∑ k = 0 p − 1 a i , k ∗ b k , j (AB)_{ij}=\sum_{k=0}^{p-1}a_{i,k}*b_{k,j} (AB)ij=k=0p1ai,kbk,j,也就是前行乘后列,是一种内层乘法外层加法的复合运算。
已知矩阵乘法具有结合率,所以矩阵可以进行快速幂加速
如果我们定义一种全新的矩阵运算 ( A B ) i j = △ k = 0 p − 1 a i , k □ b k , j (AB)_{ij}=△_{k=0}^{p-1}a_{i,k} \square b_{k,j} (AB)ij=k=0p1ai,kbk,j
只要可以:
1、 □ \square 运算满足交换律,结合律。
2、 △ \triangle 运算满足交换律,结合律。
3、 □ \square △ \triangle 的分配率。
该新矩阵运算法则就可以使用快速幂。
所以 D P { i , j } = M A X t r i e [ p ] [ c h ] = = j D P { i − 1 , p } + v a l p DP\{i,j\}=MAX_{trie[p][ch]==j}DP\{i-1,p\} + val_p DP{i,j}=MAXtrie[p][ch]==jDP{i1,p}+valp也可以
正常的矩阵乘法:
[ 1 1 1 0 ] ∗ [ f [ n ] f [ n − 1 ] ] = [ f [ n + 1 ] f [ n ] ] \begin{bmatrix}1 \quad 1\\1\quad 0\end{bmatrix}*\begin{bmatrix}f[n] \\ f[n-1]\end{bmatrix}=\begin{bmatrix}f[n+1] \\ f[n]\end{bmatrix} [1110][f[n]f[n1]]=[f[n+1]f[n]]
本题我们定义的矩阵乘法:
[ 1 1 1 0 ] ∗ [ f [ n ] f [ n − 1 ] ] = [ m a x { f [ n ] + 1 , f [ n − 1 ] + 1 } m a x { f [ n − 1 ] + 1 , f [ n ] + 1 , f [ n − 1 ] + 0 } ] \begin{bmatrix}1 \quad 1\\1\quad 0\end{bmatrix}*\begin{bmatrix}f[n] \\ f[n-1]\end{bmatrix}=\begin{bmatrix}max\{f[n]+1,f[n-1]+1\} \\ max\{f[n-1]+1,f[n]+1,f[n-1]+0\}\end{bmatrix} [1110][f[n]f[n1]]=[max{f[n]+1,f[n1]+1}max{f[n1]+1,f[n]+1,f[n1]+0}]
这样的矩阵计算似乎叫做 f l o y e d floyed floyed矩阵
于是,一个本来需要连续 n n n次的转移过程,就可以通过矩阵的加速快速求出
第一次见到这样的写法,似乎可以优化见到的大多数 D P DP DP

#include<bits/stdc++.h>
#define int long long
#define inf 1e18
using namespace std;
typedef long long ll;
const int maxn=205;
char s[maxn];
int cnt,fail[maxn],son[maxn],trie[maxn][30];
int f[205][205];
int val[205];
void insert(char s[],int x)
{
	int p=0,l=strlen(s);
	for(int i=0;i<l;i++)
	{
		int u=s[i]-'a';
		if(!trie[p][u])
		{
			trie[p][u]=++cnt;
		}
		p=trie[p][u];		
	}
	son[p]+=val[x];
}
void make_fail()
{
	queue<int>q;
	for(int i=0;i<26;i++)
		if(trie[0][i])
			q.push(trie[0][i]);
	while(!q.empty())
	{
		int t=q.front();
		q.pop();
		for(int i=0;i<26;i++)
		{
			int p=trie[t][i];
			if(!p) trie[t][i]=trie[fail[t]][i];
			else
			{
				fail[p]=trie[fail[t]][i];
				son[p]+=son[fail[p]];
				q.push(p);
			}
		}
	}
}
struct Mat
{
    ll a[maxn][maxn];
    Mat()
    {
        for(int i=0;i<maxn;i++)
            for(int j=0;j<maxn;j++)
                a[i][j]=-inf;
    }
};
Mat mul(Mat p,Mat q)
{
    Mat mat;
    for(int i=0;i<=cnt;i++)
        for(int j=0;j<=cnt;j++)
            for(int k=0;k<=cnt;k++)
                mat.a[i][k]=max(mat.a[i][k],p.a[i][j]+q.a[j][k]);
    return mat;
}
Mat b,ans;
signed main()
{
	int n,m;
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%s",s);
		scanf("%lld",&val[i]);
		insert(s,i);
	}
	make_fail();
	for(int i=0;i<maxn;i++)
        for(int j=0;j<maxn;j++)
            b.a[i][j]=-inf;
    for(int i=0;i<=cnt;i++)
        for(int j=0;j<26;j++)
		{
			b.a[i][trie[i][j]]=son[trie[i][j]];
		} 
	for(int i=0;i<maxn;i++)
	{
		ans.a[0][i]=-inf;
	}
	\\答案矩阵中的数字就是当前为字符串第i为字符时,原DP数组中各项的结果
	\\由于dp[0][0]=0,其他状态无意义,所以初始矩阵仅有a[0][0]=0
    ans.a[0][0]=0;
    while(n)
    {
        if(n%2)
        ans=mul(ans,b);
        b=mul(b,b);
        n/=2;
    }
    ll tmp=-inf;
    for(int i=0;i<=cnt;i++)
        tmp=max(tmp,ans.a[0][i]);
    printf("%lld",tmp);
    return 0;
}

B. cocktail with hearthstone

题意:
一个计数器 ( a , b ) (a,b) (a,b)记录的是进行了 a + b a+b a+b局游戏,胜了 a a a场,负了 b b b场,每次游戏开始会匹配两个胜负场次完全相同的玩家游戏,玩家数量 2 n + m ( 1 ≤ m < n ≤ 2 × 1 0 5 ) 2^{n+m}(1≤m<n≤2×10^5) 2n+m(1m<n2×105),当有一个玩家在赢了 n n n场或输了 m m m场后,就会结束游戏。接下来 q ( q ≤ 2 × 1 0 5 ) q(q\leq 2×10^5) q(q2×105)次询问,每次询问一个 a , b ( 0 ≤ a ≤ n , 0 ≤ b ≤ m ) a,b(0≤a≤n,0≤b≤m) a,b(0an,0bm),且 a = n a=n a=n b = m b=m b=m,求在 ( a , b ) (a,b) (a,b)状态下退游戏的有多少人。
思路:
整个游戏过程如下:
( 0 , 0 ) ( 1 , 0 ) ( 0 , 1 ) ( 2 , 0 ) ( 1 , 1 ) ( 1 , 1 ) ( 0 , 2 ) . . . . . . (0,0) \\(1,0) \qquad(0,1) \\(2,0) \qquad(1,1) \qquad(1,1) \qquad(0,2) \\...... (0,0)(1,0)(0,1)(2,0)(1,1)(1,1)(0,2)......
各个数字在同一行的比例为
1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 . . . . . . 1 \\1\qquad 1 \\1\qquad2\qquad1 \\1\qquad3\qquad3\qquad1 \\1\qquad4\qquad6\qquad4\qquad1 \\1\qquad5\qquad10\qquad10\qquad5\qquad1 \\...... 11112113311464115101051......
观察到比例为杨辉三角
已知杨辉三角第 i i i行第 j j j列的数字为 C i − 1 j − 1 C_{i-1}^{j-1} Ci1j1,第 i i i行的总数为 2 i − 1 2^{i-1} 2i1,那么 ( a , b ) (a,b) (a,b)占总人数的 C a + b a 2 a + b \frac{C_{a+b}^{a}}{2^{a+b}} 2a+bCa+ba
但是这些并非完整的答案,如果 a = n a=n a=n,有一些玩家在 ( a , b − 1 ) , ( a , b − 2 ) . . . (a,b-1),(a,b-2)... (a,b1),(a,b2)...状态下就已经退游戏了,所以应该减去这些位置的贡献,这样一来,能够转化为 ( a , b ) (a,b) (a,b)的有效状态只有 ( a − 1 , b ) (a-1,b) (a1,b),且状态会有一半的人变为 ( a − 1 , b + 1 ) (a-1,b+1) (a1,b+1),所以答案为 C a + b − 1 a − 1 2 a + b − 1 ∗ 2 \frac{C_{a+b-1}^{a-1}}{2^{a+b-1}*2} 2a+b12Ca+b1a1

#include<bits/stdc++.h>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const int maxn=4e5+7;
const int mod=1e9+7;
ll fac[maxn],inv[maxn],finv[maxn];
ll ksm(ll b,ll p){ll r=1;while(p>0){if(p&1){r=(r%mod*(b%mod))%mod;}p>>=1;b=(b%mod*(b%mod))%mod;}return r;}
void binom_init(int x) 
{
    fac[0]=fac[1]=1;
    inv[1]=1;
    finv[0]=finv[1] = 1;
    for(int i=2; i<x; i++)
	{
        fac[i]=fac[i-1]*i%mod;
        inv[i]=mod-mod/i*inv[mod%i]%mod;
        finv[i]=finv[i-1]*inv[i]%mod;
    }
}
ll binom(ll n, ll r)
{
    if(n<r || n<0 || r<0) return 0;
    return fac[n]*finv[r]%mod*finv[n-r]%mod;
}
ll _inv(ll x)
{
	return ksm(x,mod-2);
}
signed main()
{
	
	ll n,m,q;
	scanf("%lld%lld%lld",&n,&m,&q);
	binom_init(n+m);
	ll p=ksm(2,n+m),ans;
	while(q--)
	{
		int a,b;
		scanf("%lld%lld",&a,&b);
		if(a==n)
		{
			ans=(p%mod*_inv(ksm(2,a+b-1))%mod*binom(a+b-1,a-1)%mod*_inv(2)%mod)%mod;
		}
		else
		{
			ans=(p%mod*_inv(ksm(2,a+b-1))%mod*binom(a+b-1,b-1)%mod*_inv(2)%mod)%mod;
		}
		printf("%lld\n",ans%mod);
	}
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值