2023年8月24日练习赛复盘
复盘
题目列表
题目来自《2023牛客暑期多校训练营》
题目链接是内网SPOJ,未必能打开,建议下载题目文件(.7z)
-
T1 第二场:Problem D. The Game of Eating
知识点:逆向思维,贪心 -
T2 第四场:Problem J. Qu'est-ce Que C'est?
知识点:DP,计数 -
T3 第六场:Problem I. Journey
知识点:二分图染色 -
T4 第五场:Problem F. NoCruelty
知识点:Trie树维护序列
時間情況
概況
總時長是4.5h,從早上8:00到中午12:30。說是這麼多,其實最後半個小時都在划水。
時間線
大概在9:00左右把T1的正解寫完了。
之後就在和T2鬥智鬥勇,雖然很容易看出來是DP題,但是搞不出來。先寫了個矩陣快速冪,調了蠻久的,結果發現時間複雜度和正確性都假了,遂跳過。猜測這時候是接近10:00。
T3大概想了10min左右,有一點想法,寫了輸入輸出和BFS。之後果斷放棄了。
T4的話,感覺很可以寫暴力。於是寫了個的,並且花了一點時間進行剪枝(然而是無效剪枝,最終TLE0)。寫完估計是11:00左右。
對T4無能為力,於是又回去寫T2。重新搞了方程式,過樣例。
對所有題無能為力了,去把T3的“不可以,總司令”寫了。
然後到了12:00,其實已經打算開溜了,但是其他人都還在寫(尤其是hyb,說是正解快寫完),所以就原地划水(給自己OC寫點小產品),但是很怕被看到所以說效率極低。
部分分
這次根本沒有部分分啊
主要是由於題面沒有給出部分分數據範圍,所以就只能自己胡。
- T1:是胡亂搞的正解,沒有寫部分分,但是最終掛了一個點(?)
- T2:是的DP非正解,但是胡到70pts
- T3:“不可以,總司令”胡到2pts(據說有100個測試點)
- T4:是的暴力非正解,爆零(貌似無人生還)
二編:T1掛的點是評測機問題,需要快讀快寫。
题解
T1 The Game of Eating
译作:《聚餐》
一个反向贪心。
没有证明,但可以毛估估(雾)。
所有人都非常聪明,但是前面的人拥有更多选择权。他知道自己后面的人一定会“替”他选某些菜,所以他把最喜欢的菜留给后面的人去选。他自己只选自己较喜欢但后面的人不会选的菜,这样可以点到更多自己喜欢的菜。
这个就叫借刀杀人借人点菜。
然后这个过程考虑反过来想。
最后一个人没办法把借人点菜,所以他一定会选自己最喜欢的菜。
再捋一遍。
假设最后一个人选的菜是A。或许前面有些人也喜欢A,但是他们预料到即使自己不点,最后一个人也会点A。于是前面的人都会把A预留下来,专门让最后一个人来点。
所以最后一个人一定会点A。
那么倒数第二个人,同理,他会点除了A以外自己最喜欢的菜。
于是可以从最后一个人开始贪心,每次点这个人还没被点的最喜欢的菜,直到让第一个人点第道菜。
换一个角度想。
前面的人是不是理论上有更多选择权?他们可以选菜的次数和选择范围都比后面的人多。
所以我们可以假定“前面的人拥有更多的权利”。
于是让后面的人给他们跑腿,选他们喜欢的菜。
再换一个角度想。
选了最喜欢的菜,但却给后面的人“打白工”,不是雪妈亏?
反之,不亏就是赚,可以直接贪。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2005;
int n, m, k, a[N][N];
bool ans[N];
int maxa(int x){
int mx = 0, ret = 0;
for(int i = 1; i <= m; i++)
if(a[x][i] > mx && !ans[i])
mx = a[x][i], ret = i;
return ret;
}
int main(){
int t; scanf("%d", &t);
while(t--){
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= m; i++) ans[i] = 0;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
for(int i = k, j = k%n; i >= 1; i--){
if(!j) j = n;
ans[maxa(j)] = 1;
j--;
}
for(int i = 1; i <= m; i++)
if(ans[i]) printf("%d ", i);
printf("\n");
}
return 0;
}
T2 Qu'est-ce Que C'est?
译作:《写报告》(?)
考场做法
p.s.这题是真的绕,都给绕晕了
设表示写前个数,最小后缀和为的情况数
后来发现这个状态定义不是很好,于是限制成“合法情况数”。
有一个好习惯是提前考虑答案统计,可以快速发现做法有没有假。
(事实证明我还没培养好这种习惯)
状态转移,绕的有点晕就直接写了刷表
其中表示第个数填的多少。
然后第一维可以用滚动数组消除掉。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define lld long long
const lld p = 998244353;
const int N = 10005;
int n, m;
lld f[2][N];
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= m+m+1; i++)
f[1][i] = 1;
for(int i = 1; i < n; i++){
for(int j = 1; j <= m+m+1; j++)
f[1-(i&1)][j] = 0;
for(int j = 1; j <= m+m+1; j++){
int j_ = j-m-1;
for(int k = 1; k <= m+m+1; k++){
// k_+h_ -> j_(>=0) or h_ = j_
int k_ = k-m-1;
if(k_+j_ < 0) continue;
(f[1-(i&1)][min(k, k+j-m-1)] += f[i&1][j]) %= p;
}
}
}
lld ans = 0;
for(int i = 1; i <= m+m+1; i++)
(ans += f[n&1][i]) %= p;
printf("%lld\n", ans);
return 0;
}
正解
实际上就是我考场做法+前缀和优化
先换一个更好理解的状态转移方程:
假设填的数是,那么应该满足:
- ,或者说
其实不需要枚举,只需要枚举和。
分类讨论一下,会发现转移的时候实际上是区间加。
其中
于是改一下状态,使得它表示情况数的前缀和。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define lld unsigned long long
const lld p = 998244353;
const int N = 10005;
int n, m;
lld f[2][N];
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= m+m+1; i++)
f[1][i] = i;
for(int i = 2; i <= n; i++){
for(int j = 1; j <= m+m+1; j++)
f[i&1][j] = 0;
for(int j = 1; j <= m+m+1; j++){
int j_ = j-m-1;
if(j_ >= 0) f[i&1][j] = (f[1-(i&1)][m+m+1]-f[1-(i&1)][j_]+p)%p;
else f[i&1][j] = (f[1-(i&1)][m+m+1]-f[1-(i&1)][m-j_]+p)%p;
(f[i&1][j] += f[i&1][j-1]) %= p;
}
}
printf("%lld\n", f[n&1][m+m+1]);
return 0;
}
二编:原来是正解写挂了啊(悲)我还以为是好暴力
T3 Journey
非常经典的边分类复杂图论题(雾)
(怎么搜不到题解啊西内,给的题解是真的一点细节都没说啊)
题意
图中所有点有颜色(红蓝),经过时颜色会反转。要求一条长度小于的路径,使得图中所有点颜色一致。
判断无解
讨论图是否是二分图。
-
当图是二分图
性质:
给每个点在红蓝染色的基础上进行黑白染色。
对于一个点,它有两个颜色权值:红0蓝1,黑0白1。
由于二分图的性质,一定是每条边一定是(黑点,白点)。
考虑每次移动的影响:移动后,“所在点的黑白情况”会反转,且“某一点的红蓝”会反转。则两者造成的影响,在异或和中会抵消。
也就是说,无论怎么移动,的值都不会改变。
(其中表示点的黑白情况,表示当前所在点,表示所有点的红蓝情况异或和,结果均为0/1)
判断:
初始情况和终止情况的是否相同,若相同则有解,否则无解。
初始情况指:在点尚未行动时的局面。
终止情况指:到点且红蓝同色的局面。 -
当图非二分图
性质:
在图上建生成树,生成树一定是二分图。而由于原图非二分图,那么一定存在“调整边”(两端点同色),通过这些边可以令取反,故一定可以使得初始情况的等于终止情况的。
判断:
非二分图上必定有解。
构造方案
对于一个有解的二分图(非二分图的生成树),不需要调整,直接走到点就能得到答案。
而对于一个需要调整的非二分图,
按照以下步骤调整:
-
先沿着生成树走到一条“调整边”,经过这条边以调整。
-
走到终点,并从点开始进行树上dp以调整红蓝颜色。
(所以建造生成树时应当以为根) -
上述步骤的方案至多是步,但目标是,需要继续优化。
-
事实上可以一边调整,一边进行dp调整红蓝颜色,这样就可以省略第2条产生的步。此时的方案路径长度至多为。
-
还有一个补充的优化:“调整边”一定是返祖边,分若干种情况讨论可以使路径长度降低至。这一条笔者不理解,好在也并不必要。
关于第2条中的dp:
T4 NoCruelty
本文来自博客园,作者:_kilo-meteor,转载请注明原文链接:https://www.cnblogs.com/meteor2008/p/17654023.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现