GRYZ10.19模拟赛解题报告

写在前面

今天洛谷打卡大凶。

“忌考试,忌装弱”,感觉很慌。

期望得分:\(100+100+100=300pts\)
实际得分:\(100+100+100=300pts\)

不要FST啊!!!!!

没有大样例差评。

CSP有四道题,为什么模拟赛只给三道?

下面来分享一下我做题过程:

14:10 下发题面
14:13 读完 T1,感觉可以秒了它。
14:15 T2 不会扔掉,然后发现 T3 是非常经典的次短路(原汁原味)。
14:23 T1 写完了,开始写 T3 (因为忘记怎么做了只能边写边yy)
14:34 T3 写完了,一遍过了样例,感觉这个样例很弱,可能自己又要挂分了
14:35 感觉 T2 很像一个背包,然后二分答案在外面套起来,这样复杂度是 \(\mathcal O(n^2 \log V)\) 的,极限在 \(1.2e8\) 左右,我感觉机房的评测机不一定能跑过去。其他地方感觉也不好优化了,于是开始写。
14:45 T2 写完了,稍微调了调过了样例
15:15 把 T1 的暴力和对拍写完开始拍。
15:26 感觉有点无聊开始写今天的解题报告
15:50 解题报告写完了
16:30 感觉有点饿,出去吃了条脆脆鲨,但是被 ycc 发现了
17:30 一个小插曲:距离结束还有 10min,zzg 的电脑蓝屏了哈哈哈哈,大杯。
17:40 另一个小插曲:结束了我第一个把用飞鸽把代码发过去,一会儿 斜揽残箫 过来跟我说,来来来我好不容易手造了一个数据,专门卡什么什么的,我给你试一试。然后呢我把 freopen 注释掉试了试,但是我大意了,这个时候老师还没有接收我的代码,作为第一个发过去的被压在了最下面,然后接收的时候已经把 freopen 注释掉了,然后我就十分乌龙的挂掉了 T2 /ll
17:45 听说 Chen_怡 半小时 AK 了这场比赛,我只能 Orz。

T1

肯定不能像他说的那样枚举子集啊。

然后你转化一下方向,从前到后确定每一位,把 \(n\) 个串的某一位放到一起考虑,然后你发现可以直接统计某一位 \(1\) 的个数或者 \(0\) 的个数,然后通过判断多少直接确定出填什么是最优的。每一位是相互独立的,所以这样做没有问题。

时间复杂度 \(\mathcal O(nL)\),瓶颈在读入。

