20240709比赛总结

T1 超市抢购

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

仔细读懂数据生成器,就能看出来,实际上物品肯定是够用的

因为只能从右向左搬运物品,所以我们只需要对于每一个i,i+1的间隔,考虑有多少个物资需要从右边搬到左边去,把这个贡献累加即可

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=1e7+5;
int a[maxn],b[maxn],n;
int randseed;
unsigned int rnd()
{
  unsigned int r;
  r = randseed = randseed * 1103515245 + 12345;
  return (r << 16) | ((r >> 16) & 0xFFFF);
}
int main()
{
	scanf("%d%d",&n,&randseed);
	int sum=0;
	for (int i=n;i>=1;i--)
	{
		a[i]=rnd()%1000,b[i]=rnd()%1000;
		if (sum+(a[i]-b[i])<0) swap(a[i],b[i]);
		sum+=(a[i]-b[i]);
	}
	ll x=0,ans=0;
	for(int i=1;i<=n;i++)
	{
	//	printf("%d %d %d\n",a[i],b[i],x);
		int tmp=a[i]-b[i];
		ans+=x;
		if(tmp>0) x=max(1ll*0,x-tmp);
		else x-=tmp;
	}
	printf("%lld",ans);
	return 0;
}

T2 核酸检测

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

别乱开long long!考试最后10分钟不要动代码!要注释freopen!!!

\(dp_i\)表示在i及i之前开启的区间被全部覆盖的最小次数,\(cnt_i\)表示它对应的方案数

因为对于每个位置,假设它最早结束的位置能被覆盖,那么显然从它开始的区间都可以被覆盖,此时,只需要记录最小的r即可,这里记为\(minn_i\)

接下来考虑转移,假设上一个结束点为i,如何求下一个结束点的位置?

首先,i+1一定可以转移成功,而可转移的区间范围也缩小至了\(minn_{i+1}\),因为此时只加了一次操作,超过\(minn_{i+1}\)就会至少有1个区间覆盖不到

以此类推,就可以不断更新转移的范围,假设当前到j,则将原范围和\(minn_{j}\)去最小值就是新的范围

如果操作次数相等,方案数直接相加,如果新的次数比它小,就覆盖

如何统计答案?

记录最大的l和最大的r,因为超过最后的r的部分就不会产生贡献,超过最后的l的部分就一定包含了所有区间,所以范围就是maxl到maxr,直接按上述转移方法统计即可

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
int n,minn[1030],dp[1030],cnt[1030],maxr,maxl,mod=1e9+7;
int main()
{
//	freopen("2.in","r",stdin);
	scanf("%d",&n);
	for(int i=0;i<=1024;i++) minn[i]=dp[i]=1025;
	for(int i=1;i<=n;i++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		minn[l]=min(minn[l],r);
		maxr=max(maxr,r),maxl=max(maxl,l);
	}
	cnt[0]=1,dp[0]=0;
	for(int i=0;i<=maxr;i++)
	{
		int mn=minn[i+1];
		for(int j=i+1;j<=mn;j++)
		{
			if(dp[j]>dp[i]+1)
			{
				dp[j]=dp[i]+1;
				cnt[j]=cnt[i];
			}
			else if(dp[j]==dp[i]+1)
			{
				cnt[j]=(cnt[i]+cnt[j])%mod;
			}
			mn=min(mn,minn[j]);
		}
	//	printf("%d %d\n",dp[i],cnt[i]);
	}
	int ans1=2000,ans2=0;
	for(int i=maxl;i<=maxr;i++)
	{
		if(ans1>dp[i])
		{
			ans1=dp[i];
			ans2=cnt[i];
		}
		else if(ans1==dp[i])
		{
			ans2=(ans2+cnt[i])%mod;
		}
	}
	printf("%d\n%d",ans1,ans2);
	return 0;
}

T3 七龙珠

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

讲一种另类的方法

首先就是进行01背包,得到每个龙珠可以组成的数并记录

为满足在成系数后最大的在前面,所以若系数为正,就从小到大,否则从大到小

显然,这个时候每个龙珠的情况数相乘就是总情况数,当小于k时,直接输出Stop dreaming xm!

接下来考虑如何求第k大的方案,想到 20240706 T2 最长路径 ,所以可以枚举它和最大的差了多少,求它的下限

这时,统计大于等于下限的数量,直到大于等于k,但是因为范围很大,所以考虑二分差值

现在的时间复杂度的瓶颈在背包,可以采用bitset,可以去看https://www.luogu.com.cn/article/osrhh40p

