20260725 小测总结
省流:燃尽了,切完 t1 ~ t5 之后没什么时间了 t6 摆烂。打的还不错感觉没什么大问题。唯一要说的就是代码实现能力吧在调试上面花太多时间了。
T1 美丽整数
题意概述
若一个正整数 $ x $ 是一个九位数,并且它的十进制表示 $ S_1 S_2 \ldots S_9 $ 满足以下全部条件,则称 $ x $ 为一个美丽整数:
- $ S_1 > 0 $;
- $ S_1 = S_2 $;
- $ S_5 = S_6 $;
- $ S_7 = S_9 $。
求第 $ n $ 个美丽整数。
solution
暴力。枚举 $ S_1 $ , $ S_3 $ , $ S_4 $ , $ S_5 $ , $ S_7 $ , $ S_8 $ 即可。
什么你问第 $ n $ 个怎么处理?加一个 $ cnt $ 表示枚举到了第几个美丽整数即可。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,cnt;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=9;i++){
for(int j=0;j<=9;j++){
for(int k=0;k<=9;k++){
for(int q=0;q<=9;q++){
for(int p=0;p<=9;p++){
for(int l=0;l<=9;l++){
cnt++;
if(cnt==n){
cout<<i<<i<<j<<k<<q<<q<<p<<l<<p;
exit(0);
}
}
}
}
}
}
}
return 0;
}
T2 取牌游戏
题意概述
两个人,每个人有 $ n $ 张牌,上面有 $ 1 $ ~ $ n $ 的编号。两个人轮流出牌放入牌堆,最后谁出了一张牌,使得牌堆里牌的编号总和是 $ m $ 的倍数那么这个人就输。问是谁赢。
保证 $ n < m $ 。
solution
注意到 $ n < m $ 。这也就意味着对于牌堆的任意一个 $ S $ ,只有一张/零张牌可以让其变成 $ m $ 的倍数。
所以只要一个人手里还有两张牌,那么ta就一定可以打出另外一张牌以保证其不会输。那么牌局的输赢仅仅决定为两人都只剩下一张牌的时候。
这个时候轮到先手出牌。因为如果后手想赢就必须要让其打出牌之后牌堆里牌的编号总和为 $ m $ 的倍数。我们令牌堆的总和为 $ S $ ,这个时候后手必须留下一张编号为 $ id $ 的牌,使得 $ ( S - id ) \bmod m = 0 $ 。这样牌堆里牌的总和就是 $ m $ 的倍数。然后先手就输了。
相对应的,如果后手需要的编号为 $ id $ 的牌不存在(比如 $ id $ 为 $ 0 $ 或者 $ id > n $ ),这个时候后手就必输了。
AC code
补充一下,原题面里先手为 Alice ,后手为 Bob 。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,m;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--){
cin>>n>>m;
int k=n*(n+1)%m;//需要的id,n*(n+1)对应S,直接高斯求和O(1)搞定
if(k<=n&&k>0)cout<<"Bob\n";//id合法
else cout<<"Alice\n";//id不合法
}
return 0;
}
T3 +1 and -1
题意概述
给定一个整数序列,可以执行一个操作,每次选择前面一个数后面一个数并令前面 $ +1 $ 后面 $ -1 $ 。
问能否可以让这个整数序列变成一个 广义单调递增(即单调不降) 的序列。
solution
做的时候感觉挺恐怖的。
单调不降对我而言有亿点少见。考虑简化问题。
曾经有一位高人告诉我这种操作题要从操作入手。注意到 $ +1 -1 $ 并不会影响总和,于是我们考虑往平均分配上面思考。
考虑构造一个 $ a,a,a, \ldots ,a+1,a+1,a+1 $ 的序列。一个不降的序列一定可以通过题目里给出的操作变成这样一个序列。
所以我们考虑将给出的序列尝试变成这样一个序列,如果可以的话那么这个序列一定可以变成一个不降的序列,否则不行。
那么如何判断能不能变成这样的一个序列呢?对于一对给出序列的前缀和与目标序列的前缀和,如果给出序列的前缀和小于目标序列的前缀和,那么无所谓我可以前面执行 $ +1 $ 操作弥补。但是如果给出序列的前缀和大于目标序列的前缀和那么这显然是没救的。
于是做完了。
AC code
代码的判断部分似乎不是那么好理解。如果真的没看懂可以多看几遍。毕竟和我讲的似乎不是很一样。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=200005;
int t,n,a[N],b[N],sum;
bool flag;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--){
cin>>n;
sum=0;
flag=0;
for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
for(int i=1;i<=n;i++){
b[i]=sum/n;
if(i>n-sum%n)b[i]++;
}
int res=0;
for(int i=1;i<=n;i++){
res+=b[i]-a[i];
if(res<0){
flag=1;
break;
}
}
if(flag)cout<<"No\n";
else cout<<"Yes\n";
}
return 0;
}
T4 跳题
题意概述
给出 $ n $ 道题,每道题有其对应的分数。系统会先推送第 $ 1 $ 题。当选手拿到一道题 $ i $ ,可以选择跳过这道题并接受系统推送的第 $ b_i $ 道题,或者切掉这道题,获得这道题的得分 $ a_i $ 并接受系统推送的一道题 $ j $ ,使得 $ j < i $ 并且 $ j $ 最大并且这道题没有被切掉。如果没有题可以推送那么比赛结束。求选手能获得的最大总得分。
solution
感觉挺一眼的啊。
建图跑最短路。具体的,对于每道题 $ i $ :
- 连一条到 $ i-1 $ 的边,权值为 $ 0 $ ;
- 连一条道 $ b_i $ 的边,权值为 $ a_i $ 。
这个时候从 $ 1 $ 到 $ a_i $ 的最短路表示的就是切掉 $ 1 $ 到 $ a_i $ 的最小花费。因为到达 $ a_i $ 之后可以一路往回走收割路上的所有题目。
于是我们从 $ 1 $ 出发跑一遍 $ Dijkstra $ ,求出到每个点的最小花费,然后计算 $ 1 $ 到 $ a_i $ 的分数,减去它的最小花费,然后取个最大值即可。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=400005;
int t;
struct EDGE{
int to;
int wei;
bool operator>(const EDGE& other)const{
return wei>other.wei;
}
};
int n,dis[N],a[N],b[N];
bool vis[N];
vector<vector<EDGE> >g;
priority_queue<EDGE,vector<EDGE>,greater<EDGE> >q;
void dijkstra(int st){
for(int i=1;i<=n;i++)dis[i]=0x3f3f3f3f3f3f3f3f,vis[i]=0;
while(!q.empty())q.pop();
q.push({st,0});
dis[st]=0;
while(!q.empty()){
int u=q.top().to;
q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=0;i<g[u].size();i++){
int v=g[u][i].to,w=g[u][i].wei;
if(dis[u]+w<dis[v]){
dis[v]=dis[u]+w;
q.push({v,dis[v]});
}
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--){
cin>>n;
g.clear();
g.resize(n+1);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
for(int i=1;i<=n;i++){
g[i].push_back({i-1,0});
g[i].push_back({b[i],a[i]});
}
dijkstra(1);
int sum=0,zd=0;
for(int i=1;i<=n;i++){
sum+=a[i];
zd=max(zd,sum-dis[i]);
}
cout<<zd<<'\n';
}
return 0;
}
T5 不必要的卡片
题意概述
给你一些卡片上面有数字,定义好集合为数字之和不小于 $ K $ 的卡片集合,不必要卡片则是在任意包含它的好集合中,去掉后仍为好集合的卡片。
求有多少个不必要的卡片。
solution
开心。为数不多场切的青。
一时间感觉无从下手。看见前面的 T2 于是也鼓捣出一个 $ S $ 并展开思考。
注意到如果一个卡片 $ a_i $ 是必要的,一定有一个包含 $ a_i $ 的好集合 $ S $ 使得 $ S-a_i < K \le S $ 。
把玩一下这个不等式,得 $ K \le S < K+a_i $ 。
那么问题转化为对于每个求一个 $ a_i $ ,求是否有一个 $ S $ ,使得 $ K \le S < K+a_i $ 。
这样就好办了,这是个背包的板子。
但问题是时间复杂度会炸。每个 $ a_i $ 都要做一个背包,并检查 $ K $ 到 $ K+a_i $ 。虽然对于每个大于 $ K $ 的 $ a_i $ 我们可以做个特判直接去掉,但是这样我们检查的时间复杂度还是 $ O(N^2 + K) $ 的,再加上外面 $ O(N) $ 的枚举会坠机。所以我们还要考虑优化。
注意到背包的转移可以用 $ bitset $ 优化,那么我们就获得了 $ O ( n \cdot \frac{5005}{64} + k ) $ 的检查的时间复杂度。但是还是不够。
还能怎么优化呢?事情似乎陷入了僵局。
手玩一下样例,发现所有不必要的卡片排序之后是连续的。也就是说它们都会聚集在前面。这是个很重要的观察。
详细看看。发现对于一个不必要的卡片,存在一个最大的 $ S $ ,使得 $ K \le S < S+a_i $ 。而要是按顺序替换一个更大的 $ a_i $ , $ S $ 会变小,直到小于 $ K $ 。而这一段的 $ a_i $ 都是不必要的。
二分不必要和必要的分界线即可。然后做完了。
时间复杂度 $ O( n \log n + \log n \cdot ( n \cdot \frac{5005}{64} + k ) ) $ 。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5005;
int n,k,a[N];
inline bool check(int x){
if(a[x]>=k)return true;
bitset<N>dp;
dp[0]=1;
for(int i=1;i<=n;i++){
if(i==x||a[i]>=k)continue;
dp|=(dp<<a[i]);
}
for(int s=k-a[x];s<k;s++)if(dp[s])return true;
return false;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+1+n);
int l=0,r=n+1;
while(l+1<r){
int mid=(l+r)/2;
if(check(mid))r=mid;
else l=mid;
}
cout<<l;
return 0;
}
T6 旋转回文数列
考试的时候拼尽全力切掉前五题燃尽了。随便写了点特殊性质获得 13pts 之后跑路了。正解先咕咕。

浙公网安备 33010602011771号