2019牛客多校训练第一场题解
A.Equivalent Prefixes
考虑位置(i)为区间最小值的下标,那么只需要找到左边第一个值比它小的位置就行了。单调栈搞一搞就行。
Code
```cpp #includeB. Integration
题解参见:传送门
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e3 + 5, MOD = 1e9 + 7;
int n;
ll a[N];
ll qp(ll A, ll B) {
ll ans = 1;
for(; B; B >>= 1) {
if(B & 1) ans = ans * A % MOD;
A = A * A % MOD;
}
return ans ;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
while(cin >> n) {
for(int i = 1; i <= n; i++) cin >> a[i];
ll ans = 0;
for(int i = 1; i <= n; i++) {
ll tmp = 2ll * a[i];
for(int j = 1; j <= n; j++) {
if(i == j) continue ;
tmp = (tmp * (a[j] + a[i]) % MOD * (a[j] - a[i]) % MOD + MOD) % MOD;
}
ans = (ans + qp(tmp, MOD - 2)) % MOD;
}
cout << ans << '
';
}
return 0;
}
C.Euclidean Distance
题解说的用拉格朗日乘子法,但蒟蒻不会= =
后面知道直接贪心就行了,首先将(a_i)从大到小排个序,因为题目的条件:(sum{p_i}=1)且(p_i>=0),我们可以先提取一个(m^2)出来,那么我们需要最小化的式子就变为了((a_i-m*p_i)^2)。
然后我们感性理解一下这个式子,相当于把数(m)分配,然后用(a_i)减去。所以直接贪心地想,每次不断减少当前最大的(a_i)就行了,如果有多个都最大就一起减就是了。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e4 + 5;
int n, m;
int a[N] ;
ll gcd(ll A, ll B) {
return B == 0 ? A : gcd(B, A % B);
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
while(cin >> n >> m) {
for(int i = 1; i <= n; i++) cin >> a[i];
ll fm, fz = 0;
ll remain = m, cnt = 0, last;
sort(a + 1, a + n + 1);
for(int i = n; i >= 2; i--) {
cnt++;
int d = a[i] - a[i - 1];
if(remain >= d * cnt) {
remain -= d * cnt;
} else {
last = cnt * a[i] - remain;
remain = -1;
break ;
}
}
if(remain >= 0) {
cnt++;
last = cnt * a[1] - remain;
}
fm = cnt * cnt;
for(int i = n - cnt; i >= 1; i--) {
fz += fm * a[i] * a[i];
}
fz += cnt * last * last; fm *= (m * m);
ll g = gcd(fz, fm);
fz /= g, fm /= g;
if(fm == 1 || !fz) cout << fz << '
';
else cout << fz << '/' << fm << '
';
}
return 0;
}
E.ABBA
这题有两种解法,计数dp或者组合数学来搞。
dp的话,设(dp[x][y])表示目前有(x)个(A),(y)个(B)且满足条件的前缀串的个数,那么转移方程就是(dp[x][y]=dp[x-1][y]+dp[x][y-1]),即分别考虑当前位为(A)或(B)。
但并不是所有的状态都能转移过来,所以就需要一些限制,就以当前考虑放的是(A),已经有(x)个(A),(y)个(B)为例。因为要合法,所以就有限制条件:(y>=x+1-n),也就是说,先尽可能地(AB)配对,然后会剩下一些(A),这些就只能(BA)配对了,此时(B)的个数要不小于剩下的,如果小于就说明会多出一对(AB)了。
另外一个限制条件同理可得。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e3 + 5, MOD = 1e9 + 7;
int n, m;
ll dp[N][N];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
while(cin >> n >> m) {
for(int i = 0; i <= n + m; i++)
for(int j = 0; j <= n + m; j++) dp[i][j] = 0;
dp[0][0] = 1;
for(int i = 0; i <= n + m; i++) {
for(int j = 0; j <= n + m; j++) {
if(i + 1 - n <= j) dp[i + 1][j] = (dp[i + 1][j] + dp[i][j]) % MOD;
if(j + 1 - m <= i) dp[i][j + 1] = (dp[i][j + 1] + dp[i][j]) % MOD;
}
}
cout << dp[n + m][n + m] << '
';
}
return 0;
}
组合数学的话核心思想就是容斥一下,所有情况减去不合法的,证明过程有点类似于卡特兰数的证明过程。
所有的情况很显然为(C_{2*(n+m)}^{n+m}),对于不合法的情况,还是只拿一个举例,另一个同理。
上面说过要满足(y>=x-n),不合法的话说明就存在一个有(x)个(A),(y)个(B)的前缀,满足(x=y+n+1)。然后我们将这个前缀的(A),(B)互换,会得到一个有(m-1)个(A),(n+m+n+1)个(B)的序列。而这两个序列是一一对应的。那么易知不合法的方案数就为(C_{2*(n+m)}^{m-1}),同理另一个为(C_{2*(n+m)}^{n-1})。
(n=0)或(m=0)的情况单独考虑一下就行了,具体来推也是上面的方法。
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 4e3 + 5, MOD = 1e9 + 7;
int n, m;
ll fac[N], inv[N];
ll qp(ll a, ll b) {
ll ans = 1;
for(; b; b >>= 1) {
if(b & 1) ans = ans * a % MOD;
a = a * a % MOD;
}
return ans ;
}
ll C(ll a, ll b) {
return fac[a] * inv[b] % MOD * inv[a - b] % MOD;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
fac[0] = inv[0] = 1;
for(int i = 1; i < N; i++) fac[i] = fac[i - 1] * i % MOD, inv[i] = qp(fac[i], MOD - 2) ;
while(cin >> n >> m) {
ll ans = C(2 * (n + m), n + m);
if(n) ans -= C(2 * (n + m), n - 1);
if(m) ans -= C(2 * (n + m), m - 1);
cout << (ans % MOD + MOD) % MOD << '
';
}
return 0;
}
F.Random Point in Triangle
emmm,找到重心然后推一下就行了,可以用等边三角形来搞一搞。
求期望面积,而底边确定,那么我们就只需要求期望高度,也就是要确定一个点,多边形可以看作一个质量均匀分布的物体,所以找重心就好了。
详细证明可以参见:传送门
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a1, a2, a3, b1, b2, b3;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
while(cin >> a1 >> b1 >> a2 >> b2 >> a3 >> b3) {
ll x1 = a1 - a2, y1 = b1 - b2;
ll x2 = a1 - a3, y2 = b1 - b3;
ll ans = 11 * labs(x1 * y2 - x2 * y1);
cout << ans << '
';
}
return 0;
}
H.XOR
直接考虑子集不好思考,所以我们考虑每个数对答案的贡献。
先插入(n)个数的线性基,得秩为(r),那么现在从非基底中选择一个,它与其余(n-r-1)个的组合的异或值都能被这(r)个基底表示(线性基的性质),这些非基底的贡献还是比较好算的。
求基底的贡献就需要搞点事情了。一个基底有贡献也就等价于需要它来进行异或,也就是说它能被其余的数给异或出来。所以我们就可以枚举基底将其去掉,求出剩下(n-1)个数的基来看是否能够异或出我们枚举的(也可以直接用秩来判断),如果是,那么贡献就为(2^{n-r-1}),因为其余的组合也能被异或出;否则就没有贡献。
这题的关键就是将问题转化为求每个数的贡献,其它的只要比较了解线性基以及异或的一些性质就行了。
代码如下:
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 64, N = 1e5 + 5, MOD = 1e9 + 7;
int n;
ll a[N];
struct linerbase{
int SZ;
ll v[MAX];
void Clear() {
SZ = 0, memset(v, 0, sizeof(v));
}
void Copy(linerbase a) {
SZ = a.SZ; memcpy(v, a.v, sizeof(v));
}
bool Ins(ll x) {
for(int i = 63; i >= 0; i--) {
if(x >> i & 1) {
if(!v[i]) {
v[i] = x;
SZ++;
break;
} else x ^= v[i];
}
}
return x > 0;
}
}b1, b2, b3;
vector <int> ID;
ll qp(ll A, ll B) {
ll ans = 1;
while(B) {
if(B & 1) ans = A * ans % MOD;
A = A * A % MOD;
B >>= 1;
}
return ans;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
while(cin >> n) {
for(int i = 1; i <= n; i++) cin >> a[i];
ll ans = 0, cnt = 0;
b1.Clear(), b2.Clear(), b3.Clear(), ID.clear();
for(int i = 1; i <= n; i++) {
if(!b1.Ins(a[i])) {
b2.Ins(a[i]);
} else {
ID.push_back(i);
}
}
if(b1.SZ != n) {
cnt = qp(2, n - b1.SZ - 1);
ans = (ans + cnt * (n - b1.SZ) % MOD) % MOD;
}
for(auto i : ID) {
b3.Copy(b2);
for(auto j : ID) if(i != j) b3.Ins(a[j]);
if(b3.SZ == b1.SZ) ans = (ans + cnt) % MOD;
}
cout << ans << '
';
}
return 0;
}
I.Points Division
不是很懂的线段树+dp。
分析题目条件可以知道如果一个点属于集合(B),那么其右下角的所有点都必须属于(B)集合。那么最后就会形成一个不降的折线段,之后就在这上面dp。
排序后枚举每个点然后分情况来转移,如果这个点在折线段上面,那么就从(1)到(y_i)上面找一个最大值mx,进行转移(dp[y_i]=mx+b_i);否则,我们就需要对之前的dp值进行更新,对于所有(y)值大于(y_i)的就加上(b_i),小于的就加上(a_i)。
可以证明这样来搞是不会损失最优解的。
代码如下:
Code
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int n;
struct node{
int x, y;
ll a, b;
bool operator < (const node &A)const {
if(A.x == x) return y > A.y;
return x < A.x;
}
}p[N];
int Hash[N];
ll maxx[N << 2], add[N << 2];
void push_up(int o) {
maxx[o] = max(maxx[o << 1], maxx[o << 1|1]);
}
void push_down(int o) {
if(add[o]) {
add[o << 1] += add[o];
add[o << 1|1] += add[o];
maxx[o << 1] += add[o];
maxx[o << 1|1] += add[o];
add[o] = 0;
}
}
void build(int o, int l, int r) {
add[o] = 0;
if(l == r) {
maxx[o] = 0;
return ;
}
int mid = (l + r) >> 1;
build(o << 1, l, mid), build(o << 1|1, mid + 1, r);
push_up(o);
}
ll query(int o, int l, int r, int L, int R) {
if(L <= l && r <= R) return maxx[o];
push_down(o);
int mid = (l + r) >> 1;
ll mx = 0;
if(L <= mid) mx = max(mx, query(o << 1, l, mid, L, R));
if(R > mid) mx = max(mx, query(o << 1|1, mid + 1, r, L, R));
return mx;
}
void update(int o, int l, int r, int k, ll v) {
if(l == r && l == k) {
maxx[o] = v;
return ;
}
push_down(o);
int mid = (l + r) >> 1;
if(k <= mid) update(o << 1, l, mid, k, v);
else update(o << 1|1, mid + 1, r, k, v);
push_up(o);
}
void update2(int o, int l, int r, int L, int R, int v) {
if(L <= l && r <= R) {
maxx[o] += v;
add[o] += v;
return ;
}
push_down(o);
int mid = (l + r) >> 1;
if(L <= mid) update2(o << 1, l, mid, L, R, v);
if(R > mid) update2(o << 1|1, mid + 1, r, L, R, v);
push_up(o);
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
while(cin >> n) {
int D = 0;
Hash[++D] = -INF, Hash[++D] = INF;
for(int i = 1; i <= n; i++) {
cin >> p[i].x >> p[i].y >> p[i].a >> p[i].b;
Hash[++D] = p[i].y;
}
sort(p + 1, p + n + 1);
sort(Hash + 1, Hash + D + 1);
D = unique(Hash + 1, Hash + D + 1) - Hash - 1;
for(int i = 1; i <= n; i++) p[i].y = lower_bound(Hash + 1, Hash + D + 1, p[i].y) - Hash;
build(1, 1, D);
for(int i = 1; i <= n; i++) {
ll mx = query(1, 1, D, 1, p[i].y) ;
update(1, 1, D, p[i].y, mx + p[i].b);
update2(1, 1, D, 1, p[i].y - 1, p[i].a);
update2(1, 1, D, p[i].y + 1, D, p[i].b);
}
cout << query(1, 1, D, 1, D) << '
';
}
return 0;
}
J.Fraction Comparision
python大法好。
Code
while True:
try:
x,a,y,b = map(int,input().split())
x = x * b
y =y *a
if x==y:
print("=")
elif x<y:
print('<')
else:
print('>')
except:
break;