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={ ab∣2≤a≤n,2≤b≤m}元素个数。
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=1n∑r=ln∑i=lra[i]
Tag: 前缀和、思维题
难度: ☆☆
来源: 51 n o d 2651 51nod\ 2651 51nod 2651
思路: 70 − 80 70-80 70−80分做法: 最后一个求和变成前缀和 s u m [ r ] − s u m [ l − 1 ] sum[r]-sum[l-1] sum[r]−sum[l−1]。
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 1≤l≤i≤r≤n的 < l , r > <l,r> <l,r>对的数量。有 i − 1 i-1 i−1个数字在 a [ i ] a[i] a[i]左边, n − i n-i n−i个数字在 a [ i ] a[i] a[i]右边,对于 a [ i ] a[i] a[i]而言,出现区间的数量为 ( i − 1 + 1 ) ∗ ( n − i + 1 ) (i-1+1)*(n-i+1) (i−1+1)∗(n−i+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]∗(i−1+1)∗(n−i+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;
}