2021牛客多校5(BCDJK)

目录

B:Boxes(数学期望)

 C:Cheating and Stealing(模拟)

D:Double Strings(DP,组合数)

 J:Jewels(二分图最大权完美匹配)

K:King of Range(ST表,双指针)


B:Boxes(数学期望)

题意:

有 n 个盒子,每个盒子中装有黑球白概率均为\frac{1}{2}。打开第 i个盒子所需代价为 w_{i}。现在有一个机会,使用 c 的代价知晓剩余盒子中黑球个数,问使用最优策略开盒子直到直到全部盒子装球颜色情况的期望最小代价。

思路:

只能有一次机会知道剩余盒子黑球的个数,那么最好的结果是用了这次机会花费c,剩余盒子都是黑球或都是白球,那么游戏结束。

由于每个盒子装有黑球白球概率相同,如果有盒子里有黑球也有白球,那么我们肯定查询权值小的盒子。按照权值排序,从小到大开,接下来考虑在第i个盒子打开然后查询且剩下的都是黑球或都是白球

比如

在第0个盒子打开然后查询且剩下的都是黑球或都是白球的代价为C,概率为\frac{1}{2^{n-1}}

在第1个盒子打开然后查询且剩下的都是黑球或都是白球的代价为C+w_{1},概率为\frac{1}{2^{n-2}}

在第2个盒子打开然后查询且剩下的都是黑球或都是白球的代价为C+w_{1}+w_{2},概率为\frac{1}{2^{n-3}}

依此类推

显然如果我们用一次查询的话我们最多只需要开n-1次箱即可,如果我们不用查询,我们就需要开n次箱子,这两种情况最后取个min即可

#include <bits/stdc++.h>
// #define int long long
using namespace std;
const int N = 1e5 + 10;
double w[N];
signed main()
{
	int n;
	double C;
	cin>>n>>C;
	double tot=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%lf",&w[i]);
		tot+=w[i];
	}
	sort(w+1,w+1+n);
	for(int i=1;i<=n;i++)
	{
		w[i]+=w[i-1];
	}
	double ans=C;
	double p=0.5;
	for(int i=n-1;i>=1;i--)
	{
		ans+=w[i]*p;
		p=p*0.5;
	}
	ans=min(ans,tot);
	printf("%.6lf",ans);
}

 C:Cheating and Stealing(模拟)

 思路:

比较难的预处理模拟

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=1e6+10;
const int mod=998244353;
int n;
char s[N];
int L[N],W[N];//L和W的前缀和
int d[N];//i点的分差 w[i]-L[i]
int pL[N],pW[N];//第几个L或W 的坐标
int f1[N],f2[N];//f1[i]表示i点后 win最近的下标 f2[i]表示i点后 lost最近的下标
void init()//预处理f1,f2数组
{
    for(int i=n;i>=1;i--)
    {
        int k=i+1;
        while(k<=n&&d[k]<=d[i]) k=f1[k];
        f1[i]=k;
        k=i+1;
        while(k<=n&&d[k]>=d[i]) k=f2[k];
        f2[i]=k;
    }
}
void slove()
{
    ll ans=0,p=1;
    for(int i=1;i<=n;i++)
    {
        int x=1,last=1e9+7;//last表示该局比赛结束(有一人获胜)的末尾下标 x表示开始
        ll sum=0;//赢得比赛的得分
        while(W[n]-W[x-1]>=i||L[n]-L[x-1]>=i)
        {
            last=1e9+7;
            if(W[n]-W[x-1]>=i)//满足第一个条件
            last=min(last,pW[W[x-1]+i]);//W[x-1]+i表示从x-1开始赢了i分 更新last末尾坐标
            if(L[n]-L[x-1]>=i)
            last=min(last,pL[L[x-1]+i]);
            if(abs(d[last]-d[x-1])<2)//还不存在分差
            {
                int last1=last,last2=last;
                while(last1<=n&&d[last1]-d[x-1]<2)
                last1=f1[last1];//跳到下一个得分的位置
                while(last2<=n&&d[last2]-d[x-1]>-2)
                last2=f2[last2];
                last=min(last1,last2);
                if(last>n)//说明比赛到最后也没有分差
                break;
            }
            if(d[last]-d[x-1]>=2)
            sum++;
            x=last+1;//开始下一句
        }
        ans=(ans+sum*p%mod)%mod;
        p=p*(1+n)%mod;
    }
    cout<<ans%mod;
}
int main()
{
    cin>>n>>(s+1);
    for(int i=1;i<=n;i++)
    {
        L[i]+=L[i-1];//统计前缀和
        W[i]+=W[i-1];
        if(s[i]=='L')
        {
            L[i]++;
            pL[L[i]]=i;//记录第几个L或W 的坐标
        }
        if(s[i]=='W')
        {
            W[i]++;
            pW[W[i]]=i;
        }
        d[i]=W[i]-L[i];//记录分差
    }
    init();
    slove();
}

