2023年8月24日练习赛复盘

复盘

题目列表

题目来自《2023牛客暑期多校训练营》
题目链接是内网SPOJ,未必能打开,建议下载题目文件(.7z)

時間情況

概況

總時長是4.5h,從早上8:00到中午12:30。說是這麼多,其實最後半個小時都在划水。

時間線

大概在9:00左右把T1的正解寫完了。

之後就在和T2鬥智鬥勇,雖然很容易看出來是DP題,但是搞不出來。先寫了個矩陣快速冪,調了蠻久的,結果發現時間複雜度和正確性都假了,遂跳過。猜測這時候是接近10:00。

T3大概想了10min左右,有一點想法,寫了輸入輸出和BFS。之後果斷放棄了。

T4的話,感覺很可以寫暴力。於是寫了個O(n2)的,並且花了一點時間進行剪枝(然而是無效剪枝,最終TLE0)。寫完估計是11:00左右。

對T4無能為力,於是又回去寫T2。重新搞了方程式,過樣例。

對所有題無能為力了,去把T3的“不可以,總司令”寫了。

然後到了12:00,其實已經打算開溜了,但是其他人都還在寫(尤其是hyb,說是正解快寫完),所以就原地划水(給自己OC寫點小產品),但是很怕被看到所以說效率極低。

部分分

這次根本沒有部分分啊
主要是由於題面沒有給出部分分數據範圍,所以就只能自己胡。

  • T1:是胡亂搞的正解,沒有寫部分分,但是最終掛了一個點(?)
  • T2:是O(nm2)的DP非正解,但是胡到70pts
  • T3:“不可以,總司令”胡到2pts(據說有100個測試點)
  • T4:是O(n2)的暴力非正解,爆零(貌似無人生還)

二編:T1掛的點是評測機問題,需要快讀快寫。

题解

T1 The Game of Eating

译作:《聚餐》

一个反向贪心。
没有证明,但可以毛估估(雾)。

所有人都非常聪明,但是前面的人拥有更多选择权。他知道自己后面的人一定会“替”他选某些菜,所以他把最喜欢的菜留给后面的人去选。他自己只选自己较喜欢但后面的人不会选的菜,这样可以点到更多自己喜欢的菜。
这个就叫借刀杀人借人点菜。

然后这个过程考虑反过来想。
最后一个人没办法把借人点菜,所以他一定会选自己最喜欢的菜。

再捋一遍。
假设最后一个人选的菜是A。或许前面有些人也喜欢A,但是他们预料到即使自己不点,最后一个人也会点A。于是前面的人都会把A预留下来,专门让最后一个人来点。
所以最后一个人一定会点A。
那么倒数第二个人,同理,他会点除了A以外自己最喜欢的菜。

于是可以从最后一个人开始贪心,每次点这个人还没被点的最喜欢的菜,直到让第一个人点第k道菜。

换一个角度想。
前面的人是不是理论上有更多选择权?他们可以选菜的次数和选择范围都比后面的人多。
所以我们可以假定“前面的人拥有更多的权利”。
于是让后面的人给他们跑腿,选他们喜欢的菜。

再换一个角度想。
选了最喜欢的菜,但却给后面的人“打白工”,不是雪妈亏?
反之,不亏就是赚,可以直接贪。

点击查看代码
#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.这题是真的绕,都给绕晕了

f(i,j)表示写前i个数,最小后缀和为j的情况数

后来发现这个状态定义不是很好,于是限制成“合法情况数”。

有一个好习惯是提前考虑答案统计,可以快速发现做法有没有假。
(事实证明我还没培养好这种习惯)

ans=j=mmf(n,j)

状态转移,绕的有点晕就直接写了刷表

f(i,j)f(i+1,min(k,k+j))

其中k([j,m])表示第i+1个数填的多少。

然后第一维可以用滚动数组消除掉。

点击查看代码
#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;
}

正解

参考题解

实际上就是我考场做法+前缀和优化

先换一个更好理解的状态转移方程:

f(i1,k)f(i,j)

假设填的数是x,那么应该满足:

  1. x+k0,或者说xk
  2. j=min(x,x+k)

其实不需要枚举j,只需要枚举xk

分类讨论一下,会发现转移的时候实际上是区间加。

{f(i,j)f(i+1,k1),j<0f(i,j)f(i+1,k2),j0

其中k1[0,m+j],k2[j,m]

于是改一下状态,使得它表示情况数的前缀和。

点击查看代码
#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

非常经典的边分类复杂图论题(雾)
(怎么搜不到题解啊西内,给的题解是真的一点细节都没说啊)

题意

图中所有点有颜色(红蓝),经过时颜色会反转。要求一条长度小于5e5的路径,使得图中所有点颜色一致。

判断无解

讨论图是否是二分图。

  1. 当图是二分图
    性质:
    给每个点在红蓝染色的基础上进行黑白染色。
    对于一个点,它有两个颜色权值:红0蓝1,黑0白1。
    由于二分图的性质,一定是每条边一定是(黑点,白点)。
    考虑每次移动的影响:移动后,“所在点的黑白情况”会反转,且“某一点的红蓝”会反转。则两者造成的影响,在异或和中会抵消。
    也就是说,无论怎么移动,S=col(u)col2的值都不会改变。
    (其中col(u)表示点u的黑白情况,u表示当前所在点,col2表示所有点的红蓝情况异或和,结果均为0/1)
    判断:
    初始情况和终止情况的S是否相同,若相同则有解,否则无解。
    初始情况指:在点1尚未行动时的局面。
    终止情况指:到点n且红蓝同色的局面。

  2. 当图非二分图
    性质:
    在图上建生成树,生成树一定是二分图。而由于原图非二分图,那么一定存在“调整边”(两端点同色),通过这些边可以令S取反,故一定可以使得初始情况的S等于终止情况的S
    判断:
    非二分图上必定有解。

构造方案

对于一个有解的二分图(非二分图的生成树),不需要调整,直接走到点n就能得到答案。
而对于一个需要调整的非二分图,
按照以下步骤调整

  1. 先沿着生成树走到一条“调整边”,经过这条边以调整S

  2. 走到终点n,并从点n开始进行树上dp以调整红蓝颜色。
    (所以建造生成树时应当以n为根)

  3. 上述步骤的方案至多是6n步,但目标是5n,需要继续优化。

  4. 事实上可以一边调整S,一边进行dp调整红蓝颜色,这样就可以省略第2条产生的n步。此时的方案路径长度至多为5n

  5. 还有一个补充的优化:“调整边”一定是返祖边,分若干种情况讨论可以使路径长度降低至4n。这一条笔者不理解,好在也并不必要。

关于第2条中的dp:

T4 NoCruelty

推荐题解

posted @   _kilo-meteor  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示