20240705比赛总结

T1 酸碱度中和

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

因为是要将一些数变为一个值,所以差相对小的一些数修改的会更少,所以可以先将原数组排序

因为当x可以时,x+1必然可以,所以考虑二分

接下来考虑到因为上下变动的都至多为m,所以开头和结尾的差必然不超过2m

它就可以看作用一些长度为2m的版进行覆盖,问最少要多少块

显然,从1开始,然后在终点后的第一个点继续,暴力枚举即可

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
int n,k,a[100005];
bool check(int x)
{
	int lst=-2e9-1,cnt=0;
	for(int i=1;i<=n;i++)
	{
		if(a[i]-2*x>lst)
		{
			lst=a[i];
			cnt++;
		}
	}
	if(cnt<=k) return 1;
	return 0;
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	sort(a+1,a+n+1);
	int l=0,r=a[n];
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid))
		{
			r=mid;
		}
		else l=mid+1;
	}
	printf("%d",l);
	return 0;
}

T2 聪明的小明

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

很抽象的状压dp

5pts:k=m,在这种情况下,就是将很多一样的串拼在一起,答案显然是k!

50pts:因为此时只有0和1,考虑状压,将后面m位直接拼起来,然后进行转移即可

100pts:其实我们并不关注每个位置究竟是什么,因为这些就本质上是等价的

而且我们只用判断是否出现,所以只记录最后一位的位置即可

例如对于排列3,2,1,2,3,2,其实可以直接抽象为001101,所以直接状压

所以在初始状态,只需要将每一位的情况数相乘即可

在倒序枚举的情况下,显然,每出现一个1,可选的数量就+1

dpi,s表示目前是第i位,往前m位的状态是s

因为每一次转移,s都要去掉前面的一位,如果是1,则意味着有一种酒必须要加,所以只有一种情况

如果是0,则最后一位是什么都行,所以循环枚举即可

代码:

#include<cstdio>
#define ll long long
using namespace std;
int n,k,m,mod=998244353,dp[100005][1030],st[1030],cnt;
int get_sum(int x)
{
	int res=0;
	while(x)
	{
		if(x&1) res++;
		x>>=1;
	}
	return res;
}
ll clac(int x)
{
	ll num=0,res=1;
	for(int i=0;i<m;i++)
	{
		if((x>>i)&1)
		{
			res=res*(k-num)%mod;
			num++;
		}
		else res=res*num%mod;
	}
	return res;
}
int main()
{
	scanf("%d%d%d",&n,&k,&m);
	for(int i=1;i<(1<<m);i+=2)
	{
		int x=i;
		if(get_sum(x)==k)
		{
			dp[m][x]=clac(x);
			st[++cnt]=x;
		}
	}
	int tmp=(1<<m)-1;
	for(int i=m;i<n;i++)
	{
		for(int j=1;j<=cnt;j++)
		{
			int s=st[j];
			if(!dp[i][s]) continue;
	//		printf("%d %d %d\n",i,s,dp[i][s]);
			if((s>>(m-1))&1)
			{
				int tmp1=((s<<1)&tmp)|1;
				dp[i+1][tmp1]=(dp[i+1][tmp1]+dp[i][s])%mod;
			//	printf("1:%d %d %d %d\n",i,s,tmp1,dp[i+1][tmp1]);
			}
			else
			{
				for(int t=0;t<m;t++)
				{
					if((s>>t)&1)
					{
						int tmp2=((s^(1<<t))<<1)|1;
						dp[i+1][tmp2]=(dp[i+1][tmp2]+dp[i][s])%mod;
				//		printf("2:%d %d %d %d\n",i,s,tmp2,dp[i+1][tmp2]);
					}
				}
			}
		}
	}
	ll ans=0;
	for(int i=1;i<=cnt;i++)
	{
		ans=(ans+dp[n][st[i]])%mod;
	}
	printf("%lld",ans);
	return 0;
}

T3 线段树

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

30pts:直接dfs枚举断点即可

