CF1307(模拟赛记录)
偶然发现一道做过的 G;C 的罚时:没开 longlong,谨记。
然后一个小时没想出 E ……
E 题面:
在一年成功的牛奶生产后,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\)。
赛时一直执着于 "牛的终止位置应该递减"、"同口味的牛不能一起出现" 并依次 DP,根本没有想到在口味的集合固定的时候,排列的顺序也是固定的;同时也同样只想着按照草的口味顺序枚举,根本没有想到枚举左边队列的终止位置。
在一个错误的思路上越走越深,以至于完全没有想着回头尝试一下别的方向。
下面是 E 的正解。
注意到草的口味之间是独立的:当决定了集合里包含哪些牛之后,牛的排列顺序只有一种(这代表不需要乘以某个贡献了)。这启发我们把口味之间分开看,不论每个口味内部选出了怎么样的牛,总能找到且仅有一种排列顺序是合法的。
考虑枚举左边的牛最右到达的位置 \(i\),则右边的牛最左只能走到 \(i+1\)。
先考虑口味 \(s_i\),左边必须有一头口味是 \(s_i\) 的牛吃到 \(i\) 刚好吃饱;同时右边口味是 \(s_i\) 的牛必须在到达 \(i\) 之前就吃饱。
预处理 \(pfx[i][j]\) 表示 \(s_1\sim s_i\) 里有多少个 \(j\),\(suf[i][j]\) 表示 \(s_i\sim s_n\) 里有多少个 \(j\)。用它们可以推出 \(cnt[i][j]\) 表示口味是 \(i\) 且饥饿度 \(\le j\) 的牛个数。
则左边恰好能吃到 \(i\) 吃饱的牛方案数是 \(l=cntp[s_i][pfx[s_i][i]]-cntp[s_i][pfx[s_i][i]-1]\),右边合法的牛数量是 \(r=cnts[s_i][suf[s_i][i+1]]-[suf[s_i][i+1]\ge pfx[s_i][i]]\),减去是因为如果这样就代表从右边进牛的可能包含了所有左边进牛的可能,所以左边一定会消耗掉右边的一头牛的可能性,在这里提前减去这一头被左边用掉的牛。
如果 \(l=0\),说明不可行,直接 break;否则如果 \(r=0\),只有左边能进牛,也就是会多一头睡觉的牛,同时这头牛有 \(l\) 中可能。
当 \(l>0,r>0\),左右两边都能进牛,就会多两头睡觉的牛,同时有 \(l\times r\) 种可能。
再考虑其他口味 \(j\),左边的方案数是 \(l=cnt[j][pfx[j][i]]\),右边的方案数是 \(r=cnt[j][suf[j][i+1]]\),注意这里左边必须进牛已经在口味 \(s_i\) 那里进过了,所以右边不用再减。
如果 \(l=r=0\),说明不可行;如果 \(l=r=1\),这头牛是唯一的,会多一头睡觉的牛,同时有 \(2\) 种可能:左边/右边进入。
如果 \(l,r\) 恰好有一个是 \(0\),则会多一头睡觉的牛,有 \(l+r\) 种可能(选牛)。
当 \(l>0,r>0\),左右两边都能进牛,就会多两头睡觉的牛,同时有 \(l\times r-\min(l,r)\) 种可能。
以上就是对一个固定的分界点 \(i\) 的处理,对于所有分界点 \(i\),取能睡觉的牛最多的,把方案数累加。
最后特判左边不进牛的情况。
upd:会被卡空间 …… 注意到每次只会用到 \(pfx[][i],suf[][i+1]\) 在枚举分界点的同时维护即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5005, MOD = 1e9 + 7;
int n, m;
int s[N];
int pfx[N] = {}, suf[N] = {}, cnt[N][N] = {{}};
//pfx[i]:口味i在s[1~now]有多少个
//cnt[i][j]:口味i,饥饿度<=j的有多少头牛
int f[N], h[N];
int ans_slp = 0, ans_cnt = 0;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
scanf("%d", &s[i]);
suf[s[i]]++;
}
for (int i = 1; i <= m; i++) {
scanf("%d%d", &f[i], &h[i]);
cnt[f[i]][h[i]]++;
cnt[f[i]][n + 1]--;
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cnt[i][j] += cnt[i][j - 1];
for (ll i = 1, slp, c, l, r; i <= n; i++) { //左边的牛恰好到i
pfx[s[i]]++;
suf[s[i]]--;
slp = 0, c = 1;
l = cnt[s[i]][pfx[s[i]]] - cnt[s[i]][pfx[s[i]] - 1];
r = cnt[s[i]][suf[s[i]]] - (suf[s[i]] >= pfx[s[i]] ? 1 : 0);
// cout << i << ':' << l << ',' << r << endl;
if (l == 0)
continue;
else if (r == 0)
slp++, c = c * l % MOD;
else
slp += 2, c = c * l % MOD * r % MOD;
for (int j = 1; j <= n; j++) {
if (j == s[i])
continue;
l = cnt[j][pfx[j]];
r = cnt[j][suf[j]];
if (!l && !r)
;
else if (l == 0 || r == 0)
slp++, c = c * (l + r) % MOD;
else if (l == 1 && r == 1)
slp++, c = c * 2 % MOD;
else
slp += 2, c = c * ((l * r % MOD - min(l, r) + MOD) % MOD) % MOD;
// cout << j << ':' << slp << ' ';
}
// cout << slp << ',' << c <<"!" << endl;
if (slp > ans_slp)
ans_slp = slp, ans_cnt = c;
else if (slp == ans_slp)
ans_cnt = (ans_cnt + c) % MOD;
}
//特殊处理:左边不进牛,只从右边进
ll tmp = 0, tmp_cnt = 1;
for (int i = 1; i <= n; i++) {
if (cnt[i][pfx[i]])
tmp++, tmp_cnt = tmp_cnt * cnt[i][pfx[i]] % MOD;
}
if (tmp > ans_slp)
ans_slp = tmp, ans_cnt = tmp_cnt;
else if (tmp == ans_slp)
ans_cnt = (ans_cnt + tmp_cnt) % MOD;
if (ans_slp)
cout << ans_slp << ' ' << ans_cnt << endl;
else
puts("0 1");
return 0;
}