NewOJ Week 1题解

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

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

A 前缀和的因子数

题意: 在自然数前缀和数列中,找出第一个超过 n ( n ≤ 500 ) n(n\le500) n(n500)个因子的数字。

Tag: 数论、因子数

难度:

来源: 欧拉计划 P r o b l e m   12 Problem\ 12 Problem 12 改编

思路: 自然数前缀和数列 a a a 1 , 3 , 6 , 10 , 15 , 21 , . . . 1,3,6,10,15,21,... 1,3,6,10,15,21,...,通项公式 a i = i ∗ ( i + 1 ) 2 a_i=\frac{i*(i+1)}{2} ai=2i(i+1)

因此可以直接从小到大枚举 i i i,对每一个 a i a_i ai统计因子数。

因子数统计:

(1)可以在 O ( n ) O(\sqrt{n}) O(n )时间复杂度枚举 n \sqrt{n} n 以内所有数字,然后统计因子:
这是由于 n = p q n=pq n=pq p , q p,q p,q中至少有一个 ≤ n \le \sqrt{n} n ,因此找到一个小于 n \sqrt{n} n 的因子,贡献 + 2 +2 +2,特殊判断等于 n \sqrt{n} n 的情况。

(2)利用唯一分解定理,可以更快地求出一个数字的因子数:
如果 n = p 1 q 1 p 2 q 2 . . . p k q k n=p_1^{q_1}p_2^{q_2}...p_k^{q_k} n=p1q1p2q2...pkqk,其中 p 1 , p 2 , . . . , p k p_1,p_2,...,p_k p1,p2,...,pk为k个互不相同的素数,则因子数= ( q 1 + 1 ) ( q 2 + 1 ) . . . ( q k + 1 ) (q_1+1)(q_2+1)...(q_k+1) (q1+1)(q2+1)...(qk+1)
先预处理素数再去求因子数会更快。

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

//因子数模板
int count_divisors(ll n)
{
    int ans = 0;
    for(ll i = 1; i * i <= n; i++)//枚举根号n以内数字
    {
        if(n % i == 0)//找到因子贡献+2
        {
            ans++;
            if(i * i != n)ans++;//特判
        }
    }
    return ans;
}

int main()
{
    int n;
    cin >> n;
    for(ll i = 1; ; i++)
    {
        ll now = (i + 1) * i / 2;
        if(count_divisors(now) > n)
        {
            cout<<now<<endl;
            break;
        }
    }
    return 0;
}

B 英文数字计数

题意: 1 − n 1-n 1n转化成英文单词,统计字母数量。

Tag: 模拟,分类讨论

难度: ☆☆

来源: 欧拉计划 P r o b l e m   17 Problem\ 17 Problem 17 改编

思路: 直接按照题意来模拟,分类讨论以下情况:

  • 1 − 20 1-20 120:直接打表
  • 20 20 20 30 30 30 40 40 40、…、 90 90 90:直接打表
  • 100 100 100 200 200 200 300 300 300、…、 1000 1000 1000:直接打表
  • 剩下的两位数: a b ab ab,根据 a a a b b b找出对应字母
  • 剩下的三位数: a b c abc abc " a "   h u n d r e d   a n d + " b c " "a"\ hundred\ and + "bc" "a" hundred and+"bc" b c bc bc贡献同两位数的情况)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 1e9 + 7;

///one two three four five six seven eight nine ten
int a1[] = {0, 3, 3, 5, 4, 4, 3, 5, 5, 4, 3};

///eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty
int a2[] = {0, 6, 6, 8, 8, 7, 7, 9, 8, 8, 6};

///ten twenty thirty forty fifty sixty seventy eighty ninety hundred
int a3[] = {0, 3, 6, 6, 5, 5, 5, 7, 6, 6, 7};

///x小于100
int Count_100(int x)
{
    if(x <= 10)return a1[x];
    if(x <= 20)return a2[x - 10];
    return a3[x / 10] + a1[x % 10];
}

