NewOJ Week 2题解

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

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

A xyz

题意: x , y , z x,y,z x,y,z为三个正整数,给定 7 7 7个数表示 x , y , z , x + y , x + z , y + z , x + y + z x,y,z,x+y,x+z,y+z,x+y+z x,y,z,x+y,x+z,y+z,x+y+z的某种排列,求 x , y , z x,y,z x,y,z

Tag: 思维题

难度:

来源: U S A C O   2020   D e c USACO\ 2020\ Dec USACO 2020 Dec

思路: 由于 x , y , z x,y,z x,y,z都是正整数,所以最小的两个数字分别对应着 x x x y y y,所以只需要排个序就求出了 x x x y y y。而 7 7 7个数字中的最大值一定对应着 x + y + z x+y+z x+y+z,所以 z z z可以利用最大值减去 x x x y y y求得。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
    int a[7];
    for(int i = 0; i < 7; i++)
        cin >> a[i];
    sort(a, a + 7);     //排序
    //最小的数字a[0]、a[1]对应着x和y
    //最大的数字a[6]对应着x+y+z
    cout<<a[0]<<" "<<a[1]<<" "<<a[6] - a[0] - a[1]<<endl;
    return 0;
}

B Pow Set

题意: 求集合 S = { a b ∣ 2 ≤ a ≤ n , 2 ≤ b ≤ m } S=\{a^b|2\le a \le n,2\le b \le m\} S={ab2an,2bm}元素个数。

Tag: 数论、哈希

难度: ☆☆☆

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

思路1: 对于两个幂而言,如果 a 1 b 1 = a 2 b 2 a_1^{b_1}=a_2^{b_2} a1b1=a2b2,则它们的因式分解后的素因子表示是相同的。因此可以最开始就处理出 [ 2 , 500 ] [2,500] [2,500]每个数字的因式分解,然后对于每个幂 a b a^b ab而言,只需要对 a a a的因式分解的基础上,所有指数乘上 b b b即可。

我们可以用 p a i r < i n t , i n t > x pair<int,int>x pair<int,int>x表示 x . f i r s t x . s e c o n d x.first^{x.second} x.firstx.second,这样对于每个数字的因式分解可以用 v e c t o r < p a i r < i n t , i n t > > vector<pair<int,int> > vector<pair<int,int>>表示。然后再用 S T L STL STL中的集合set去重。

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

///对x进行因式分解
vector<Pair> factorization(int x)
{
    vector<Pair> ans;
    for(int i = 2; i * i <= x; i++)if(x % i == 0)///i是x的因子
    {
        int num = 0;
        while(x % i == 0)++num, x /= i;///统计i的幂
        ans.push_back(make_pair(i, num));///存入ans
    }
    ///最后可能会有一个大于根号x的素因子
    if(x != 1)ans.push_back(make_pair(x, 1));
    return ans;
}

///all_factor[i]表示i的唯一分解
vector<Pair>all_factor[510];

///存储集合
set<vector<Pair>>S;

///把a^b以唯一分解的形式塞入集合S
void insert_pow(int a, int b)
{
    vector<Pair>ans;
    ///遍历a的唯一分解
    for(auto x : all_factor[a])
        ans.push_back(make_pair(x.first, x.second * b));
    S.insert(ans);
}

int main()
{
    ///预处理出2到500每个数字的唯一分解表示
    for(int i = 2; i <= 500; i++)
        all_factor[i] = factorization(i);
    int n, m;
    cin >> n >> m;
    for(int a = 2; a <= n; a++)
        for(int b = 2; b <= m; b++)
            insert_pow(a, b);
    cout<<S.size()<<endl;
    return 0;
}

思路2: 由于这里要求的相当于大数去重,我们无法直接求出大数,但是可以间接利用哈希的思路来判断是否重复。

比如我们利用一个模数 m m m,计算出每一个 a b % m a^b\%m ab%m,对集合 a b % m {a^b\%m} ab%m去重即可。

但是可能会存在两个不同的幂, a 1 b 1 ≠ a 2 b 2 a_1^{b_1}\ne a_2^{b_2} a1b1=a2b2,但是对 m m m取模之后相同。

为了避免这种情况,可以采用两个模数 m 1 , m 2 m_1,m_2 m1,m2,那么对于一个幂 a b a^b ab,我们用二元组 < a b % m 1 , a b % m 2 > <a^b\%m_1,a^b\%m_2> <ab%m1,ab%m2>表示。只要二元组相同,认为幂是相同的。

一般来说常用的 m 1 m_1 m1 m 2 m_2 m2可以取 1 0 9 + 7 10^9+7 109+7 1 0 9 + 9 10^9+9 109+9,这是由于取孪生质数出错的概率较低。

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

