NewOJ周赛于2022年3月19日正式开始,比赛时间为每周六晚19:00-22:00。
比赛链接:http://oj.ecustacm.cn/contest.php?cid=1015
A 前缀和的因子数
题意: 在自然数前缀和数列中,找出第一个超过 n ( n ≤ 500 ) n(n\le500) n(n≤500)个因子的数字。
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 1−n转化成英文单词,统计字母数量。
Tag: 模拟,分类讨论
难度: ☆☆
来源: 欧拉计划 P r o b l e m 17 Problem\ 17 Problem 17 改编
思路: 直接按照题意来模拟,分类讨论以下情况:
- 1 − 20 1-20 1−20:直接打表
- 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} 3n−1种情况,每种情况 O ( n ) O(n) O(n)求解表示式的值,总时间复杂度 O ( n 3 n − 1 ) O(n3^{n-1}) O(n3n−1)。
最终求解表达式的时候,先计算乘法,再计算加减法。
为方便计算,例如 a + b ∗ c ∗ d − c a+b*c*d-c a+b∗c∗d−c这种情况,处理乘法的时候可以逐步变成 a + 0 + b c ∗ d − c a+0+bc*d-c a+0+bc∗d−c、 a + 0 + 0 + b c d − c a+0+0+bcd-c a+0+0+bcd−c。
类似的,对于 a − b ∗ c ∗ d − c a-b*c*d-c a−b∗c∗d−c,也处理成 a − 0 − b c ∗ d − c a-0-bc*d-c a−0−bc∗d−c、 a − 0 − 0 − b c d − c a-0-0-bcd-c a−0−0−bcd−c,这样方便最后的加法和减法计算。
#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 i∗r。
#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;
}