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
因为如果龙按照题面描述随便吃,就会把区间一分为二,转移的时候就无法满足小明只在两边拿的限制了
所以可以让龙已在两边吃,但是它可以选择不吃,将这些次数攒起来,到后面一起吃
所以此时的龙就分为两种情况
-
每次可以从两边拿其中一个,或者不吃,并把决策权转给小明
-
每一次不拿都可以累积一张抢吃券,使用一张抢吃券能够让其多拿一次
所以设\(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;
}