///快速幂模板
int ksm(int a, int b, int m)
{
    int ans = 1;
    while(b)
    {
        if(b & 1)ans = (ll)ans * a % m;
        b >>= 1;
        a = (ll)a * a % m;
    }
    return ans;
}

///存储哈希二元组
set<Pair>S;

int main()
{
    int MOD1 = 1e9 + 7;
    int MOD2 = 1e9 + 9;
    int n, m;
    cin >> n >> m;
    for(int a = 2; a <= n; a++)
        for(int b = 2; b <= m; b++)
            S.insert(make_pair(ksm(a, b, MOD1), ksm(a, b, MOD2)));
    cout<<S.size()<<endl;
    return 0;
}

C Sum of Interval

题意: 给定长度为 n n n的数组 a a a,求 ∑ l = 1 n ∑ r = l n ∑ i = l r a [ i ] \sum_{l=1}^n\sum_{r=l}^n\sum_{i=l}^{r}a[i] l=1nr=lni=lra[i]

Tag: 前缀和、思维题

难度: ☆☆

来源: 51 n o d   2651 51nod\ 2651 51nod 2651

思路: 70 − 80 70-80 7080分做法: 最后一个求和变成前缀和 s u m [ r ] − s u m [ l − 1 ] sum[r]-sum[l-1] sum[r]sum[l1]

100 100 100分做法: 反向思考,考虑每一个 a [ i ] a[i] a[i]出现的次数,即 a [ i ] a[i] a[i]在哪些区间 [ l , r ] [l,r] [l,r]中出现,相当于求 1 ≤ l ≤ i ≤ r ≤ n 1\le l \le i \le r\le n 1lirn < l , r > <l,r> <l,r>对的数量。有 i − 1 i-1 i1个数字在 a [ i ] a[i] a[i]左边, n − i n-i ni个数字在 a [ i ] a[i] a[i]右边,对于 a [ i ] a[i] a[i]而言,出现区间的数量为 ( i − 1 + 1 ) ∗ ( n − i + 1 ) (i-1+1)*(n-i+1) (i1+1)(ni+1)

所以,最终的答案 a n s = ∑ i = 1 n a [ i ] ∗ ( i − 1 + 1 ) ∗ ( n − i + 1 ) ans=\sum_{i=1}^na[i]*(i-1+1)*(n-i+1) ans=i=1na[i](i1+1)(ni+1)

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

D 极差

题意: 询问固定数组的区间极差

Tag: S T ST ST表,线段树

难度: ☆☆☆

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

思路: 由于对原数组不进行修改,可以直接用 S T ST ST表求区间最大值、区间最小值。也可以使用线段树进行查询区间最值。不带修改的线段树写起来也很简便。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e4 + 10;
int tree_max[maxn << 2];
int tree_min[maxn << 2];
int a[maxn];

///建树
void build(int o, int l, int r)
{
    if(l == r)
    {
        tree_max[o] = tree_min[o] = a[l];
        return;
    }
    int mid = (l + r) >> 1;
    ///递归建立左子树和右子树
    build(o << 1, l, mid);
    build(o << 1 | 1, mid + 1, r);
    ///回溯时更新父节点权值
    tree_max[o] = max(tree_max[o<<1], tree_max[o<<1|1]);
    tree_min[o] = min(tree_min[o<<1], tree_min[o<<1|1]);
}

int ans_max, ans_min;
///查询区间[L, R]的最值
void query(int o, int l, int r, int L, int R)
{
    ///递归出口 [l,r]属于[L,R]
    if(L <= l && R >= r)
    {
        ans_max = max(ans_max, tree_max[o]);
        ans_min = min(ans_min, tree_min[o]);
        return;
    }
    ///递归处理左右子树即可
    int mid = (l + r) >> 1;
    if(L <= mid)query(o << 1, l, mid, L, R);
    if(R > mid)query(o << 1 | 1, mid + 1, r, L, R);
}

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    build(1, 1, n);
    while(m--)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        ans_max = 0;
        ans_min = 1e9 + 7;
        query(1, 1, n, l, r);
        printf("%d\n", ans_max - ans_min);
    }
    return 0;
}

E 比赛

题意: n n n位选手进行 m m m场比赛,每场比赛两位选手均有可能获胜,赢的最多的人,胜利次数最小是多少。

Tag: 二分答案、网络流

难度: ☆☆☆☆

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

思路: 这道题目属于典型的最大值最小化,可以考虑使用二分答案进行求解。假设当前赢的最多的人胜利次数为 x x x

将问题转换成判定性问题:每个选手最多赢 x x x场,能否完成 m m m场比赛。