D:Double Strings(DP,组合数)

题意:

给定长度为 n 与 m 的两个字符串 S,T,问分别从 S,T 中选出长度相同的子序列 a,b 且满足 a 的字典序小于 b 的方案数。n,m<=5*10^3

思路:

我们可以把选出来的子序列a,b分为3段考虑:

第一段:前i-1个字符都相同

第二段:第i个字符不同,且a_{i}<b_{i}

第三段:任意选择子序列,但长度要求相同

解决第一步:如何求出两个字符串任意位置S[1:i],T[1,j]的相同子序列数量, 用动态规划

f[i][j]表示S前i个字符串和T前j个字符串相同子序列的数量。

考虑当S[i]!=T[j]

f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]

考虑S[i]==T[j]

f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+(f[i-1][j-1]+1)

(f[i-1][j]+f[i][j-1]-f[i-1][j-1]考虑的是不包含S[i]==T[j]的情况,f[i-1][j-1]+1考虑的是 包含S[i]==T[j]的情况,+1是因为当前这两个元素相同也属于一种情况)

处理好第一步,接下来我们就可以枚举每个不同位置S[i]<T[j]

然后考虑后面怎么任意选择

假设枚举到S串后面还是剩长度为x,T串后面长度是y

我们要求当选择后面长度0时,长度1时,长度2时...长度min(x,y)时的不同组合,也就是求\sum_{i=0}^{min(x,y)}C_{x}^{i}*C_{y}^{i}

这个式子暴力求肯定t,需要转换,我们知道C_{x}^{i}=C_{x}^{x-i},意思就变成了有两堆球,左边那x-i个,右边拿i个的方案数,等价于有x+y个球,拿出x个球的方案数,所以等价于C_{x+y}^{x}

接下来计算答案即可

(注意一个细节,预处理阶乘时要开到5000*2的空间,因为x+y最大时是N+N。)

#include <bits/stdc++.h>
#define LL long long
#define int long long
using namespace std;
const int N = 5010;
const int mod = 1e9 + 7;
int f[N][N];
LL fact[N*2],infact[N*2];
int qmi(int a, int k, int p) // 快速幂模板
{
	int res = 1;
	while (k)
	{
		if (k & 1)
			res = (LL)res * a % p;
		a = (LL)a * a % p;
		k >>= 1;
	}
	return res;
}
void init()
{
	fact[0] = infact[0] = 1;
	for (int i = 1; i < N*2; i++)
	{
		fact[i] = (LL)fact[i - 1] * i % mod;
		infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
	}
}
signed main()
{
	init();
	string s1, s2;
	cin >> s1 >> s2;
	int lena = s1.size(), lenb = s2.size();
	s1 = " " + s1;
	s2 = " " + s2;
	for (int i = 1; i <= lena; i++)
	{
		for (int j = 1; j <= lenb; j++)
		{
			if (s1[i] == s2[j])
			{
				f[i][j] = f[i - 1][j] + f[i][j - 1] + 1;
				f[i][j]%=mod;
			}
			else
			{
				f[i][j] = ((f[i - 1][j] + f[i][j - 1])%mod - f[i - 1][j - 1]+mod)%mod;
			}
		}
	}
	// 枚举不同Ai,Aj
	LL ans=0;
	for (int i = 1; i <= lena; i++)
	{
		for (int j = 1; j <= lenb; j++)
		{
			if(s1[i]<s2[j])
			{
				int x1=lena-i;
				int x2=lenb-j;
				int a=x1+x2,b=min(x1,x2);
				int res=(fact[a]*infact[a-b]%mod)*infact[b]%mod;
				res=res*(f[i-1][j-1]+1)%mod;
				ans=(ans+res)%mod;
			}
		}
	}
	printf("%lld",ans%mod);
}

 J:Jewels(二分图最大权完美匹配)

题意:

你有n(1≤n≤300)个宝藏,他们分布在一些三维点(xi​,yi​,zi​),他们还有一个各自的上升速度ti​,他们第k秒所在的位置为(xi​,yi​,zi​+k⋅ti​)。你拿掉宝藏的代价为x_{i}^{2}+y_{i}^{2}+(z_{i}+t*v_{i})^{2}​,你每秒只可以拿掉一个宝藏,问拿完n个宝藏的最小代价是多少?

思路:

可以发现,x_{i}y_{i}是不会变的,对答案没有影响,只是z_{i}会随时间而变,我们每秒要选择一个点拿走,就变成了每个点对应一个时间,要求代价最小,问题就变成了二分图最大权完美匹配问题。

左边是n个点,右边是n个时间,匹配的边权是这一点对应的这一时刻所产生的代价,求每个点都匹配成功所产生的最小代价花费是多少。

直接套用KM算法板子即可。

这个题卡最小费用最大流以及KM的常规dfs板子,需要用到KM的bfs的板子,时间复杂度更优秀。

#include <bits/stdc++.h>
#define int long long
#define ll long long
using namespace std;
const int N = 310;
const int INF = 1e15+7;
struct node
{
	int a, v;
} tr[N];
ll w[N][N];
ll la[N], lb[N];
bool va[N], vb[N];
int match[N];
ll delta, upd[N];
int p[N];
ll c[N];
int n;
void bfs(int x)
{
	int a, y = 0, y1 = 0;
	for (int i = 1; i <= n; i++)
		p[i] = 0, c[i] = INF;
	match[y] = x;
	do
	{
		a = match[y], delta = INF, vb[y] = true;
		for (int b = 1; b <= n; b++)
		{
			if (!vb[b])
			{
				if (c[b] > la[a] + lb[b] - w[a][b])
					c[b] = la[a] + lb[b] - w[a][b], p[b] = y;
				if (c[b] < delta)
					delta = c[b], y1 = b;
			}
		}
		for (int b = 0; b <= n; b++)
			if (vb[b])
				la[match[b]] -= delta, lb[b] += delta;
			else
				c[b] -= delta;
		y = y1;
	} while (match[y]);
	while (y)
		match[y] = match[p[y]], y = p[y];
}
ll KM()
{
	for (int i = 1; i <=n; i++)
		match[i] = la[i] = lb[i] = 0;
	for (int i = 1; i <=n; i++)
	{
		for (int j = 1; j <=n; j++)
			vb[j] = false;
		bfs(i);
	}
	ll res = 0;
	for (int y = 1; y <=n; y++)
		res += w[match[y]][y];
	return res;
}
signed main()
{
	cin >> n;
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		int x, y, z, v;
		scanf("%lld%lld%lld%lld", &x, &y, &z, &v);
		ans += x * x;
		ans += y * y;
		tr[i].a = z, tr[i].v = v;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			w[i][j]=-(tr[i].a+tr[i].v*(j-1))*(tr[i].a+tr[i].v*(j-1));
		}
	}
	ans-=KM();
	printf("%lld\n",ans);
}

K:King of Range(ST表,双指针)

题意:

给你长度为n(1≤n≤10^5)的序列a_i​,并且有Q(1≤Q≤200)次查询,每次查询给出一个k(1≤k≤10^9),询问在a中有多少个区间的最大值减掉最小值严格大于k。

思路:

考虑到区间最大值和最小值,想到ST表维护可以快速查询到。

区间越大,最大值和最小值的差只会越来越大,不会越来越小,所以考虑双指针维护

考虑某个区间(i,j)刚好符合条件,j往左移动就不符合要求了,那么j往右 i不动的区间也是符合的,也就是左指针在i时,贡献为n-j+1。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int a[N];
int mn[N][18];
int mx[N][18];
int lg[N];
int n, m;
void init()
{
	for (int j = 0; j < 18; j++)
	{
		for (int i = 1; i + (1 << j) - 1 <= n; i++)
		{
			if (!j)
				mn[i][j] = a[i], mx[i][j] = a[i];
			else
				mx[i][j] = max(mx[i][j - 1], mx[i + (1 << j - 1)][j - 1]),
				mn[i][j] = min(mn[i][j - 1], mn[i + (1 << j - 1)][j - 1]);
		}
	}
	for (int i = 1; i <= n; i++) //预处理log函数 不预处理可能会T
		lg[i] = log2(i);
}
bool check(int l, int r, int k)
{
	int len = r - l + 1;
	// int k = log(len) / log(2);
	int x = lg[len];
	int maxx = max(mx[l][x], mx[r - (1 << x) + 1][x]);
	int minn = min(mn[l][x], mn[r - (1 << x) + 1][x]);
	// cout<<maxx<<" "<<minn<<endl;
	return maxx-minn>k;
}
signed main()
{
	scanf("%lld%lld", &n, &m);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	init();
	while(m--)
	{
		int k;
		scanf("%lld",&k);
		int ans=0;
		for(int i=1,j=1;i<=n;i++)
		{
			while(j<=n&&!check(i,j,k))
			{
				j++;
			}
			if(j<=n)
			{
				ans+=n-j+1;
			}
		}
		printf("%lld\n",ans);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值