时间复杂度\(O(能过)\)

代码:

#include<cstdio>
#include<bitset>
#include<algorithm>
#define ll long long
using namespace std;
int m,k,n,a[10004];
ll v[10],num[10][100005],cnt[10],sum,cnt1;
bitset<100005> vis;
void solve(int x)
{
	int tmp=0;
	vis[0]=1;
	for(int i=1;i<=n;i++)
	{
		vis|=(vis<<a[i]);
	}
	if(v[x]>0)
	{
		for(int i=sum;i>=0;i--)
		{
			if(vis[i])
			num[x][++cnt[x]]=i;
			vis[i]=0;
		}
	}
	else
	{
		for(int i=0;i<=sum;i++)
		{
			if(vis[i])
			num[x][++cnt[x]]=i;
			vis[i]=0;
		}
	}
}
bool dfs(int x,ll y)
{
	if(x>7)
	{
		if(y>=0) cnt1++;
		if(cnt1>=k) return 1;
		return 0;
	}
	for(int i=1;i<=cnt[x];i++)
	{
		ll tmp=(num[x][1]-num[x][i])*v[x];
		if(tmp<=y)
		{
			bool fl=dfs(x+1,y-tmp);
			if(fl) return 1;
		}
		else break;
	}
	return 0;
}
int main()
{
	//freopen("20.in","r",stdin);
	scanf("%d%d",&m,&k);
	for(int i=1;i<=7;i++)
	{
		scanf("%lld",&v[i]);
	}
	for(int i=1;i<=7;i++)
	{
		scanf("%d",&n);
		sum=0;
		for(int j=1;j<=n;j++)
		{
			scanf("%d",&a[j]);
			sum=min(1ll*m,sum+a[j]);
		}
		solve(i);
	}
	sum=1;
	for(int i=1;i<=7;i++)
	{
	//	printf("%d ",cnt[i]);
		sum*=cnt[i];
		if(sum>k) break;
	}
	if(sum<k)
	{
		printf("Stop dreaming xm!");
		return 0;
	}
	sum=0;
	for(int i=1;i<=7;i++)
	{
		sum=sum+num[i][1]*v[i];
	}
	if(k==1)
	{
		printf("%lld",sum);
		return 0;
	}
	ll l=0,r=sum;
	while(l<r)
	{
		//printf("%d %d\n",l,r);
		ll mid=(l+r)>>1;
		cnt1=0;
		if(dfs(1,mid))
		{
			r=mid;
		}
		else l=mid+1;
	}
	printf("%lld",sum-l);
	return 0;
}

T4 龙珠游戏

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

很抽象的一道题

这道题的时间复杂度显然是\(O(n^3)\),考虑区间dp

因为如果龙按照题面描述随便吃,就会把区间一分为二,转移的时候就无法满足小明只在两边拿的限制了

所以可以让龙已在两边吃,但是它可以选择不吃,将这些次数攒起来,到后面一起吃

所以此时的龙就分为两种情况

  1. 每次可以从两边拿其中一个,或者不吃,并把决策权转给小明

  2. 每一次不拿都可以累积一张抢吃券,使用一张抢吃券能够让其多拿一次

所以设\(tp/dp_{i,j,k}\)表示现在是神龙/小明决策,在区间i,j,神龙有k张抢吃券的情况下小明的最大收益

所以这个时候记忆化搜索即可

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll dp[501][501][255],tp[501][501][255],a[505];
int n;
ll TP(int l,int r,int k);
ll DP(int l,int r,int k);
ll TP(int l,int r,int k)
{
	if(l>r) return 0;
	if(k==r-l+1) return 0;
	int tmp=tp[l][r][k];
	if(tmp!=0) return tmp-1;
	ll ans=DP(l,r,k);
	if(k>=1)
	{
		ans=min(ans,min(TP(l+1,r,k-1),TP(l,r-1,k-1)));
	}
	tp[l][r][k]=ans+1;
	return ans;
}
ll DP(int l,int r,int k)
{
	if(l>r) return 0;
	ll tmp=dp[l][r][k];
	if(tmp!=0) return tmp-1;
	ll ans=max(TP(l+1,r,k+1)+a[l],TP(l,r-1,k+1)+a[r]);
	dp[l][r][k]=ans+1;
	return ans;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	DP(1,n,0);
	printf("%lld",dp[1][n][0]-1);
	return 0;
}
posted @ 2024-07-09 19:47  wangsiqi2010916  阅读(52)  评论(0编辑  收藏  举报