20240602比赛总结

T1 等差子序列

https://gxyzoj.com/d/hzoj/p/3638

主要在枚举的方法上,要选小常数较小的方法

30pts:

枚举前两个数,然后算出第三个数的值,看位置是否满足条件,最慢的点会跑到1.16s

100pts:

上面的方法中,不是每组数都可以满足条件,可能会出现大于n或小于1的情况,但是却无法避免对它的的枚举,所以常数较大

可以发现,当第一个数和差确定时,数列必然确定了,所以考虑枚举差,显然,差的范围是固定的,下界为\(\dfrac{1-i}{2}\),上界为\(\dfrac{n-i}{2}\)

所以,只需要判断这三个的位置关系即可

代码:

#include<cstdio>
using namespace std;
int T,n,a[10005],t[10005];
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			t[a[i]]=i;
		}
		int fl=0;
		for(int i=1;i<=n;i++)
		{
			for(int j=(1-i)/2;j<=(n-i)/2;j++)
			{
				int x=t[i],y=t[i+j],z=t[i+j+j];
				if(y<z&&x<y)
				{
					fl=1;
					break;
				}
			}
			if(fl) break;
		}
		if(fl) printf("Y\n");
		else printf("N\n");
	}
	return 0;
}

T2 [Jsoi2015]非诚勿扰

https://gxyzoj.com/d/hzoj/p/450

考场上因为题意的模糊,直接猜了3个结论,加上自己多此一举将括号拆了,导致推了接近1h

首先考虑位于每个位置的男性被选中的概率,假设有k个人,则显然

\(P(1)=p+p(1-p)^k+p(1-p)^{2k}+\cdots\)

\(P(2)=p(1-p)+p(1-p)^{k+1}+p(1-p)^{2k+1}+\cdots\)
……
\(P(k)=p(1-p)^{k-1}+p(1-p)^{2k-1}+p(1-p)^{3k-1}+\cdots\)

显然可以发现,每一项都是上面一项的\((1-p)\)倍,构成等比数列,所以可以直接用\(P(1)\)表示出所有概率,记所有系数的和为x

(此处可以用公式直接求解,也可以暴力相加)

因为这些事件的概率和为1,则\(P(1)\)的概率为\(\dfrac{1}{x}\),其他的按上述规律枚举即可

接下来考虑如何计算,最简单的方法就是倒序枚举女性,再枚举男性,然后记录有多少的概率有编号比当前女性大,选的男性的比当前编号小,可以用前缀和实现

但是可以发现,m其实并不大,所有有很多的枚举是多余的,考虑优化

其实可以在倒序枚举完女性后,就可以只枚举选择的男性,但是此时就不能用前缀和了,因为要实现单点修改区间查询,所有可以使用树状数组

代码:

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
int n,m,cnt[500005];
double P,p[500005],ans;
vector<int>v[500005];
double sum[500005];
int lowbit(int x)
{
	return x & (-x);
}
void add(int x,double val)
{
	while(x<=n)
	{
		sum[x]+=val;
		x+=lowbit(x);
	}
}
double query(int x)
{
	double res=0.0;
	while(x)
	{
		res+=sum[x];
		x-=lowbit(x);
	}
	return res;
}
int main()
{
	scanf("%d%d",&n,&m);
	scanf("%lf",&P);
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		cnt[x]++;
		v[x].push_back(y);
	}
	for(int i=1;i<=n;i++)
	{
		if(!cnt[i]) continue;
		sort(v[i].begin(),v[i].end());
	}
	for(int i=n;i>0;i--)
	{
		if(!cnt[i]) continue;
		double num=1.0,tmp=1.0;
		for(int j=2;j<=cnt[i];j++)
		{
			tmp=tmp*(1.0-P);
			num+=tmp;
		}
		num=1.0/num;
		for(int j=0;j<cnt[i];j++)
		{
			p[j]=num;
			num=num*(1.0-P);
		}
		for(int j=0;j<cnt[i];j++)
		{
			ans=ans+p[j]*query(v[i][j]-1);
		}
		for(int j=0;j<cnt[i];j++)
		{
			add(v[i][j],p[j]);
		}
	}
	printf("%.2lf",ans);
	return 0;
}

T3 [CSP-S 2021] 括号序列

https://gxyzoj.com/d/hzoj/p/3710

题面中说一个合法的子串可以由其他子串拼起来,所有考虑区间dp

显然二维是不够的,需要加一维表示状态

  1. \(dp_{i,j,0}\)表示只由*构成的情况,也是题目中的s

  2. \(dp_{i,j,1}\)表示形如(……)的情况,要求左右直接被括号包裹且左右括号匹配

  3. \(dp_{i,j,2}\)表示左边由括号开始,右边由结束的情况,类似于(……)……**

  4. \(dp_{i,j,3}\)表示两边均为括号且相邻括号之间可以有*的情况

  5. \(dp_{i,j,4}\)表示左边由开始,右边由括号结尾的情况,类似于……**(……)

  6. \(dp_{i,j,5}\)表示左右均为括号的情况,类似于……**(……)……**

接下来考虑转移

  • \(dp_{i,j,0}\)直接特判

  • \(dp_{i,j,1}\)可以视作在一个括号里加入一个合法且两边不全为*的串,所以显然是:

\[dp_{i,j,1}=dp_{i+1,j-1,0}+dp_{i+1,j-1,2}+dp_{i+1,j-1,3}+dp_{i+1,j-1,4} \]

  • \(dp_{i,j,2}\)可以视作一个由一个括号序列加一个全*的序列组成的

\[dp_{i,j,2}=\sum_{k=i}^{j-1} dp_{i,k,3}*dp_{k+1,j,0} \]

  • \(dp_{i,j,3}\)的前面需要括号打头,结尾不用管,但是后面扁蓄是一个完整的括号

