Codeforces Gym - 100917 部分题解

本文深入探讨了信息技术领域的核心内容,包括前端开发、后端开发、移动开发、游戏开发等细分技术领域。通过详细分析各类技术的特点和应用,旨在为读者提供全面的技术指导和深入的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A.Abstract Picture Gym - 100917A

分析:由于最后刷的一笔肯定使得某一行或者是某一列均为相同的颜色。
因此我们可以在一开始找到所有的行或者列,他们的颜色全都一样,把这样的行或列加入到队列里面去。
我们处理在队列里面的行或者列:把它取出来,并且把该笔刷成的颜色全都变成’?’(即可变颜色)。然后检查是否有新的行或者列变成颜色均相同了,如果有的话,继续加入队列中进行处理。
队列出队的顺序反过来即为答案。
具体实现细节略。


F - Find the Length Gym - 100917F

分析:因为不存在重复边,那么一个最短简单环,至少需要有三个点组成,而删掉最短简单环上的一条边uvuv那么剩下的一条路径一定是删去uvuv之后的从uuv的最短路。
对于一定包含点ss的最短简单环,我们枚举这里面假装删掉的那条边,那么会出现下面两种情况
一、这条边的一个端点是s
这种情况下,设另一端的端点为tt,如果stt的最短路就不是这条边,那么很好,从stt的最短路+stst就是一个可行结果,更新包含ss的最短简单环答案;而如果从stt的最短路就是这条边,那么这种情况我们直接扔掉,不做处理,因为这种情况可以由枚举其他边的时候解决。
二、这条边的两个端点uv均非ss
这种情况下,从uvv的最短路还要包含s,那么只能是从uus的最短路++vss的最短路,这里千万要注意两条最短路不能有重复边。否则从uvv的最短路将不会包含点s
具体做法:
枚举点ss,跑一边Dijkstra,得到一颗最短路径树,然后根据第一种情况更新一次答案。根据第二种情况更新答案的时候注意,判断两条路不包含重复边的方法可以用lca,当然也可以用其他的方法。

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
typedef pair<int,int> pii;
const int N = 400;
const int inf = 1e8;
int dist[N];
int G[N][N];
int n;
int fa[N];
int find(int x){
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void dij(int s){
    for(int i = 1;i <= n;++i) dist[i] = inf,fa[i] = i;
    dist[s] = 0;
    priority_queue<pii> Q;
    Q.push({0,s});
    while(!Q.empty()){
        pii p = Q.top();Q.pop();
        int u = p.second;
        int d = -p.first;
        if(d > dist[u]) continue;
        for(int v = 1;v <= n;++v){
            if(v == u) continue;
            if(dist[v] > dist[u] + G[u][v]){
                dist[v] = dist[u] + G[u][v];
                if(u != s) fa[v] = u;
                Q.push({-dist[v],v});
            }
        }
    }
}
#define pr(x) cout<<#x<<":"<<x<<endl
int main(){
    cin>>n;
    for(int i = 1;i <= n;++i){
        for(int j = 1;j <= n;++j){
            scanf(" %d",&G[i][j]);
            if(G[i][j] == -1) G[i][j] = inf;
        }
    }
    for(int s = 1;s <= n;++s){
        int ans = inf;
        dij(s);
        for(int i = 1;i <= n;++i){
            if(fa[i] != i){
                if(dist[i] + G[i][s] < ans)
                    ans = dist[i] + G[i][s];
            }
        }
        for(int i = 1;i <= n;++i){
            if(i == s) continue;
            for(int j = 1;j <= n;++j){
                if(j == s || find(i) == find(j)) continue;
                if(dist[i]+dist[j]+G[i][j] < ans)
                    ans = dist[i]+dist[j]+G[i][j];
            }
        }
        ans = ans == inf ? -1:ans;
        printf("%d\n",ans);
    }

    return 0;
}

I - Interactive Casino Gym - 100917I

这道题很有意思。
我们把所有的种子每个都用随机数生成器计算出前几个值,并把每个值得到的输赢用01串存起来,实践发现当计算到第38层的时候,我们就可以把每个种子的01串给区分开了。对于每个种子,01串的长度为38。
我们将这所有的01串放入一个Trie树里面去。一开始一直用猜1来试,可以根据反馈结果确定这次的位。38轮以后我们就可以找得到随机数种子了。找到随机数种子这道题基本就结束了。


J - Judgement Gym - 100917J

我们用背包dp求出在第一种方案下的权值和为s1s1时候所能得到第二种方案下权值和的最大值s2s2,只要出现s1<ps1<ps2>=qs2>=q的情况,那么就输出”NO”并且打印方案。反过来再做一遍就好了。
这里注意这道题要使用滚动数组,这样的话记录方案就会变得麻烦,为了保存信息,需要使用一个长为100的bitset来存方案。

#include <iostream>
#include <cstdio>
#include <assert.h>
#include <cstring>
#include <bitset>
using namespace std;
const int N = 1e6+10; 
int n,upb[2],val[2][N];
int dp[2][N];
bitset<101> way[2][N];
int vis[111];
int main(){
    scanf("%d",&n);
    scanf("%d",&upb[0]);
    for(int i = 1;i <= n;++i) scanf("%d",&val[0][i]);
    scanf("%d",&upb[1]);
    for(int i = 1;i <= n;++i) scanf("%d",&val[1][i]);
    int cc = 0 ;
    for(int cas = 0;cas < 2;++cas){
        for(int i = 1;i <= n;++i){
            for(int j = upb[cas]-1;j >= val[cas][i];j--){
                if(dp[cas][j-val[cas][i]] + val[cas^1][i] > dp[cas][j]){
                    dp[cas][j] = dp[cas][j-val[cas][i]] + val[cas^1][i];
                    //map[cas][j] = i;
                    way[cas][j] = way[cas][j-val[cas][i]];
                    way[cas][j].set(i); 
                    if(dp[cas][j] >= upb[cas^1]){
                        //output(cas,j);
                        puts("NO");
                        for(int i = 1;i <= n;++i){
                            cout<<way[cas][j][i];
                        }
                        return 0;
                    }
                }
            }
        }
    }
    puts("YES");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值