NewOJ Week 5题解

NewOJ周赛于2022年3月19日正式开始,比赛时间为每周六晚19:00-22:00。

比赛链接:http://oj.ecustacm.cn/contest.php?cid=1019

A 并行处理

题意: 现在有 n n n个任务需要到GPU上运行,但是只有两张​GPU,每张​GPU​一次只能运行一个任务,两张GPU可以并行处理。告诉你 n n n个任务需要的时间,你需要选择一个数字 i i i:将任务 1 1 1到任务 i i i放到第一个GPU上运行,任务 i + 1 i+1 i+1到任务 n n n放到第二个GPU上运行。求最短运行时间。

Tag: 思维题,前缀和

难度:

来源: C o d e C h e f   E a s y CodeChef\ Easy CodeChef Easy

思路: 最短运行时间为 m i n { m a x ( ∑ j = 1 i a j , ∑ j = i + 1 n a j ) , i = 1 , . . . , n } min\{max(\sum_{j=1}^ia_j,\sum_{j=i+1}^na_j),i=1,...,n\} min{max(j=1iaj,j=i+1naj),i=1,...,n}。如果暴力计算每一个 i i i,则总的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

优化: 利用前缀和来优化里面那一项,最短运行时间为 m i n { m a x ( s u m [ i ] , s u m [ n ] − s u m [ i ] ) , i = 1 , . . . , n } min\{max(sum[i],sum[n]-sum[i]),i=1,...,n\} min{max(sum[i]sum[n]sum[i]),i=1,...,n},这样时间复杂度为 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
ll sum[maxn];
int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        sum[i] = sum[i - 1] + x;
    }
    ll ans = sum[n];
    for(int i = 1; i <= n; i++)
        ans = min(ans, max(sum[i], sum[n] - sum[i]));
    cout<<ans<<endl;
    return 0;
}

B 排列变换

题意: 给定一个长度为 n n n的排列 a a a,需要将这个排列变成 b b b。每次可以选择一个数字往左移若干个位置。请求出最小移动次数。

Tag: 思维题,类逆序对

难度: ☆☆

来源: U S A C O   2022   F e b USACO\ 2022\ Feb USACO 2022 Feb

思路: 将排列 a a a转换成排列 b b b,首先需要处理出数组 c c c c [ i ] c[i] c[i]表示数字 a [ i ] a[i] a[i] b b b中的下标。

例如样例 a = [ 5 , 1 , 3 , 2 , 4 ] , b = [ 4 , 5 , 2 , 1 , 3 ] a=[5,1,3,2,4],b=[4,5,2,1,3] a=[5,1,3,2,4],b=[4,5,2,1,3],对应的 c = [ 2 , 4 , 5 , 3 , 1 ] c=[2,4,5,3,1] c=[2,4,5,3,1] c c c表示当前排列 a a a对应的 b b b中的下标,则最终肯定要把 c c c变成 [ 1 , 2 , 3 , 4 , 5 ] [1,2,3,4,5] [1,2,3,4,5]

则问题变成:每次可以将 c c c数组中的数字往左移,最终变成 [ 1 , 2 , 3 , 4 , 5 ] [1,2,3,4,5] [1,2,3,4,5],求最小移动次数。

什么时候一定要往左移?存在逆序的时候,即 c [ i ] c[i] c[i]的左边存在一个大于 c [ i ] c[i] c[i]的数字,则 c [ i ] c[i] c[i]一定要左移。

对于每个 c [ i ] c[i] c[i]判断左边是否有数字大于 c [ i ] c[i] c[i],暴力求解时间复杂度为 O ( n 2 ) O(n^2) O(n2)

优化: 维护一个从左往右的最大值,判断最大值是否大于 c [ i ] c[i] c[i]即可。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int a[maxn], b[maxn], c[maxn];
int id[maxn];///id[x]表示数字x在b数组中的下标

int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    for(int i = 1; i <= n; i++)
        cin >> b[i], id[b[i]] = i;
    ///把a数组全部变成对应b数组的下标
    for(int i = 1; i <= n; i++)
        c[i] = id[a[i]];
    int ans = 0, Max = 0;
    for(int i = 1; i <= n; i++)
    {
        ///只要目前c[i]左边有一个比c[i]大的数字,则答案++
        if(Max > c[i])ans++;
        Max = max(Max, c[i]);
    }
    cout<<ans<<endl;
    return 0;
}