\[dp_{i,j,3}=\sum_{k=i}^{j-1} (dp_{i,k,2}+_{i,k,3})*dp_{k+1,j,1}+dp_{i,j,1} \]

  • \(dp_{i,j,4}\)可以视作一个由一个以*开头的序列加一个括号序列组成的

\[dp_{i,j,4}=\sum_{k=i}^{j-1} (dp_{i,k,4}+dp_{i,k,5})*dp_{k+1,j,1} \]

  • \(dp_{i,j,5}\)则是一个以开头和以结尾的序列拼成的

\[dp_{i,j,5}=\sum_{k=i}^{j-1} dp_{i,k,4}*dp_{k+1,j,0} +dp_{i,j,0} \]

代码:

#include<cstdio>
#include<algorithm>
#include<string>
#include<iostream>
#define ll long long
using namespace std;
const int p=1e9+7;
int n,m;
string s;
ll dp[505][505][10];
int main()
{
	scanf("%d%d",&n,&m);
	cin>>s;
	s=" "+s;
	for(int i=1;i<=n;i++)
	{
		dp[i][i-1][0]=1;
	}
	for(int len=1;len<=n;len++)
	{
		for(int i=1;i<=n-len+1;i++)
		{
			int j=i+len-1;
			if(len<=m&&(s[j]=='?'||s[j]=='*'))
			{
				dp[i][j][0]=dp[i][j-1][0];
			}
			if(len>1)
			{
				if((s[i]=='?'||s[i]=='(')&&(s[j]=='?'||s[j]==')'))
				{
					dp[i][j][1]=(dp[i+1][j-1][0]+dp[i+1][j-1][2]+dp[i+1][j-1][3]+dp[i+1][j-1][4])%p;
				}
				for(int k=i;k<=j-1;k++)
				{
					dp[i][j][2]=(dp[i][j][2]+dp[i][k][3]*dp[k+1][j][0]%p)%p;
					dp[i][j][3]=(dp[i][j][3]+(dp[i][k][2]+dp[i][k][3])%p*dp[k+1][j][1]%p)%p;
					dp[i][j][4]=(dp[i][j][4]+(dp[i][k][4]+dp[i][k][5])%p*dp[k+1][j][1]%p)%p;
					dp[i][j][5]=(dp[i][j][5]+dp[i][k][4]*dp[k+1][j][0])%p;
				}
			}
			dp[i][j][3]=(dp[i][j][3]+dp[i][j][1])%p;
			dp[i][j][5]=(dp[i][j][5]+dp[i][j][0])%p;
		}
	}
	printf("%lld",dp[1][n][3]);
	return 0;
}

T4 围栏障碍训练场

https://gxyzoj.com/d/hzoj/p/3678

注意读题,起始点所在的栅栏也要走

显然,最简单的方法就是从上往下依次枚举,但是时间复杂度为\(O(n^2)\)且无法优化

显然,从上方的一个点显然只能到达下方最近的一个栅栏,所以考虑逆推

但是如何求该点会到达哪个栅栏,显然这是一个区间覆盖问题,\(O(n)\)模拟显然会超时,线段树维护即可

代码:

#include<cstdio>
#include<algorithm>
#include<cmath>
#define ll long long
#define lid id<<1
#define rid id<<1|1
using namespace std;
int n,s,p=1e5+1,m=2e5+5;
struct node{
	int l,r;
}a[50005];
struct seg_tree{
	int l,r,val,lazy;
}tr[1000006];
ll dpl[50005],dpr[50005];
void build(int id,int l,int r)
{
	tr[id].l=l,tr[id].r=r;
	tr[id].lazy=-1;
	if(l==r)
	{
		tr[id].val=0;
		tr[id].lazy=-1;
		return;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	return;
}
void pushdown(int id)
{
	if(tr[id].lazy!=-1)
	{
		tr[lid].lazy=tr[rid].lazy=tr[id].lazy;
		tr[lid].val=tr[rid].val=tr[id].lazy;
		tr[id].lazy=-1;
	}
}
void update(int id,int l,int r,int c)
{
	if(tr[id].l==l&&tr[id].r==r)
	{
		tr[id].val=tr[id].lazy=c;
		return;
	}
	pushdown(id);
	int mid=(tr[id].l+tr[id].r)>>1;
	if(r<=mid) update(lid,l,r,c);
	else if(l>mid) update(rid,l,r,c);
	else update(lid,l,mid,c),update(rid,mid+1,r,c);
}
int query(int id,int x)
{
	if(tr[id].l==tr[id].r)
	{
		return tr[id].val;
	}
	pushdown(id);
	int mid=(tr[id].l+tr[id].r)>>1;
	if(x<=mid) return query(lid,x);
	else return query(rid,x);
}
int main()
{
	scanf("%d%d",&n,&s);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&a[i].l,&a[i].r);
		a[i].l+=p,a[i].r+=p;
	}
	build(1,1,m);
	a[0].l=a[0].r=p;
	for(int i=1;i<=n;i++)
	{
		int x=query(1,a[i].l),y=query(1,a[i].r);
		dpl[i]=min(dpl[x]+abs(a[i].l-a[x].l),dpr[x]+abs(a[i].l-a[x].r));
		dpr[i]=min(dpl[y]+abs(a[i].r-a[y].l),dpr[y]+abs(a[i].r-a[y].r));
		update(1,a[i].l,a[i].r,i);
	}
	printf("%lld",min(dpl[n]+abs(a[n].l-s-p),dpr[n]+abs(a[n].r-s-p)));
	return 0;
}
posted @ 2024-06-02 19:36  wangsiqi2010916  阅读(14)  评论(0编辑  收藏  举报