int Count(int x)
{
    if(x < 100)return Count_100(x);
    if(x == 100)return 10;   ///one hundred
    if(x == 1000)return 11;  ///one thousand
    if(x % 100 == 0)return a1[x / 100] + 7;
    return a1[x / 100] + 7 + 3 + Count_100(x % 100);
}

int main()
{
    int n, ans = 0;
    cin >> n;
    for(int i = 1; i <= n; i++)
        ans += Count(i);
    cout<<ans<<endl;
    return 0;
}

C 凑二十四

题意: 给定 n n n个数字,在其中添加+、-、×,求等于 24 24 24的方案数。

Tag: 暴力枚举、 d f s dfs dfs、表达式求值

难度: ☆☆☆

来源: U S A C O   2004   J a n USACO\ 2004\ Jan USACO 2004 Jan

思路: 直接枚举出所有情况,每种情况计算结果是否等于 24 24 24即可。

总共存在 3 n − 1 3^{n-1} 3n1种情况,每种情况 O ( n ) O(n) O(n)求解表示式的值,总时间复杂度 O ( n 3 n − 1 ) O(n3^{n-1}) O(n3n1)

最终求解表达式的时候,先计算乘法,再计算加减法。

为方便计算,例如 a + b ∗ c ∗ d − c a+b*c*d-c a+bcdc这种情况,处理乘法的时候可以逐步变成 a + 0 + b c ∗ d − c a+0+bc*d-c a+0+bcdc a + 0 + 0 + b c d − c a+0+0+bcd-c a+0+0+bcdc

类似的,对于 a − b ∗ c ∗ d − c a-b*c*d-c abcdc,也处理成 a − 0 − b c ∗ d − c a-0-bc*d-c a0bcdc a − 0 − 0 − b c d − c a-0-0-bcd-c a00bcdc,这样方便最后的加法和减法计算。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, a[11];
int op[11]; ///第i个间隔的符号 +-* => 012
int ans;

ll Value()
{
    ll t[11] = {0}, t_op[11] = {0};
    for(int i = 1; i <= n; i++)
        t[i] = a[i], t_op[i] = op[i];

    ///处理乘号:把乘积结果存入t[i+1]、t[i]设置为0、符号沿用前面的符号
    for(int i = 1; i < n; i++)
        if(t_op[i] == 2)
            t[i + 1] *= t[i], t[i] = 0, t_op[i] = t_op[i - 1];
    ///求和
    ll sum = t[1];
    for(int i = 1; i < n; i++)
    {
        if(t_op[i] == 0)sum += t[i + 1];
        else sum -= t[i + 1];
    }
    return sum;
}

void dfs(int depth)
{
    if(depth == n)
    {
        if(Value() == 24)ans++;
        return;
    }
    for(int i = 0; i <= 2; i++)
    {
        op[depth] = i;
        dfs(depth + 1);
    }
}
int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    dfs(1);
    cout<<ans<<endl;
    return 0;
}

D 矩形

题意: 给你 n n n个矩形的长宽,求最多可以构造出几层嵌套矩形。

Tag: 动态规划

难度: ☆☆☆

来源: U S A C O   2004   J a n USACO\ 2004\ Jan USACO 2004 Jan

思路: 对于每个矩形,我们规定宽小于等于长,然后按照宽为第一关键字、长为第二关键字进行排序。

排序的目的可以保证后面的矩形在宽上一定大于等于前面的矩形,在此基础上,求一个矩形长的上升子序列即可。

换句话说,固定一个维度从小到大,找另一个维度的最长上升子序列。

状态转移方程: d p [ i ] dp[i] dp[i]表示以第 i i i个矩形结束的最长上升序列, d p [ i ] dp[i] dp[i]初始等于 1 1 1.

