2024暑假集训测试3
前言
T4 赛时想了 \(3\) 个小时打出来了,但是计算内存的时候开了两个数组忘记 \(\times 2\) 了,当时为了防止炸掉多开了一点点,就爆零了,小开一点点就过了,非常难受。
这次比赛好像一个部分分没打出来,数据范围给的也不像能有部分分的。
T1 超市抢购
签到题,注意给的随机化种子要基于 unsigned int
,不要 #define int long long
,但答案需要开 long long
。
T2 核酸检测
定义 \(f_i\) 为在 \(i\) 处喊一声,且 \(l\le i\) 的都被喊道最少需要喊多少次,\(g_i\) 为对应的方案数。
有 \(f_i=\min\limits_{0\le j\le i-1}\{f_j+1\}\) ,\(j\) 能转移到 \(i\) 当且仅当不存在任何一组 \(j+1\le l\le r\le i-1\)。
对于每一个 \(l\),处理出其 \(to_l\) 表示左端点为 \(l\) 的所有人中右端点最靠左的 \(r\),故有当 \(\min\limits_{k=i+1}^{j-1}to_k\le j\) 时,方程可以转移。
故在转移的过程中,对于一个 \(f_i\),枚举出其所有能转移到的 \(j\),显然当 \(j\) 无法被转移到的时候 \(j+1\) 一定转移不到。
至于 \(g_i\),在转移 \(f_i\) 到 \(f_j\) 的过程同时,若 \(f_j+1>f_i\),则领 \(g_j\) 集成 \(f_i\),若 \(f_j=f_i+1\),说明有不同的方案,则 \(g_j+=g_i\)。
最后统计答案,对于最靠右的 \(l\),定义为 \(maxl\),同理定义 \(maxr\),知道这段区间内必须是要喊一次的,那么对于 \(f_{maxl}\sim f_{maxr}\) 都是合法的,直接取其中的最小值为答案;方案书为 \(\sum\limits_{i=maxl}^{maxr}[f_i=ans]\times g_i\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1030,P=1e9+7;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,to[N],maxl,maxr,f[N],g[N],ans,sum;
signed main()
{
read(n);
memset(to,0x3f,sizeof(to));
for(int i=1,l,r;i<=n;i++)
read(l),read(r),
to[l]=min(to[l],r),
maxl=max(maxl,l),
maxr=max(maxr,r);
memset(f,0x3f,sizeof(f));
f[0]=0,g[0]=1;
for(int i=0;i<=maxr;i++)
{
int r=to[i+1];
for(int j=i+1;j<=r;j++)
{
if(f[i]+1<f[j])
f[j]=f[i]+1,
g[j]=g[i];
else if(f[i]+1==f[j])
(g[j]+=g[i])%=P;
r=min(r,to[j]);
}
}
ans=0x3f3f3f3f;
for(int i=maxl;i<=maxr;i++)
{
if(f[i]<ans)
{
ans=f[i];
sum=g[i];
}
else if(f[i]==ans)
(sum+=g[i])%=P;
}
write(ans),puts(""),write(sum);
}
T3 七龙珠
对于每颗龙珠都可以是多少能量可以用背包 DP 求出,考虑只需处理能量 \(x\) 是否合法,所以用 bool
即可,考虑这样复杂度为 \(O(\sum\limits_{i=1}^7x_i\times m)\) 为 \(7e9\) 级别,肯定过不去,考虑使用 bitset
优化,使其复杂度为 \(O(\dfrac{\sum\limits_{i=1}^7x_i\times m}{64})\),鉴于 bitset
为一个 \(01\) 串,联系状压里的知识,对于每个 \(b_i\),\(f|=(f<<b_i)\) 即可,更加方便。
之后 bfs 求出第 \(k\) 大方案即可。
全局最大一定为 \((1,1,1,1,1,1,1)\),\(1\) 指这颗龙珠的能量的排名(此处注意若 \(a_i<0\) 则能量从小到大排名),将其放到大根堆里,每次去除最大的一个,将这 \(7\) 位中枚举每一位使该位 \(+1\),其余不变,在把他让扔到堆里,第 \(k\) 次弹出的即使答案。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=5e5+10,P=1e9+7;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int m,k,a[10],b[N],len[10];
struct aa
{
int val,p[10];
friend bool operator < (const aa &x,const aa &y)
{
if(x.val!=y.val) return x.val<y.val;
for(int i=1;i<=7;i++)
if(x.p[i]!=y.p[i])
return x.p[i]<y.p[i];
return 0;
}
};
bitset<N>f;
map<aa,bool>v;
vector<int>e[10],ans;
priority_queue<aa>q;
void bfs()
{
int sum=0;
while(!q.empty()&&sum<k)
{
aa x=q.top();
q.pop();
ans.push_back(x.val);
sum++;
for(int i=1;i<=7;i++)
{
aa y=x;
if(x.p[i]<len[i]-1)
{
y.p[i]++;
y.val=x.val-e[i][x.p[i]]*a[i]+e[i][y.p[i]]*a[i];
if(v[y]==1) continue;
q.push(y);
v[y]=1;
}
}
}
if(sum==k) write(ans[k-1]);
else puts("Stop dreaming xm!");
}
signed main()
{
read(m),read(k);
for(int i=1;i<=7;i++) read(a[i]);
for(int i=1,c;i<=7;i++)
{
read(c);
for(int j=1;j<=c;j++) read(b[j]);
f[0]=1;
for(int j=1;j<=c;j++)
f|=(f<<b[j]);
if(a[i]>0)
{
for(int j=m;j>=0;j--)
if(f[j])
e[i].push_back(j);
}
else
{
for(int j=0;j<=m;j++)
if(f[j])
e[i].push_back(j);
}
len[i]=e[i].size();
for(int j=0;j<=m;j++) f[j]=0;
}
aa x;
x.val=0;
for(int i=1;i<=7;i++) x.p[i]=0,x.val+=e[i][0]*a[i];
v[x]=1,q.push(x);
bfs();
}
T4 龙珠游戏
不难看出要跑区间 DP,发现开了 \(3s\) 的时限和 \(1G\) 的内存,考虑 \(n^3\) 做法,思考第三维处理什么。
对于区间 \([l,r]\),人
只能选 \(a_l\) 或 \(a_r\),但 龙
可以随便选,这就很不好处理,人
每次一定选择的为最优的,从而思考 龙
行动的本质就是阻止 人
的最优行动,不妨记录一个 \(k\) 表示 龙
可以行动的次数,对于 人
的每次操作,若此时 \(k>0\),说明 龙
可以阻止本次操作的进行,同时消耗一次行动次数,反之若 龙
没有阻止 人
的操作,则 人
顺利拿到了 \(a_l,a_r\) 中的一个,同时 \(k+1\),即 龙
获得一次行动次数。
已知结束状态 \(l=1,r=n,k=0\),初始状态不太好搞,递推不太好想,不妨记忆化搜索倒推进行递归处理,定义 \(f_{l,r,k}\) 表示 人
在进行操作,当前区间为 \([l,r]\),龙
有 \(k\) 次行动次数,人
最多能拿到多少;\(g_{l,r,k}\) 则表示 龙
在进行操作,能使 人
最少拿到多少。鉴于 人
操作后一定轮到 龙
,而在 \(k>0\) 的前提下 龙
有不操作和操作两种选择;同时根据贪心思想,当 \(r-l+1=k\) 时,龙
一次也不会再让 人
拿到,故此时 \(g_{l,r,k}=0\)。转移方程:
两个 \(dfs\) 轮流递归即可,复杂度 \(O(n^3)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=510;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,a[N],f[N][N][226],g[N][N][226];
int dfs(int l,int r,int k);
int dfs2(int l,int r,int k);
int dfs(int l,int r,int k) // 龙先不选,并获得一次优先选权。
{
if(l>r) return 0;
if(r-l+1==k) return 0;
if(f[l][r][k]!=-1) return f[l][r][k];
return f[l][r][k]=max(dfs2(l+1,r,k+1)+a[l],dfs2(l,r-1,k+1)+a[r]);
}
int dfs2(int l,int r,int k) // 龙要使用优先选择权。
{
if(l>r) return 0;
if(g[l][r][k]!=-1) return g[l][r][k];
int ans=dfs(l,r,k); // 龙不使用优先选择权。
if(k>=1) ans=min({ans,dfs2(l+1,r,k-1),dfs2(l,r-1,k-1)});
return g[l][r][k]=ans;
}
signed main()
{
read(n);
for(int i=1;i<=n;i++) read(a[i]);
memset(f,-1,sizeof(f));
memset(g,-1,sizeof(g));
dfs(1,n,0);
write(f[1][n][0]);
}