2025 暑假集训 Day7
2025.8.11
模拟赛,\(25+0+25+10=60\) rk18/42 抽象分数抽象排名。一场比赛三个搜索一个网络流也要搜索。鉴定为写 DFS 写的。
A. 下棋
有n个白色棋子,m个黑色棋子,现在需要把他们排成一排,要求对于任意一段棋子,其中的白色棋子和黑色棋子的差不能超过k,求棋子排列方案数对1000000007(1e9+7)的结果。n,m<=150,k<=20。
注:(黑-黑-白-白) 与 (白-白-黑-黑) 视为不同的方案。
【样例1输入】
2 2 1
【样例1输出】
2
考虑 DFS,要考虑四个状态:放的黑、白棋子个数 \(i,j\) 以及已经放的棋子中黑比白、白比黑棋子多多少个 \(a,b\)。显然一次状态有两种决策:要么放黑棋、要么放白棋。如果放黑棋那么就可以从状态 \((i,j,a,b)\) 转移到 \((i+1,j,a+1,b-1)\),放白棋就可以从状态 \((i,j,a,b)\) 转移到 \((i,j+1,a-1,b+1)\)。最后套个记忆化搜索就可以了。时间复杂度 \(O(nmk^2)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
constexpr int N=157;
constexpr int K=25;
constexpr int mod=1e9+7;
int dp[N][N][K][K];
int n,m,k,ans;
int dfs(int i,int j,int a,int b)
{
if(dp[i][j][a][b]!=-1) return dp[i][j][a][b];
if(i==n&&j==m) return 1;
int tmp=0;
if(i<n&&a<k) tmp=(tmp+dfs(i+1,j,a+1,max(b-1,0)))%mod; //放白棋
if(j<m&&b<k) tmp=(tmp+dfs(i,j+1,max(a-1,0),b+1))%mod; //放黑棋
return dp[i][j][a][b]=tmp;
}
int main()
{
// freopen("data.in","r",stdin);
// freopen("data.out","w",stdout);
cin>>n>>m>>k;
for(int i=0;i<=n;i++) for(int j=0;j<=m;j++) for(int a=0;a<=k;a++) for(int b=0;b<=k;b++) dp[i][j][a][b]=-1;
cout<<dfs(0,0,0,0);
return 0;
}
/*
dp[i][j][a][b]表示现在放完i个白棋,j个黑棋,
且在已经放过的棋子中,白棋比黑棋多a个,黑棋比白棋多b个
的方案数
*/
B. 出租车
出租车有k个停靠点,这k个停靠点依次排成一条直线,从左到右编号为1到k。出租车一次最多带4个人。共有n个人正在等待乘车,每个人都想从某个停靠点,去往另一个停靠点,即每个人的出发点和目的点不尽相同。这n个人到达各自上车的停靠点的时间均不相同,我们按到达时间的前后依次给这n个人编号为1到n,即先到的编号小。出租车公司有一个规定,先到的人必须比后到的人先上车,当然下车顺序则任意。
出租车每次可以移动到相邻编号的停靠点,需要耗费一个单位的时间,每个人上车或者下车都需要耗费一个单位时间,出租车一开始为空,且位于1号停靠点,出租车最后停靠点任意,那么将则n个人送到目的地至少需要多少时间呢?(注:题目保证出租车到达时需要上车的人已到达出发点)n<=2000,k<=10。
【样例1输入】
2 4
2 3
3 2
【样例1输出】
7
还是 DFS。因为要按照编号上车,所以不妨记录状态 \((i,pos,a,b,c,d)\),其中 \(i\) 为接到第几个人,\(pos\) 为出租车现在的坐标,\(a,b,c,d\) 为四个人的目的地。但是这样 \(O(nk^5)\) 显然会爆炸。
考虑优化。发现第四个人 \(d\) 可以不用记录,因为装满了四个人之后下一步一定要先把一个人丢下车才能装当前的人,所以状态可以变成 \((i,pos,a,b,c)\) 了。接着就直接按照题意写一个记忆化 DFS 就好了。时间复杂度 \(O(nk^4)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
constexpr int N=2001;
constexpr int K=11;
constexpr int inf=0x3f3f3f3f;
int n,k,u[N],v[N];
int dp[N][K][K][K][K];
int dfs(int i,int pos,int a,int b,int c) //当前看到第i个人,位置在pos,车上四个人的目的地是(a,b,c,省略)
{
if(dp[i][pos][a][b][c]!=inf) return dp[i][pos][a][b][c];
int res=inf;
//如果出租车不为空的话就丢几个人下去
if(a) res=min(res,dfs(i,a,0,b,c)+abs(pos-a)+1);
if(b) res=min(res,dfs(i,b,a,0,c)+abs(pos-b)+1);
if(c) res=min(res,dfs(i,c,a,b,0)+abs(pos-c)+1);
//乘客送完了
if(i>n)
{
if(a==0&&b==0&&c==0) return dp[i][pos][a][b][c]=0; //车上没有乘客了
else return dp[i][pos][a][b][c]=res;
}
if(a&&b&&c) //出租车满载了,就先丢掉一个人然后换一个新的人上来
{
// 先过去把人抓上车 然后丢掉
res=min(res,abs(pos-u[i])+1 +dfs(i+1,v[i],a,b,c)+abs(u[i]-v[i])+1);
res=min(res,abs(pos-u[i])+1 +dfs(i+1,a,v[i],b,c)+abs(u[i]-a)+1);
res=min(res,abs(pos-u[i])+1 +dfs(i+1,b,a,v[i],c)+abs(u[i]-b)+1);
res=min(res,abs(pos-u[i])+1 +dfs(i+1,c,a,b,v[i])+abs(u[i]-c)+1);
}
else //出租车还有空位那就把当前人抓上来
{
if(!a) res=min(res,abs(pos-u[i])+dfs(i+1,u[i],v[i],b,c)+1);
else if(!b) res=min(res,abs(pos-u[i])+dfs(i+1,u[i],a,v[i],c)+1);
else if(!c) res=min(res,abs(pos-u[i])+dfs(i+1,u[i],a,b,v[i])+1);
}
return dp[i][pos][a][b][c]=res;
}
int main()
{
// freopen("data.in","r",stdin);
// freopen("data.out","w",stdout);
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>u[i]>>v[i];
memset(dp,0x3f,sizeof dp);
cout<<dfs(1,1,0,0,0);
return 0;
}
C. 堆石子
毛毛有n堆石子,其中第i堆石子有Si个石子,每个石子大小为1。
现在牛牛有一些袋子,他每次会选择其中一堆,用袋子装走其中m个石子,如此往复,直到所有堆的石子数少于m。
(当然牛牛不会选择剩余数量小于m的堆)
为了使牛牛装完之后剩余最大堆的石子数尽可能多,毛毛决定事先将某些石子堆进行合并,即选择一些堆,将它们合成一堆,如此往复形成新的若干堆。请你帮助毛毛制定合理的策略,并计算剩余最大堆的石子数。n<=35,m,Si<=10^9
【样例1输入】
4 4
5 2 4 1
【样例1输出】
3
赛时写了两档部分分,一个是 \(n \le 20\) 的 \(O(2^n)\) 30pts 爆搜,一个是 \(m \le 10^6\) 的 \(O(nm)\) 20pts DP(dp[i][j]表示前i个石子是否存在一个合并之后的堆的大小%m=j的方案),但因为 XXXOJ 的抽象评测机喜提 TLE 25pts 的高分。
正解:所谓的把一大堆石子合并成一些堆,其实就是在这 \(n\) 堆石子里面选出若干堆作为最终答案,即在 \(S=\{s_1,s_2,\cdots,s_n\}\) 中选出一个子集 \(S^\prime\) 使得 \((\sum S^\prime_i) \bmod m\) 最大。
如果爆搜的话是 \(O(2^n)\) 的复杂度显然会爆炸,所以可以考虑一下 meet-in-middle 算法,搜出前一半(\(1 \sim n/2\))和后一半(\(n/2 \sim n\))可能的组合,然后再二分查找找一个最优的答案即可。时间复杂度 \(O(2^{n/2} \log 2^{n/2})\)。
D. 不稳定的导弹系统
王国发生了叛乱,整个国家处处都有叛军,在这危急时刻,国王下令将刚研制的导弹系统投入使用,以消灭叛军。
但导弹发射器分散在全国各地,每个导弹发射器只来得及配备一枚超级导弹。更糟糕的是,导弹发射器的转向器还没装上,以至于只能攻击一个方向上的叛军,一枚超级导弹可以将一个单位区域的所有叛军歼灭。超级导弹将在同一时间发射,超级导弹的飞行路线不能有交叉,否则一旦碰撞后果不堪设想。
现在将王国简化成一个 N × M 的网格图,每一小格代表一个单位区域,单位区域上要么是空地,要么是导弹发射器,要么有一定数量的叛军。现在国王希望能歼灭尽可能多的叛军。
注:保证不会有导弹发射器能炸到另一个导弹发射器。
N,M<=50,单位区域中的叛军不超过1000人。
【样例输入】
4 5
0 0 -2 0 0
-4 0 5 4 0
0 -4 3 0 6
9 0 0 -1 0
【样例输出】
12
不知道为什么赛时写了个输出矩阵最大值的代码可以拿到 10pts……正解是网络流最小割,今天时间不够了先咕咕咕了吧。
还有就是在 NOIP 模拟赛丢了一个 NOI 级别的知识点网络流?

浙公网安备 33010602011771号