C 硬币游戏

题意: N N N个硬币,每次可以移除1个或者 p x p^x px个,其中 p p p为质数, x x x为正整数。均采取最优策略,求先手胜还是后手胜。

Tag: 博弈论,找规律

难度: ☆☆

来源: C o d e C h e f   E a s y CodeChef\ Easy CodeChef Easy

思路: 小范围模拟,可以发现 N = 1 , 2 , 3 , 4 , 5 N=1,2,3,4,5 N=1,2,3,4,5时,均为必胜态,因为 2 , 3 , 5 2,3,5 2,3,5为质数, 4 = 2 2 4=2^2 4=22,而 N = 6 N=6 N=6为必败态。按照这个规律可以发现问题变成巴什博奕

博弈基础:必胜态可以转换成必败态,而必败态只能转换成必胜态。

N N N只要是 6 6 6的倍数,那么一定是必败态。

1、如果 N % 6 = 0 N\%6=0 N%6=0,由于取出的数字要么是 1 1 1,要么是质数的幂,不可能取出一个 6 6 6的倍数,因此只能转移到非 6 6 6的倍数。

2、如果 N % 6 ≠ 0 N\%6\ne0 N%6=0,那么一定可以让对方面临 6 6 6的倍数。

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        int n;
        cin >> n;
        if(n % 6 != 0)cout<<"Alice"<<endl;
        else cout<<"Bob"<<endl;
    }
    return 0;
}

D 矩阵

题意: 给定两个 n ∗ n n*n nn的矩阵 A A A B B B,记 C = A ∗ B C=A*B C=AB(此处为矩阵乘法), m m m次询问,每次询问 C C C中一个子矩阵中所有数字之和。

Tag: 数学,前缀和,枚举

难度: ☆☆☆

来源: B Z O J   2901 BZOJ\ 2901 BZOJ 2901

思路: 矩阵乘法: C i , j = ∑ k = 1 n A i , k B k , j C_{i,j}=\sum_{k=1}^nA_{i,k}B_{k,j} Ci,j=k=1nAi,kBk,j,对于每次询问 a , b , c , d a,b,c,d a,b,c,d,询问子矩阵和为:
a n s = ∑ i = a c ∑ j = b d C i , j = ∑ i = a c ∑ j = b d ∑ k = 1 n A i , k B k , j = ∑ k = 1 n ∑ i = a c ∑ j = b d A i , k B k , j = ∑ k = 1 n [ ( ∑ i = a c A i , k ) ( ∑ j = b d B k , j ) ] \begin{aligned} ans &= \sum_{i=a}^c\sum_{j=b}^dC_{i,j} \\&=\sum_{i=a}^c\sum_{j=b}^d\sum_{k=1}^nA_{i,k}B_{k,j} \\&=\sum_{k=1}^n\sum_{i=a}^c\sum_{j=b}^dA_{i,k}B_{k,j} \\&=\sum_{k=1}^n\left[ \left(\sum_{i=a}^cA_{i,k}\right) \left(\sum_{j=b}^dB_{k,j}\right) \right] \end{aligned} ans=i=acj=bdCi,j=i=acj=bdk=1nAi,kBk,j=k=1ni=acj=bdAi,kBk,j=k=1n(i=acAi,k)j=bdBk,j
可以发现,括号中的两个值分别可以用 A A A的列前缀和、 B B B的行前缀和 O ( 1 ) O(1) O(1)获取。
∑ i = a c A i , k = ( S u m A [ c ] [ k ] − S u m A [ a − 1 ] [ k ] ) ∑ i = b d B k , j = ( S u m B [ k ] [ d ] − S u m B [ k ] [ b − 1 ] ) \sum_{i=a}^cA_{i,k}=(Sum_A[c][k]-Sum_A[a-1][k]) \\ \sum_{i=b}^dB_{k,j}=(Sum_B[k][d]-Sum_B[k][b-1]) i=acAi,k=(SumA[c][k]SumA[a1][k])i=bdBk,j=(SumB[k][d]SumB[k][b1])
因此,对于每次询问,暴力枚举 k k k即可,时间复杂度 O ( n 2 + m ∗ n ) O(n^2+m*n) O(n2+mn)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 1e9 + 7;
template <typename T>
inline T read(T& x) {
  x = 0;
  T w = 1;
  char ch = 0;
  while (ch < '0' || ch > '9') {
    if (ch == '-') w = -1;
    ch = getchar();
  }
  while (ch >= '0' && ch <= '9') {
    x = x * 10 + (ch - '0');
    ch = getchar();
  }
  return x = x * w;
}
const int maxn = 2010;
int A[maxn][maxn], B[maxn][maxn];
int main()
{
    int n, m;
    read(n); read(m);
    ///读入矩阵,同时处理前缀和
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            read(A[i][j]), A[i][j] += A[i - 1][j];
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            read(B[i][j]), B[i][j] += B[i][j - 1];
    while(m--)
    {
        int a, b, c, d;
        read(a);read(b);read(c);read(d);
        ///可能存在a>c、b>d的情况
        if(a > c)swap(a, c);
        if(b > d)swap(b, d);
        ll ans = 0;
        for(int k = 1; k <= n; k++)
            ans += (ll)(A[c][k] - A[a - 1][k]) * (B[k][d] - B[k][b - 1]);
        printf("%lld\n", ans);
    }
    return 0;
}