m m m场比赛中,每场比赛最多只有一个人胜利。在上述约束“每个选手最多赢 x x x场”下,给每场比赛匹配一个胜利的人。

如果我们把比赛和人都看做点的话,用流量表示胜利次数,则图可以很容易建立:

  • 每场比赛只允许一个人胜利:源点 S S S向比赛连边,流量为 1 1 1
  • 每场比赛对应两个人:比赛向两位选手分别连边,流量为 1 1 1
  • 每个选手最多赢 x x x场:选手向汇点 T T T连边,流量为 x x x

如果从 S S S T T T的最大流等于 m m m,说明m场比赛在上述约束下,都可以成功匹配,至此,判定性问题解决。

回到最大值最小化问题,利用二分答案模板,由于本题求最小值,因此满足条件时,说明 x x x是合法的,最终的答案还可能更小,更新的是右端点 r i g h t right right

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e4 + 10;
const int INF = 1e9 + 7;
int n, m;
int a[10010], b[10010];
struct edge
{
    int u, v, c, f;
    edge(int u, int v, int c, int f):u(u), v(v), c(c), f(f){}
};
vector<edge>e;
vector<int>G[maxn];

int level[maxn];//BFS分层,表示每个点的层数
int iter[maxn];//当前弧优化

void init(int n)
{
    for(int i = 0; i <= n; i++)G[i].clear();
    e.clear();
}
void addedge(int u, int v, int c)
{
    e.push_back(edge(u, v, c, 0));
    e.push_back(edge(v, u, 0, 0));
    int m = e.size();
    G[u].push_back(m - 2);
    G[v].push_back(m - 1);
}
void BFS(int s)//预处理出level数组
//直接BFS到每个点
{
    memset(level, -1, sizeof(level));
    queue<int>q;
    level[s] = 0;
    q.push(s);
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int v = 0; v < G[u].size(); v++)
        {
            edge& now = e[G[u][v]];
            if(now.c > now.f && level[now.v] < 0)
            {
                level[now.v] = level[u] + 1;
                q.push(now.v);
            }
        }
    }
}
int dfs(int u, int t, int f)//DFS寻找增广路
{
    if(u == t)return f;//已经到达源点,返回流量f
    for(int &v = iter[u]; v < G[u].size(); v++)
        //这里用iter数组表示每个点目前的弧,这是为了防止在一次寻找增广路的时候,对一些边多次遍历
        //在每次找增广路的时候,数组要清空
    {
        edge &now = e[G[u][v]];
        if(now.c - now.f > 0 && level[u] < level[now.v])
            //now.c - now.f > 0表示这条路还未满
            //level[u] < level[now.v]表示这条路是最短路,一定到达下一层,这就是Dinic算法的思想
        {
            int d = dfs(now.v, t, min(f, now.c - now.f));
            if(d > 0)
            {
                now.f += d;//正向边流量加d
                e[G[u][v] ^ 1].f -= d;
    //反向边减d,此处在存储边的时候两条反向边可以通过^操作直接找到
                return d;
            }
        }
    }
    return 0;
}
int Maxflow(int s, int t)
{
    int flow = 0;
    for(;;)
    {
        BFS(s);
        if(level[t] < 0)return flow;//残余网络中到达不了t,增广路不存在
        memset(iter, 0, sizeof(iter));//清空当前弧数组
        int f;//记录增广路的可增加的流量
        while((f = dfs(s, t, INF)) > 0)
            flow += f;
    }
    return flow;
}

///赢最多的人次数为mid,判断是否合法
bool check(int mid)
{
    int S = 0, T = n + m + 1;
    init(T);
    ///源点S向每场比赛连边,流量为1
    for(int i = 1; i <= m; i++)
        addedge(S, i, 1);
    ///每场比赛分别向两个选手连边
    for(int i = 1; i <= m; i++)
        addedge(i, a[i] + m, 1), addedge(i, b[i] + m, 1);
    ///每个选手向汇点T连边,流量为mid,表示每个人最多赢mid次
    for(int i = 1; i <= n; i++)
        addedge(i + m, T, mid);
    ///满流则说明每场比赛在当前约束下,都可以正常匹配,说明允许每个人最多赢mid次
    return Maxflow(S, T) == m;
}
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
        cin >> a[i] >> b[i];
    int left = 1, right = m, ans;
    while(left <= right)
    {
        int mid = (left + right) >> 1;
        if(check(mid))
        {
            ///合法的话,说明可以继续更小
            ans = mid;
            right = mid - 1;
        }
        else
            left = mid + 1;
    }
    cout<<ans<<endl;
    return 0;
}
  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

傅志凌

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

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

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

打赏作者

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

抵扣说明:

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

余额充值