【cf1307E】E. Cow and Treats(组合数学)
题意:
现有\(n,n\leq 5000\)块草皮,每块草皮都有一个美味度\(s_i\)。
现有\(m,m\leq 5000\)头牛,每头牛有两个属性\(f_i\ h_i\),分别表示这头牛只吃美味度为\(f_i\)的草皮以及要吃\(h_i\)块这样的草皮。
每头牛在吃饱过后就会原地打盹,之后的牛都不能从那里跨过去。我们称一头打盹的牛为“幸福的牛”。
现在可以安排这\(m\)头牛从左边出发或者从右边出发,现在要使得“幸福的牛”数量最多,同时统计使得数量最多的方案数。
思路:
容易发现,现在要吃某一种美味度的草皮,从一侧出发的牛不能超过一头。
然后还有一个重要的性质:
- 假设现在确定了边界\(i\),即从左边出发不能超过\(i\)这个位置,从右边出发不能超过\(i\)这个位置。那么一定存在一种方案,使得左侧/右侧的草皮能吃的都吃完。
也就是说,我们不需要考虑牛从两侧出发的顺序,即,我们对于左右两侧同一种美味度的草皮,可以单独考虑其贡献:
- 若对于美味度为\(i\)的草皮,左边有\(l_i\)块,右边为\(r_i\)块,有\(c_{i1}\)头牛吃的草皮数不超过\(l_i\),有\(c_{i2}\)头牛吃的草皮数不超过\(r_i\),不妨\(c_{i1}<c_{i2}\),那么此时方案数为\(c_{i1}\cdot (c_{i2}-1)\)。
就有一个暴力的算法:我们依次每个位置,假设当前枚举的美味度为\(s_i\),那么对于其它美味度的贡献可以直接像上述来计算,对于\(s_i\)来说则只有\(r_{pos_{now}}\)种情况。若这里直接将\(s_i\)按照上述那样来计算,则会计算相同的情况。
这样的话时间复杂度为\(O(n^2)\)或者\(O(n^2logn)\),也能够通过此题。
这里的话可以优化时间复杂度到\(O(n)\)。
我们只需要维护\(tot_{asleep},tot_{ways}\),那么枚举每个位置时,直接减去这种美味度的贡献即可。最后统计完答案了再加回来。这样的话就去掉了一层循环。
代码可能有些细节:
/*
* Author: heyuhhh
* Created Time: 2020/2/18 15:29:15
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '\n'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '\n'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 5000 + 5, MOD = 1e9 + 7;
int qpow(ll a, ll b) {
ll res = 1;
while(b) {
if(b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
int inv(int x) {
return qpow(x, MOD - 2);
}
int n, m;
int a[N];
int f[N], h[N];
int x[N], y[N];
int l[N], r[N];
vector <int> v[N];
void run(){
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> a[i];
++r[a[i]];
}
for(int i = 1; i <= m; i++) {
cin >> f[i] >> h[i];
v[f[i]].push_back(h[i]);
}
for(int i = 1; i <= n; i++) sort(all(v[i]));
pii ans(0, 0);
auto upd = [&](int x, int y) {
if(x > ans.fi) ans = MP(x, 0);
if(x == ans.fi) ans.se = (ans.se + y) % MOD;
};
auto calc = [&](int i) {
int a = upper_bound(all(v[i]), l[i]) - v[i].begin();
int b = upper_bound(all(v[i]), r[i]) - v[i].begin();
if(a > b) swap(a, b);
int cnt1 = 1ll * a * (b - 1) % MOD, cnt2 = a + b;
if(cnt1 > 0) {
x[i] = 2;
y[i] = cnt1;
} else if(cnt2 > 0) {
x[i] = 1;
y[i] = cnt2;
} else {
x[i] = 0;
y[i] = 1;
}
};
int tx = 0, ty = 1;
for(int i = 1; i <= n; i++) {
calc(i);
tx += x[i];
ty = 1ll * ty * y[i] % MOD;
}
upd(tx, ty);
for(int i = 1; i <= n; i++) {
int now = a[i];
tx -= x[now];
ty = 1ll * ty * inv(y[now]) % MOD;
++l[now], --r[now];
if(binary_search(all(v[now]), l[now])) {
int t = upper_bound(all(v[now]), r[now]) - v[now].begin();
if(r[now] >= l[now]) --t;
if(t > 0) {
x[now] = 2;
y[now] = t;
} else {
x[now] = y[now] = 1;
}
int nx = tx + x[now], ny = 1ll * ty * y[now] % MOD;
upd(nx, ny);
}
calc(now);
tx += x[now];
ty = 1ll * ty * y[now] % MOD;
}
cout << ans.fi << ' ' << ans.se << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}
重要的是自信,一旦有了自信,人就会赢得一切。