# Cow and Treats 题解
Cow and Treats 题解
题目大意
在一年成功的牛奶生产后,Farmer John 奖励他的奶牛们它们最喜欢的美味的草。
在田里有 \(n\) 个单位的排成一行的草,每个单位的草有甜味 \(s_i\)。Farmer John 有 \(m\) 头奶牛,每只都有最喜欢的甜味 \(f_i\) 和饥饿值 \(h_i\)。他想要在奶牛选取两个不相交的子集,分别在这行草的左侧和右侧排成一列。注意两边站多少奶牛是无关紧要的。
奶牛会按以下方式被喂:
- Farmer John 会按照指定的顺序依次喂奶牛。 -
- 在奶牛被喂的时候,它会径直从一端走向另一端,一路上把甜味 \(s_i\) 和它喜爱的甜味 \(f_i\) 相同的草吃掉。
- 当它恰好吃了 \(h_i\) 单位的草,它会立即在原地停止不动并睡觉,这会使得其它奶牛无法通过这个位置(不论来自哪一侧)。
- 如果一个奶牛遇到了一个睡着的奶牛或者它走到了整行草的最末尾都没有吃饱,那么它就会变得沮丧。Farmer John 绝对不想让任何奶牛变得沮丧。
注意草不会长回来。并且为了防止奶牛沮丧,Farmer John 不必保证喂了所有奶牛。
惊人的是,Farmer John 已经发现睡着的奶牛是最满足的。如果 Farmer John 安排的最优。求出最多的睡着的奶牛数,并求出在此情况下有多少种左右两侧奶牛的方案 Farmer John 可以选择(对 \(10^9+7\) 取模)。只要这个方案存在一种顺序使得能不让奶牛沮丧即可,Farmer John 具体如何安排是无关紧要的。
\(n,m\le 5000\)
解题思路
首先,我们容易发现一个性质。
每种颜色的奶牛在序列中最多只会出现两次(一次从左边出发,另一次从右边出发)
我们枚举一个分界点 \(i\) ,表示从第一头出发的奶牛是从左边出发的,且它到达的位置
然后对于每个分解点,我们对每种颜色分开处理,最后按照草排列的顺序安排奶牛进入的顺序
显然,从右边出发的奶牛最多走到 \(i+1\)
设\(slm_s\)表示从左边到当前枚举点 \(i\) 的甜味为 \(s\)的草的个数,\(srm_s\) 表示从右边到 \(i+1\) 的甜味为 \(s\) 的草的个数,\(num[f][h]\) 表示要吃不多于 \(h\) 个单位甜味为 \(f\) 的草的牛的个数
首先考虑甜味为 \(s_i\) 的草,设 \(x=s_i\)
从左边走满足有 \(num[x][slm_x]-num[x][slm_x-1]\)头奶牛符合条件
从右边走满足条件的牛有 \(num[x][srm_x]\)头奶牛。但是如果 \(srm_x>=slm_x\),左边派出的奶牛也可能在右边被派出,方案就重复了,所以右边满足的条件的牛数量应为 \(num[x][srm_x]\)
设左边满足条件的牛的数量为 \(sl\) ,右边为 \(sr\)。
如果 \(sl=0\) 则不可能有满足条件的方案,直接跳过
如果 \(sr=0\) 则该方案只能贡献一头奶牛,方案数为 \(sl\)
否则 方案数为 \(sl*sr\) ,能贡献两头奶牛
接下来考虑其他甜味的草。
对于颜色 \(j\) ,从左边走满足要求的有 \(num[j][slm_j]\) 头,从右边走有 \(num[j][srm_j]\)头
接下来就很显然了,就不讲了
有一点是,当两边的符合条件的牛的数量都 \(>=2\) 时,方案数为 \(sl*sr-min(sl,sr)\),这和我们上面的哪个 \(sl*sr\) 形式其实是一样的
代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
typedef long long ll;
using namespace std;
template<typename T>void read(T &x){
x=0;int f(1);char c(getchar());
for(;!isdigit(c);c=getchar())if(c=='-')f=-f;
for(; isdigit(c);c=getchar())x=(x<<3)+(x<<1)+(c-'0');
x*=f;
}
template<typename T>void write(T x){
if(x<0)putchar('-'),x=-x;
if(x/10)write(x/10),x%=10;
putchar(x+'0');
}
const ll mod=1000000007;
ll mo(ll a){
return a>=mod?a-mod:a;
}
const int maxn=5005;
int num[maxn][maxn],slm[maxn],srm[maxn],s[maxn];
int main(){
int n,m;
read(n),read(m);
for(int i=1;i<=n;++i)
read(s[i]),++srm[s[i]];
for(int i=1;i<=m;++i){
int f,h;
read(f),read(h);
++num[f][h];
}
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
num[i][j]+=num[i][j-1];
ll anssum=1,ansnum=0;
for(int i=0;i<=n;++i){
if(i)++slm[s[i]],--srm[s[i]];
ll testsum=1,testnum=0;int sl,sr;
if(i){
sl=slm[s[i]],sr=srm[s[i]];
sr=num[s[i]][sr]-(sr>=sl);
sl=num[s[i]][sl]-num[s[i]][sl-1];
if(!sl)continue;
if(sr)testsum=testsum*sl*sr%mod,testnum+=2;
else testsum=testsum*sl%mod,testnum++;
}
for(int j=1;j<=n;++j){
if(j==s[i])continue;
sl=slm[j],sr=srm[j];
sr=num[j][sr],sl=num[j][sl];
if(!sl&&!sr)continue;
if(!sl||!sr)testsum=testsum*(sl+sr)%mod,testnum++;
else if(sr==1&&sl==1)
testsum=testsum*2%mod,testnum++;
else testsum=testsum*(sl*sr-min(sl,sr))%mod,testnum+=2;
}
if(testnum>ansnum)
ansnum=testnum,anssum=testsum;
else if(testnum==ansnum)
anssum=mo(anssum+testsum)%mod;
}
write(ansnum),putchar(' ');
if(ansnum)write(anssum),putchar('\n');
else puts("1");
return 0;
}