2019牛客多校训练第一场题解
考虑位置\(i\)为区间最小值的下标,那么只需要找到左边第一个值比它小的位置就行了。单调栈搞一搞就行。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int a[2][N];
int n;
int L[2][N];
int sta[2][N];
int main() {
ios::sync_with_stdio(false); cin.tie(0);
while(cin >> n) {
for(int i = 1; i <= n; i++) cin >> a[0][i];
for(int i = 1; i <= n; i++) cin >> a[1][i];
for(int i = 1; i <= n; i++) L[0][i] = L[1][i] = i;
L[0][n + 1] = -1;
for(int k = 0; k < 2; k++) {
int top = 0;
for(int i = n; i >= 0; i--) {
if(top == 0) {
sta[k][++top] = i;
continue ;
}
while(top && a[k][sta[k][top]] > a[k][i]) {
L[k][sta[k][top]] = i;
top--;
}
sta[k][++top] = i;
}
}
for(int i = 1; i <= n + 1; i++) {
if(L[0][i] != L[1][i]) {
cout << i - 1 << '\n';
break ;
}
}
}
return 0;
}
题解参见:传送门
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 << '\n';
}
return 0;
}
题解说的用拉格朗日乘子法,但蒟蒻不会= =
后面知道直接贪心就行了,首先将\(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 << '\n';
else cout << fz << '/' << fm << '\n';
}
return 0;
}
这题有两种解法,计数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] << '\n';
}
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 << '\n';
}
return 0;
}
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 << '\n';
}
return 0;
}
直接考虑子集不好思考,所以我们考虑每个数对答案的贡献。
先插入\(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 << '\n';
}
return 0;
}
不是很懂的线段树+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) << '\n';
}
return 0;
}
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;