/*
Work by: Suzt_ilymics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

int n, L;
char s[MAXN];
int a[MAXN], b[MAXN];

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

int main()
{
    freopen("curse.in","r",stdin);
    freopen("curse.out","w",stdout);
	n = read();
	for(int i = 1; i <= n; ++i) {
	    cin >> s + 1;
	    L = strlen(s + 1);
	    for(int j = 1; j <= L; ++j) {
	        a[j] += (s[j] == '0');
        }
    }
    for(int i = 1; i <= L; ++i) {
        if(a[i] >= n - a[i]) {
            b[i] = 0;
        } else {
            b[i] = 1;
        }
    }
    for(int i = 1; i <= L; ++i) {
        printf("%d", b[i]);
    }
    puts("");
    return 0;
}

T2

看完题面,就能感觉出来它需要二分这个 \(L\)

因为你有两种法杖,你需要确定怎么去使用这些神光是最优的。然后你很自然的想到了 DP,而且他还很像背包 DP。

先对法坛的位置排序,方便后面的处理。

然后你发现 \(R,G\) 的范围很大,但是你想了想,发现它就是来吓唬你的。

\(R + G \ge n\) 的时候,直接输出 \(1\) 就行。

然后不能直接判断的情况就是 \(R + G \le 2000\) 的情况了。

这样的情况下足够支持你开一个二维数组。你想了想发现可以这样设状态:

\(f_{i,j}\) 表示已经消灭了前 \(i\) 个法坛,用了 \(j\) 次红色神光的情况下,最少用了多少次绿色神光。

转移方程:

\[f_{i,j} = \min (f_{i,j}, f_{l1,j-1}) \]

\[f_{i,j} = \min (f_{i,j}, f_{l2,j}+1) \]

我们让 \(a_i\) 作为光芒笼罩的右端点,因为每次神光可能会笼罩多个法坛,所以转移的时候要从上一个不能笼罩的法坛的位置转移。

其中 \(l1,l2\) 就分别表示红色神光和绿色神光上一个不能笼罩的位置。

你在把 \(a\) 数组排序后可以直接用 lower_bound 查一下这个位置,但是这会平白无故多一个 \(\log\),可能只有我这个傻逼会想到这么拉的做法吧。

然后你发现,每次 DP 时 \(L\) 是固定的,所以 \(l1,l2\) 的变化一定是单调递增的,然后你就发现你可以把他们当做指针,在不合法的时候暴力右移就可以了,很显然最多会移动 \(n\) 次。

然后就做完了。

时间复杂度 \(\mathcal O(nR \log V)\)

/*
Work by: Suzt_ilymics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 2021;
const int INF = 1e9+7;
const int mod = 1e9+7;

int n, R, G;
int a[MAXN];
int f[2021][2021];

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

bool Check(int lim) {
    for(int i = 1; i <= n; ++i) {
        for(int j = 0; j <= R; ++j) {
            f[i][j] = 0x3f3f3f3f;
        }
    }
//    cout<<"new start !\n";
//    cout<<lim<<" \n";
    f[0][0] = 0;
    int l1 = 0, l2 = 0;
    for(int i = 1; i <= n; ++i) {
        while(l1 < n && a[l1 + 1] <= a[i] - lim) l1++;
        while(l2 < n && a[l2 + 1] <= a[i] - 2 * lim) l2++;
//        cout<<l1<<" "<<l2<<" "<<i<<" \n";
        for(int j = 0; j <= R; ++j) {
            if(j) f[i][j] = min(f[i][j], f[l1][j - 1]);
            f[i][j] = min(f[i][j], f[l2][j] + 1);
        }
    }
    for(int i = 0; i <= R; ++i) {
        if(f[n][i] <= G) return true;
    }
    return false;
}

int main()
{
    freopen("light.in","r",stdin);
    freopen("light.out","w",stdout);
	n = read(), R = read(), G = read();
	for(int i = 1; i <= n; ++i) {
	    a[i] = read();
    }
    sort(a + 1, a + n + 1);
    if(R + G >= n) {
        puts("1");
        return 0;
    }
    int l = 1, r = 1000000000, ans = r;
    while(l <= r) {
        int mid = (l + r) >> 1;
//        cout<<l<<" "<<mid<<" "<<r<<"\n"; 
        if(Check(mid)) ans = mid, r = mid - 1;
        else l = mid + 1;
    }
    printf("%d\n", ans);
    return 0;
} 

T3

次短路板子题吧。

考虑在存最短路 \(dis\) 的同时记录一个次短路 \(Dis\)

因为一个点可能更新多次,所以我选择 SPFA,并且点数和边数看上去不是很多,应该不好卡。(卡到 \(\mathcal O(nm)\) 也不是不可以?)

更新的时候需要分类讨论一下。

不管最短路还是次短路更新的时候都将其入队。

然后就做完了。

/*
Work by: Suzt_ilymics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)

希望别假掉!!! 

*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 2e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

struct edge {
    int to, w, nxt;
}e[MAXN << 1];
int head[MAXN], num_edge = 1;

int n, m;
int dis[MAXN], Dis[MAXN];
bool vis[MAXN];
queue<int> q;

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

void add_edge(int from, int to, int w) { e[++num_edge] = (edge){to, w, head[from]}, head[from] = num_edge; }

void SPFA() {
    memset(dis, 0x3f, sizeof dis);
    memset(Dis, 0x3f, sizeof Dis);
    dis[1] = 0, vis[1] = true, q.push(1);
    while(!q.empty()) {
        int u = q.front(); q.pop();
        vis[u] = false;
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].to;
            int w1 = dis[u] + e[i].w, w2 = Dis[u] + e[i].w;
            if(dis[v] > w1) {
                dis[v] = w1;
                if(!vis[v]) q.push(v);
            } else if(dis[v] == w1) {
                if(Dis[v] > w2) {
                    Dis[v] = w2;
                    if(!vis[v]) q.push(v);
                }
            } else {
                if(Dis[v] > w1) {
                    Dis[v] = w1;
                    if(!vis[v]) q.push(v);
                }
            }
        }
    }
}

int main()
{
    freopen("maze.in","r",stdin);
    freopen("maze.out","w",stdout);
	n = read(), m = read();
	for(int i = 1, u, v, w; i <= m; ++i) {
	    u = read(), v = read(), w = read();
	    add_edge(u, v, w), add_edge(v, u, w);
    }
    SPFA();
    printf("%d\n", Dis[n]);
    return 0;
}
/*

in: 
5 7
1 2 5
1 5 10
1 3 1
1 4 5
2 4 3
4 5 5
3 4 4

out:
12

1 -> 3 -> 1 -> 3 -> 4 -> 5
*/
posted @ 2021-10-19 17:44  Suzt_ilymtics  阅读(111)  评论(5编辑  收藏  举报