E 可达点

题意: n n n个点 m m m条边的有向图中,请你求出有多少个点是可达点。点 u u u为可达点:所有其他的点均可到达点 u u u

Tag: 强连通分量、缩点

难度: ☆☆☆

来源: B Z O J   1051 BZOJ\ 1051 BZOJ 1051

思路: 可达点表示所有点都可到达的点。对于一个普通图来说暴力去做肯定会超时,而对于 D A G DAG DAG来说,只需要统计出度为0的点是否只存在1个

在有向无环图中,出度为 0 0 0的点一定存在。如果存在多个,则说明这几个点之间互相是不可达的,此时不存在可达点。因此如果存在可达点,则最多存在 1 1 1个出度为0的点。

对于普通图而言,可以利用缩点,将图转换成 D A G DAG DAG,只需要求出 D A G DAG DAG中出度为 0 0 0的数目,等于1则有解。记 D A G DAG DAG中找到的可达点编号为 u u u,此时原图中可达点的数量 u u u对应原图强连通分量中点的数目。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 10000 + 10;
vector<int>G[maxn];
int pre[maxn], lowlink[maxn], sccno[maxn], dfs_clock, scc_cnt;
stack<int>S;

void dfs(int u)
{
    pre[u] = lowlink[u] = ++dfs_clock;
    S.push(u);
    for(int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        if(!pre[v])
        {
            dfs(v);
            lowlink[u] = min(lowlink[u], lowlink[v]);
        }
        else if(!sccno[v])
        {
            lowlink[u] = min(lowlink[u], pre[v]);
        }
    }
    if(lowlink[u] == pre[u])
    {
        scc_cnt++;
        for(;;)
        {
            int x = S.top();
            S.pop();
            sccno[x] = scc_cnt;
            if(x == u)break;
        }
    }
}
//求解强连通分量
void find_scc(int n)
{
    dfs_clock = scc_cnt = 0;
    for(int i = 0; i < n; i++)if(!pre[i])dfs(i);
}
int chu[maxn];
int main()
{
    int n, m, u, v;
    scanf("%d%d", &n, &m);
    while(m--)
    {
        scanf("%d%d", &u, &v);
        u--, v--;
        G[u].push_back(v);
    }
    find_scc(n);
    //重新构图成DAG,计算每个新点的出度
    for(int u = 0; u < n; u++)
    {
        for(int i = 0; i < G[u].size(); i++)
        {
            int v = G[u][i];
            if(sccno[u] != sccno[v])
            {
                chu[sccno[u]]++;
            }
        }
    }
    //统计出度为0点的个数
    int tot = 0, t;
    for(int i = 1; i <= scc_cnt; i++)
        if(chu[i] == 0)tot++, t = i;
    int ans = 0;
    if(tot == 1)//只有1个点出度为0,计算对应强连通分量中的点
    {
        for(int i = 0; i < n; i++)
        {
            if(sccno[i] == t)ans++;
        }
    }
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

傅志凌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值