训练实录 | 2021牛客暑期多校训练营5

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=1n1pisi( s i s_i si表示开 i i i个盒子的花费, p i p_i pi表示花费对应的概率)

先说明一下为什么是 [ 1 , n − 1 ] [1,n-1] [1,n1]
0 0 0的情况就是,询问后得到有 0 0 0 n n n个黑球,这时候是不需要开盒子的,开盒子的费用为 0 0 0
不取 n n n的原因是,依据询问的结果和前面 n − 1 n-1 n1个盒子的结果,可以确定最后一个盒子里的颜色,不需要开第 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=2ni1

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=1n12ni1si

因为花费要尽可能小,所以盒子从花费小的开启。当时比赛写代码的时候,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相邻)的最小角的最大值 ,输出度数。


请添加图片描述

思路: 要求最小角的最大值 ,可以用三分套三分 。先 三分枚举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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值