100pts:假设在查询区间[L,R]时,这个区间和[l,r]有交集,且又不是包含关系这样说明你一定会在查询的时候进入[L,R]这个节点,就可以从k处将区间划分开,满足Lk<Rlk<r

此时,必然要递归[L,R]的两个子树,而每个子树至少会产生一个代价,答案下界就+1

所以结论是,在你查询的过程中,“双边递归”的次数就是查询到的区间个数,所以我们就可以按照线段树上节点被“双边递归”的次数来dp

dpl,r表示l到r的区间的双边递归次数,则式子是:

dpl,r=mink=lr1dpl,k+dpk+1,r+cost

而cost指有多少个查询区间与[l,r]有交集且不是包含关系,并经过k+0.5

所以我们可以预处理出经过k+0.5的区间个数,记为wk,这里可以用差分

预处理出包含[l,r]的数量,记为numl,r,这里可以使用二维差分的思想,即:

numl,r=numl1,r+numl,r+1numl1,r+1

此时cost=wknuml,r

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int n,q;
ll cnt[505][505],w[505],dp[505][505];
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=q;i++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		cnt[l][r]++;
		w[l]++,w[r]--;
	}
	for(int i=1;i<=n;i++) w[i]+=w[i-1];
	for(int len=n;len>0;len--)
	{
		for(int i=1;i+len-1<=n;i++)
		{
			int j=i+len-1;
			cnt[i][j]+=cnt[i-1][j]+cnt[i][j+1]-cnt[i-1][j+1];
		}
	}
	for(int len=2;len<=n;len++)
	{
		for(int i=1;i+len-1<=n;i++)
		{
			int j=i+len-1;
			dp[i][j]=1e18;
			for(int k=i;k<j;k++)
			{
				dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[k]-cnt[i][j]);
			}
		//	printf("%d %d %d\n",i,j,dp[i][j]);
		}
	}
	printf("%lld",q+dp[1][n]);
	return 0;
}

T4 公路

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

CSP-J2023 T2 的升级版,可以分为以下情况

首先,如果加完后可以直接到终点,且前面没有费用更少的,则直接加够到终点的量即可

如果前面有一个费用更少的且加一次油就能到,就直接加够到那个位置的量

如果无法到达,就将油加满,然后到它可到达的油价最小处加油,因为此处的油价一定比这里贵

重复此过程即可

可以先预处理出前面第一个费用更少的点和它可到达的油价最小处,然后直接计算

代码:

#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
int n,nxt1[100005],nxt2[100005];
ll a[100005],v[100005],c,ans,sum;
deque<int> q;
int main()
{
	scanf("%d%lld",&n,&c);
	for(int i=2;i<=n+1;i++)
	{
		scanf("%lld",&v[i]);
		v[i]+=v[i-1];
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	q.push_back(n);
	for(int i=n-1;i>0;i--)
	{
		while(!q.empty()&&v[q.front()]-v[i]>c) q.pop_front();
		nxt1[i]=q.front();
		while(!q.empty()&&a[q.back()]>=a[i]) q.pop_back();
		q.push_back(i);
	}
	for(int i=n-1;i>0;i--)
	{
		if(v[n+1]-v[i]<=c&&a[i]<a[nxt1[i]])
		nxt1[i]=n+1;
	}
	nxt1[n]=n+1;
	while(!q.empty()) q.pop_back();
	q.push_back(1);
	for(int i=2;i<=n+1;i++)
	{
		while(!q.empty()&&a[q.back()]>=a[i])
		{
			nxt2[q.back()]=i;
			q.pop_back();
		}
		q.push_back(i);
	}
	int now=1;
	while(now!=n+1)
	{
		if(v[nxt2[now]]-v[now]<=c)
		{
			ll dis=v[nxt2[now]]-v[now];
			ans+=(dis-sum)*a[now];
			sum=0,now=nxt2[now];
		}
		else
		{
			ans+=(c-sum)*a[now];
			sum=c-v[nxt1[now]]+v[now];
			now=nxt1[now];
		}
	//	printf("%d %d\n",ans,now);
	}
	printf("%lld",ans);
	return 0;
}
posted @   wangsiqi2010916  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示