2021牛客暑期多校训练营5
B - Boxes
solved by oye. (after)
题意:
给
n
n
n个盒子,每个盒子里装了一个黑球或白球,问确定所有盒子里装的是什么颜色的球,所需要的最少的花费的期望。
c
c
c表示你问剩下没开的盒子里有几个黑球所要花的费用,第二行是开每个盒子所各需要的费用
w
i
w_i
wi。
思路:
有两种情况:
- 不询问,所有盒子都开一遍,则期望为 Σ i = 1 n w i \Sigma^n_{i=1}w_i Σi=1nwi
- 询问,然后开到确定后面都是一种颜色为止。
询问肯定是最开始就询问好比较划算,因为后面询问的话,只能知道剩下的黑球的个数,如果剩下的盒子里不是同一种颜色的,那不是还要再开。而且万一所有盒子里都是同一种颜色,一开始不问,开了几个盒子再问,开盒子的费用就浪费了。所以询问是一开始问的,后面也不用再问了,因为可以根据已经开出来的算后面还有几个。
因为对于每种盒子放球情况,都要问一下,所以 E ( x ) = c + Σ i = 1 n − 1 p i s i E(x)=c+\Sigma^{n-1}_{i=1}p_is_i E(x)=c+Σi=1n−1pisi( s i s_i si表示开 i i i个盒子的花费, p i p_i pi表示花费对应的概率)
先说明一下为什么是
[
1
,
n
−
1
]
[1,n-1]
[1,n−1]。
0
0
0的情况就是,询问后得到有
0
0
0或
n
n
n个黑球,这时候是不需要开盒子的,开盒子的费用为
0
0
0。
不取
n
n
n的原因是,依据询问的结果和前面
n
−
1
n-1
n−1个盒子的结果,可以确定最后一个盒子里的颜色,不需要开第
n
n
n个盒子。
接下来只要计算
p
i
p_i
pi就可以了。
开到第
i
i
i个盒子能确定剩下的盒子里的颜色,说明开到第
i
i
i的时候,已经把所有的黑球或白球开出来了。这种可能有
2
i
2^i
2i种,因为剩下的球的颜色由第
i
i
i个球的颜色决定(比如说第
i
i
i个球是黑色,那么剩下的球就都是白色),所以不考虑后面的球,只考虑前面
i
i
i个能有几种可能。盒子所有的可能有
2
n
2^n
2n种,所以
p
i
=
2
i
2
n
=
1
2
n
−
i
p_i=\frac{2^i}{2^n}=\frac{1}{2^{n-i}}
pi=2n2i=2n−i1
故 E ( x ) = c + Σ i = 1 n − 1 1 2 n − i s i E(x)=c+\Sigma^{n-1}_{i=1}\frac{1}{2^{n-i}}s_i E(x)=c+Σi=1n−12n−i1si
因为花费要尽可能小,所以盒子从花费小的开启。当时比赛写代码的时候,oye我就忘了这事,本来讨论的时候还记得的QAQ,然后就疯狂WA。
还有就是最后要比较不问和问的情况,哪种小输出哪种。oye我是在一开始比较的,以为当
c
c
c比开所有盒子的费用都贵时,才取开所有的盒子的方式,后来想想应该有开到一半,然后发现费用比开所有贵的情况。
——来自废话越来越多的oye
#include<iostream>
#include<string.h>
#include<queue>
#include<stack>
#include<math.h>
#include<map>
#include<set>
#include<algorithm>
#include<sstream>
#include<vector>
#include<ctype.h>
#include<deque>
#include<time.h>
using namespace std;
#define ll long long
const int maxn = 100000 + 5;
const int maxm = 20;
const double esp=1e-8;
double w[maxn];
double s[maxn];
int main ()
{
int n;
double c;
scanf("%d %lf",&n,&c);
s[0]=0;
for(int i=1;i<=n;i++){
scanf("%lf",&w[i]);
}
sort(w+1,w+n+1);
for(int i=1;i<=n;i++){
s[i]=s[i-1]+w[i];
}
double x=c;
for(int i=n-1;i>=1;i--){
x+=s[i]/pow(2.0,n-i);
}
printf("%lf",min(x,s[n]));
}
D - Double Strings
solved by Micky. (after)
题意: 给出两个字符串A,B。要求出A,B的两个不连续的子串a,b的对数。其中a与b要满足:
a与b的长度相等
对于a与b中的某一个位置x,a[x]<b[x]
而对于i<x要满足a[i]=b[i]
思路:
按照题意,可以在A与B中求出公共不连续子串个数,然后对于满足a[x]<b[x]的公共不连续子串后面取y个字符,那么假设公共子串的长度取到了A[i]和B[j],A串和B串还剩下n和m个字符可以取,那么在n与m中取x个字符,那么有C(0,n)*C(0,m)+C(1,n)*C(1,m)+…+C(min(n,m),n)*C(min(n,m),m).
化简得 C(min(n,m),m+n).
所以用动态规划求出A与B的公共不连续子串个数,在满足a[x]<b[x]的条件上,再在这些数上面乘上在后面可以取相同字符的情况。求组合数用逆元!要预处理,不然会超时。
#pragma warning (disable:4996)
#include<iostream>
#include<string.h>
#include<queue>
#include<stack>
#include<math.h>
#include<map>
#include<set>
#include<algorithm>
#include <bitset>
#include<sstream>
#include<vector>
#include <iomanip>
#include<ctype.h>
#include<list>
//#include <unordered_map>
#include<deque>
#include<functional>
using namespace std;
#define ll long long
const int maxn = 5555, inf = 0x3f3f3f3f, mod = 1e9 + 7;
ll fac[maxn*3], f[maxn][maxn];
ll inv[maxn*3];
char a[maxn], b[maxn];
int C(int m, int n)
{
if (m > n)
return -1;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
ll quick_mod(ll n, ll m)
{
ll tmp = n % mod;
ll ans = 1;
while (m)
{
if (m & 1)
ans = ans * tmp % mod;
tmp = tmp * tmp % mod;
m >>= 1;
}
return ans;
}
void init()
{
fac[0] = 1;
for (int i = 1; i <= maxn * 2; i++)
fac[i] = (fac[i - 1] * i) % mod;
inv[maxn * 2] = quick_mod(fac[maxn * 2], mod - 2);
for (int i = maxn * 2 - 1; i >= 0; i--)
inv[i] = (inv[i + 1] * (i + 1)) % mod;
}
int32_t main() {
init();
cin >> a + 1 >> b + 1;
int lena = strlen(a + 1), lenb = strlen(b + 1);
for (int i = 0; i <= max(lena, lenb); i++) {
f[0][i] = f[i][0] = 1;
}
for (int i = 1; i <= lena; i++) {
for (int j = 1; j <= lenb; j++) {
if (a[i] == b[j])f[i][j] = (f[i][j - 1] + f[i - 1][j]) % mod;
else f[i][j] = (f[i][j - 1] + f[i - 1][j] - f[i - 1][j - 1] + mod) % mod;
}
}
ll ans = 0;
for (int i = 1; i <= lena; i++) {
for (int j = 1; j <= lenb; j++) {
if (a[i] < b[j]) {
ans = (ans + f[i - 1][j - 1] * C(lena - i, lena + lenb - i - j)) % mod;
}
}
}
cout << ans;
return 0;
}
F - Finding Points
solved by YukiSam. (after)
题意: 给出一个n条边的凸包以及每个顶点坐标
A
1
,
A
2
.
.
.
A
n
A_{1},A_{2}...A_{n}
A1,A2...An ,现在凸包内部找一点
P
P
P ,求出所有
∠
A
i
P
A
j
∠A_{i}PA_{j}
∠AiPAj (i,j相邻)的最小角的最大值 ,输出度数。
![请添加图片描述](https://img-blog.csdnimg.cn/19a9524b06fa4fc5a9d0bf15eb083862.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzYwMjQzOTE1,size_16,color_FFFFFF,t_70/resize,p_35)
思路: 要求最小角的最大值 ,可以用三分套三分 。先 三分枚举x ,再在固定相同横坐标的前提下, 三分枚举y ,求出最大值。
科普:二分可以用来求解满足单调性 的序列,三分可以用来求解满足凹凸性 的序列的最值(如:二次函数的最值)
基础模板:
//f(x)为目标函数,自己另写
//while循环的条件需要具体问题具体分析,可能不同的题目还需要增加条件
double midlx,midrx;
while(lx+eps<rx)
{
midlx=lx+(rx-lx)/3.0;
midrx=rx-(rx-lx)/3.0;
if(f(midlx)>f(midrx)) rx=midrx;
else lx=midlx;
}
左图:确定x的范围后在区间
[
l
x
,
r
x
]
[lx,rx]
[lx,rx] 内进行三分
右图:在x固定的情况下,通过求线段交点来确定y的范围,在区间
[
l
y
,
r
y
]
[ly,ry]
[ly,ry] 内进行三分
最终确定横纵坐标,即最大值情况下的点P坐标,输出点P确定的情况下的最小角即可。
(讲道理 模拟退火 应该也可以的,可惜我太菜了没写出来,先鸽着,希望日后能想起来补上hhh)
#include<bits/stdc++.h>
using namespace std;
const double Pi = acos((double)(-1));
const int maxn=110;
#define eps 1e-10
int n;
double lx,rx,ly,ry;
int sgn(double x) //判断x是否为0
{
if(fabs(x) < eps) return 0;
else return x < 0?-1:1;
}
struct Point
{
double x,y;
Point(){}
Point(double x,double y):x(x),y(y){}
Point operator + (Point B){return Point(x + B.x,y + B.y);}
Point operator - (Point B){return Point(x - B.x,y - B.y);}
Point operator * (double k){return Point(x*k,y*k);}
Point operator / (double k){return Point(x/k,y/k);}
bool operator == (Point B){return sgn(x - B.x) == 0 && sgn(y - B.y) == 0;}
bool operator < (const Point &b)const{
if(x == b.x) return y < b.y;
return x < b.x;
}
}p[maxn];
struct Line
{
Point p1,p2;
Line(){}
Line(Point p1,Point p2):p1(p1),p2(p2){}
Line(Point p,double angle){ //y = kx + b
p1 = p;
if(sgn(angle - Pi/2) == 0){p2 = (p1 + Point(0,1));}
else {p2 = (p1 + Point(1,tan(angle)));}
}
Line(double a,double b,double c){ //ax + by + c = 0
if(sgn(a) == 0){
p1 = Point(0, -c/b);
p2 = Point(1, -c/b);
}
else if(sgn(b) == 0){
p1 = Point(-c/a,0);
p2 = Point(-c/a,1);
}
else{
p1 = Point(0,-c/b);
p2 = Point(1,(-c - a)/b);
}
}
}line[maxn];
//已知点p及两端点求夹角
double angle(double X1, double Y1, double X2, double Y2, double Xp, double Yp)
{
double theta = atan2(X1 - Xp, Y1 - Yp) - atan2(X2 - Xp, Y2 - Yp);
if(theta > Pi)
theta -= Pi * 2;
if (theta < -Pi)
theta += Pi * 2;
theta = abs(theta * 180.0 / Pi);
return theta;
}
//计算向量叉积
double Cross(Point A,Point B) {return A.x*B.y - A.y*B.x;}
//求两条直线的交点,Line1:ab,Line2:cd,两直线不平行不共线
Point Cross_point(Point a, Point b, Point c, Point d)
{
double s1 = Cross(b - a, c - a);
double s2 = Cross(b - a, d - a);
return Point(c.x * s2 - d.x * s1, c.y * s2 - d.y * s1) / (s2 - s1);
}
//两线段是否非规范相交
bool Cross_segment1(Point a, Point b, Point c, Point d)
{
if(a == c || a == d) return 0;
if(b == c || b == d) return 0;
return
max(a.x, b.x) >= min(c.x, d.x)&&
max(c.x, d.x) >= min(a.x, b.x)&&
max(a.y, b.y) >= min(c.y, d.y)&&
max(c.y, d.y) >= min(a.y, b.y)&&
sgn(Cross(b - a, c - a)) * sgn(Cross(b - a, d - a)) <= 0&&
sgn(Cross(d - c, a - c)) * sgn(Cross(d - c, b - c)) <= 0;
}
//已知凸包中的点p(px,py) 求最小角
double minangle(double px,double py)
{
double ans=180.0;
for(int i=0;i<n;i++)
{
int j=(i+1)%n;
double ag=angle(p[i].x,p[i].y,p[j].x,p[j].y,px,py);
ans=min(ans,ag);
}
return ans;
}
//x确定的情况下三分枚举y找最大值
double f(double x)
{
//确定纵坐标的上下界(直线x与凸包的交点) 以此来缩小查找范围 (如上右图)
Line l=Line{ {x,10000},{x,-10000} };
ly=10000,ry=-10000;
for(int i=0;i<n;i++)
{
if(Cross_segment1(l.p1, l.p2, line[i].p1, line[i].p2) != 1) continue;
Point crossp=Cross_point(l.p1,l.p2,line[i].p1,line[i].p2);
ly=min(ly,crossp.y);
ry=max(ry,crossp.y);
}
//三分枚举y求最大值
double midly,midry;
while(ly+eps<ry)
{
midly=ly+(ry-ly)/3.0;
midry=ry-(ry-ly)/3.0;
if(minangle(x,midly)>minangle(x,midry)) ry=midry;
else ly=midly;
}
return minangle(x,ly);
}
int main()
{
scanf("%d",&n);
lx=10000,rx=-10000,ly=10000,ry=10000;
//确定凸包横纵坐标的边界
for(int i = 0; i < n; ++i)
{
scanf("%lf%lf",&p[i].x,&p[i].y);
lx=min(lx,p[i].x);
rx=max(rx,p[i].x);
ly=min(ly,p[i].y);
ry=max(ry,p[i].y);
}
//将凸包按顶点依次相连成线段
for(int i=0;i<n;i++)
{
line[i]=Line(p[i],p[(i+1)%n]);
}
//三分枚举x找最大值 (模板)
double midlx,midrx;
while(lx+eps<rx)
{
midlx=lx+(rx-lx)/3.0;
midrx=rx-(rx-lx)/3.0;
if(f(midlx)>f(midrx)) rx=midrx;
else lx=midlx;
}
printf("%.14lf\n",f(lx));
}
H - Holding Two
solved by Micky. 00:37:14(+)
题意: 给出n,m
要求出一个只包含0和1的n*m的矩阵,且矩阵中任意三个横着,竖着,斜着的三个相邻元素不能相同
思路:
001100110…
110011001…
001100110…
这样就好了
#pragma warning(disable:4996)
#include<iostream>
#include<string.h>
#include<queue>
#include<stack>
#include<math.h>
#include<map>
#include<set>
#include<algorithm>
#include <bitset>
#include<sstream>
#include<vector>
#include <iomanip>
#include<ctype.h>
#include<list>
//#include <unordered_map>
#include<deque>
#include<functional>
using namespace std;
const int maxn = 1e5 + 9, inf = 0x3f3f3f3f;
long long n, m, t;
bool cmp(int a, int b) {
return a > b;
}
int32_t main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) {
if (i % 2 == 0) {
for (int j = 0; j < m / 2; j++) {
if (j % 2 == 0)cout << "11";
if (j % 2 == 1)cout << "00";
}
if (m % 2 == 1) {
if ((m / 2) % 2 == 0)cout << "1";
else cout << "0";
}
cout << endl;
}
else {
for (int j = 0; j < m / 2; j++) {
if (j % 2 == 0)cout << "00";
if (j % 2 == 1)cout << "11";
}
if (m % 2 == 1) {
if ((m / 2) % 2 == 0)cout << "0";
else cout << "1";
}
cout << endl;
}
}
return 0;
}
J - Jewels
solved by Micky. (after)
题意:
有一个三维的空间,在空间中给出n个点,获得这些点的花费是这些点到原点(0,0,0)的距离的平方。并且这些点在以vi的速度在向上移动,即坐标(x,y,z+vi*t)。初始时t=0.每获取一个点t+1。
问要获取所有点,最少花费是多少?
思路:
建立二分图,左边是时间,右边是空间中的点,对于左边的点t与右边的点(x,y,z)。两点连边的权值是-(xx+yy+(z+vit)(z+vi*t))。(由于要算最小花费,把权值设为负的)
建完图后套km模板就行了。
#pragma warning(disable:4996)
#include<iostream>
#include<string.h>
#include<queue>
#include<stack>
#include<math.h>
#include<map>
#include<set>
#include<algorithm>
#include<sstream>
#include<vector>
#include<ctype.h>
#include<list>
//#include <unordered_map>
#include<deque>
#include<functional>
using namespace std;
#define ll long long
const int N = 4e2 + 10;
const int INF = 0x3f3f3f3f;
int n;
struct point {
ll x, y, z, v;
}po[N];
int nx, ny;//两边的点数
ll g[N][N];//二分图描述
ll linker[N], lx[N], ly[N];//y 中各点匹配状态,x,y 中的点标号
ll slack[N];
bool visx[N], visy[N];
int pre[N];
void bfs(int k) {
int x, y = 0, yy = 0;
ll delta;
memset(pre, 0, sizeof pre);
for (int i = 1; i <= ny; i++)slack[i] = 1e13;
linker[y] = (ll)k;
while (1) {
x = linker[y]; delta = 1e13; visy[y] = true;
for (int i = 1; i <= ny; i++) {
if (!visy[i]) {
if (slack[i] > lx[x] + ly[i] - g[x][i]) {
slack[i] = lx[x] + ly[i] - g[x][i];
pre[i] = y;
}
if (slack[i] < delta)delta = slack[i], yy = i;
}
}
for (int i = 0; i <= ny; i++) {
if (visy[i]) {
lx[linker[i]] -= delta;
ly[i] += delta;
}
else slack[i] -= delta;
}
y = yy;
if (linker[y] == -1)break;
}
while (y) {
linker[y] = linker[pre[y]];
y = pre[y];
}
}
ll KM() {
memset(linker, -1, sizeof linker);
memset(lx, 0, sizeof lx);
memset(ly, 0, sizeof ly);
for (int i = 1; i <= ny; i++) {
memset(visy, false, sizeof visy);
bfs(i);
}
ll res = 0;
for (int i = 1; i <= ny; i++)
if (linker[i] != -1)
res += g[linker[i]][i];
return res;
}
int main(void)
{
long long ans = 0;
scanf("%d", &n);
ny=nx=n;
for (int i = 1; i <= n; i++) {
scanf("%lld%lld%lld%lld", &po[i].x, &po[i].y, &po[i].z, &po[i].v);
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < n; j++) {
g[i][j + 1] = -(po[i].x * po[i].x + po[i].y * po[i].y + (po[i].z + j * po[i].v) * (po[i].z + j * po[i].v));
}
}
printf("%lld\n", -KM());
return 0;
}
K King of Range
solved by Micky. (after)
题意:
给一串长度为n的数组a。
有m次询问,每次询问一个k,输出在a中有多少个连续子串,满足该字串的的最大值减最小值大于k的。
思路: 用RMQ维护子串的最大值减最小值。:
void init(void) {
for (int j = 1; j <= 16; j++) {
for (int i = 1; i + (1 << j) - 1 <= n && i <= n; i++) {
maxx[i][j] = max(maxx[i][j - 1], maxx[i + (1 << (j - 1))][j - 1]);
minn[i][j] = min(minn[i][j - 1], minn[i + (1 << (j - 1))][j - 1]);
}
}
}
int query(int l, int r)
{
int k = lg[r - l + 1];
return max(maxx[l][k], maxx[r - (1 << k) + 1][k]) - min(minn[r - (1 << k) + 1][k], minn[l][k]);
}
如果枚举左右区间肯定会超时。所以枚举左区间
移动右区间,如果遇到了query(i, j) > k说明此时区间合法,那么对于左区间从j一直到n这一段全部合法。所以ans+=n-j+1。
这里比较关键的一点是j不用回溯:
如果此时遍历到左区间枚举到i1,右区间枚举到j1时有query(i1, j1) <= k。
接着移动右区间,当右区间枚举到j2时有query(i1, j2) > k,此时加上贡献。
然后移动左区间,变成i2=i1++。
显然对于任意的j (i2 <= j <= j1)都有query(i2, j) <= k。
所以就不用回溯右区间。
#pragma warning(disable:4996)
#include<iostream>
#include<string.h>
#include<queue>
#include<stack>
#include<math.h>
#include<map>
#include<set>
#include<algorithm>
#include <bitset>
#include<sstream>
#include<vector>
#include <iomanip>
#include<ctype.h>
#include<list>
//#include <unordered_map>
#include<deque>
#include<functional>
using namespace std;
const int maxn = 1e5 + 9, inf = 0x3f3f3f3f;
int n, m, t, a[maxn], k;
int maxx[maxn][30], minn[maxn][30];
int lg[maxn];
void init(void) {
for (int j = 1; j <= 16; j++) {
for (int i = 1; i + (1 << j) - 1 <= n && i <= n; i++) {
maxx[i][j] = max(maxx[i][j - 1], maxx[i + (1 << (j - 1))][j - 1]);
minn[i][j] = min(minn[i][j - 1], minn[i + (1 << (j - 1))][j - 1]);
}
}
}
int query(int l, int r)
{
int k = lg[r - l + 1];
return max(maxx[l][k], maxx[r - (1 << k) + 1][k]) - min(minn[r - (1 << k) + 1][k], minn[l][k]);
}
int main() {
lg[0] = -1;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
maxx[i][0] = a[i];
minn[i][0] = a[i];
lg[i] = lg[i >> 1] + 1;
}
init();
while (m--) {
scanf("%d", &k);
long long ans = 0;
for (int i = 1, j = 1; i <= n; i++)
{
while (query(i, j) <= k && j <= n)
j++;
ans += n - j + 1;
}
printf("%lld\n", ans);
}
return 0;
}