如果矩形 i i i可以包含矩形 j j j,那么 d p [ i ] = m a x { d p [ i ] , d p [ j ] + 1 } dp[i]=max\{dp[i],dp[j]+1\} dp[i]=max{dp[i],dp[j]+1}

#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> rectangle;

rectangle r[110];
int dp[110];

int main()
{
    int n, ans = 0;
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        int a, b;
        cin >> a >> b;
        if(a > b)swap(a, b);///保持第一个小 第二个大
        r[i] = make_pair(a, b);
    }
    ///保证第一关键字非递减
    sort(r + 1, r + 1 + n);
    for(int i = 1; i <= n; i++)
    {
        dp[i] = 1;
        for(int j = 1; j < i; j++)
        {
            ///如果i可以包含j
            if(r[i].first > r[j].first && r[i].second >= r[j].second || \
               r[i].first == r[j].first && r[i].second > r[j].second)
                dp[i] = max(dp[i], dp[j] + 1);
        }
        ans = max(ans, dp[i]);
    }
    cout<<ans<<endl;
    return 0;
}

E 团队赛

题意: 比赛有 m m m道题目、 T T T分钟,有 n n n个人,完成每道题的时间为 r r r分钟。给你每个人会哪些题,计算最多完成题目和对应的最小罚时。

Tag: 二分图匹配

难度: ☆☆☆☆

来源: P O I   2011 POI\ 2011 POI 2011

思路: 由于每道题目完成的时间固定为 r r r,因此整个比赛可以分为 ⌊ T r ⌋ \lfloor\frac{T}{r}\rfloor rT个阶段,每个阶段中人和题目是一一对应关系,即一个人在该阶段内最多完成一道题目。

考虑每个阶段,给定人和题目的对应关系,只需要找到此时最大的二分图匹配即可。

在阶段 i i i,如果一个人 x x x找不到对应的题目可以匹配,那么在后续阶段,也找不到题目可以完成。也就是说,如果阶段 i i i无法找到从 x x x出发找不到增广路,那么在后续阶段也是不可能找到增广路,基于此可以剪枝,避免多次搜索。

如果一个人 x x x可以找到增广路,那么可以做的题目数量加 1 1 1,罚时取决于当前是第几个阶段,第 i i i个阶段的罚时为 i ∗ r i*r ir

#include<bits/stdc++.h>
using namespace std;
const int maxn = 500 + 10;
vector<int>Map[maxn];
int cx[maxn], cy[maxn];
//cx[i]表示X部i点匹配的Y部顶点的编号
//cy[i]表示Y部i点匹配的X部顶点的编号
bool vis[maxn];

bool dfs(int u)//X部的点进入增广路
{
    for(auto v : Map[u])if(!vis[v])//Y部的点打标记
    {
        vis[v] = 1;
        if(cy[v] == -1 || dfs(cy[v]))
        {
            cx[u] = v;
            cy[v] = u;
            return true;
        }
    }
    return false;
}
bool flag[maxn];///记录这个点是否已经找不到增广路
int main()
{
    int n, m, r, T, K;
    scanf("%d%d%d%d%d", &n, &m, &r, &T, &K);
    while(K--)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        Map[u].push_back(v);
    }
    memset(cx, -1, sizeof(cx));
    memset(cy, -1, sizeof(cy));
    int ans1 = 0, ans2 = 0;
    ///按照r将整个时间段分成T/r部分
    for(int i = 1; i <= T / r; i++)
    {
        ///每一部分进行一次二分图匹配

        for(int u = 1; u <= n; u++)if(flag[u] == 0)
        {
            memset(vis, 0, sizeof(vis));
            if(dfs(u))
            {
                ans1 += 1;
                ans2 += i * r;
            }
            else
            {
                flag[u] = 1;
            }
        }
    }
    printf("%d %d\n", ans1, ans2);
    return 0;
}
  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

傅志凌

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

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

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

打赏作者

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

抵扣说明:

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

余额充值