题目
题目链接:https://ac.nowcoder.com/acm/contest/11177/E
牛牛在玩贪吃蛇,玩贪吃蛇吃掉别的蛇就会变长。牛牛的蛇被别人吃掉后,发现可以看广告复活,且复活后长度不变。
牛牛想到了一个作弊的高招,他和他邀请的 (n-1) 个朋友进入同一个房间玩贪吃蛇,初始时一共有 (n) 条蛇,第 (i) 条蛇的长度为 (L_i)。
进入游戏后,首先牛牛从这 (n) 条蛇中选择一条蛇作为自己的蛇,然后他的第 (1) 个朋友从剩下的 (n-1) 条蛇中选择一条蛇,然后他的第 (2) 个朋友从剩下的 (n-2) 条蛇中选择一条蛇 (cdots) 直到牛牛的第 (n-1) 个朋友选择蛇后,游戏开始,现在他们互相吃掉对方。
设蛇 (A) 此时的长度 (L_A) ,蛇 (B) 此时的长度 (L_B) ,蛇 (A) 吃掉蛇 (B) 后蛇 (A) 长度会变为 (L_A+L_B),而蛇 (B) 则需要看 (x) 秒广告复活。
牛牛的朋友都会帮助牛牛使牛牛的蛇变得尽可能的长,牛牛想知道,(M) 秒后牛牛的蛇最长可以是多长?(注意从时刻 (0) 开始就可以吃了)
(假设蛇吃蛇不花时间,可以无限次复活,除了吃蛇没有其它方式可以变长,房间里除了牛牛和牛牛的朋友外没有别人)
由于答案可能很大,你只需要输出答案对 (10^9+7) 取模的结果。多测。
(Q,nleq 50),(1leq L_i,x,Mleq 10^6)。
思路
首先一秒内可以进行任意次吃的操作,不难发现,如果某个时刻存在两条蛇都活着,那么肯定是其中一条立刻吃掉另一条。如果拖后了再吃或者不吃肯定都不会更优。
所以这个 (m) 和 (x) 都是假的,其实就是说可以进行 (m'=frac{m}{x}+1) 轮互吃,且每一轮都只剩一下一条活着。
而且我们也不用去关心牛牛选的是哪一条蛇,最后活下来的那一只就给牛牛即可。所以我们只关心最大化 (m') 轮后 (L) 中的最大值。
对于初始的序列 (L),把他从小到大排序,如果只能合并一次的话,容易发现是让 (n-1) 吃 (n),(n-2) 吃 (n-1),一直到 (1) 吃 (2) 这样最优。
而吃完后这个序列从单调不降变为了单调不增,所以如果还需要继续合并,那么就是从前往后依次合并。那么其实就是一直重复从后往前,从前往后这两个合并的过程。
因为 (nleq 50),所以直接上矩阵快速幂即可。如果 (m') 是奇数,最后还需要多乘一次。
时间复杂度 (O(Qn^3log m))。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=52,MOD=1e9+7;
int Q,n,t,m;
struct Matrix
{
int a[N][N];
friend Matrix operator *(Matrix a,Matrix b)
{
Matrix c;
memset(c.a,0,sizeof(c.a));
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
for (int k=1;k<=n;k++)
c.a[i][j]=(c.a[i][j]+1LL*a.a[i][k]*b.a[k][j])%MOD;
return c;
}
}a,b,f;
void fpow(Matrix &a,Matrix &f,int k)
{
for (;k;k>>=1,a=a*a)
if (k&1) f=f*a;
}
int main()
{
scanf("%d",&Q);
while (Q--)
{
memset(f.a,0,sizeof(f.a));
memset(a.a,0,sizeof(a.a));
memset(b.a,0,sizeof(b.a));
scanf("%d%d%d",&n,&t,&m);
m=(m)/t+1;
for (int i=1;i<=n;i++)
scanf("%d",&f.a[1][i]);
sort(f.a[1]+1,f.a[1]+1+n);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
a.a[i][j]=b.a[n-i+1][n-j+1]=(i>=j);
b=a*b; fpow(b,f,m/2);
if (m&1)
{
f=f*a;
cout<<f.a[1][1]<<"
";
}
else cout<<f.a[1][n]<<"
";
}
return 0;
}