文章目录
- A. TubeTube Feed
- 1、题目
- 2、分析
- 3、代码,
- B. Karina and Array
- 1、题目
- 2、分析
- 3、代码
- C. Bun Lover
- 1、问题
- 2、分析
- (1)观察样例法
- (2)正解推导
- 3、代码
- D. Super-Permutation
- 1、问题
- 2、分析
- (1)观察样例构造
- (2)构造的简单推导
- 3、代码
- E. Making Anti-Palindromes
- 1、问题
- 2、分析
- 3、代码
- F. Gardening Friends
- 1、问题
- 2、分析
- 3、代码
- G1. Magic Triples (Easy Version)
- 1、问题
- 2、分析
- 3、代码
- G2. Magic Triples (Hard Version)
- 1、问题
- 2、分析
- 3、代码
A. TubeTube Feed
A. TubeTube Feed
1、题目
有很多电视节目,每个节目有两个属性,一个是时长 a [ i ] a[i] a[i],一个是娱乐价值 b [ i ] b[i] b[i]。
这些节目按照顺序给出,我们只能选择一个节目进行观看,目的是获得最大的娱乐价值。如果当前节目不想看,则需要花费 1 1 1秒来跳过。
现在需要我们选出最优节目所对的下标。
如果一个都不能看的话,输出 − 1 -1 −1。
2、分析
将节目存入数组中,下标从0开始,则对于第 i i i个节目而言,我们需要等待的时间是 i i i秒,观看的时间是 a [ i ] a[i] a[i]。如果我们选择第 i i i个节目,就要花费 a [ i ] + i a[i]+i a[i]+i秒的时间。只要这个值在规定的 t t t内,就可以选。
基于上述分析,我们只需要选出所有在 t t t时间内的节目,再挑出最大值即可。
3、代码,
#include<bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 1e5 + 10;
void solve()
{
int n, t;
cin >> n >> t;
vector<int>a(n), b(n);
for(auto &x : a)
cin >> x;
for(auto &x : b)
cin >> x;
int ansv = -1 ,pos = -1;
for(int i = 0; i < n; i ++ )
{
if(t >= a[i] + i)
{
if(ansv < b[i])
{
ansv = b[i];
pos = i + 1;
}
}
}
cout << pos << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
solve();
}
B. Karina and Array
B. Karina and Array
1、题目
给定一个数组,我们人为规定顺序,规定好顺序后,相邻数字相乘得到一个新数组,再从新数组中挑出一个最大值。我们要做的就是输出这个最大值。
2、分析
很明显,如果是两个正数,我们选择最大的两个相乘。如果是两个负数,我们选择最小的两个相乘。
所以,我们只需要找到最大的两个数和最小的两个数,分别相乘后比较输出最大值即可。
不要忘了开 l o n g l o n g long long longlong,最大值为 1 0 9 ∗ 1 0 9 10^9*10^9 109∗109, i n t int int存不下。
3、代码
#include<bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f3f3f3f3f
#define int long long
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 1e5 + 10;
void solve()
{
int n;
cin >> n;
vector<int>a(n);
for(int i = 0; i < n; i ++ )
cin >> a[i];
sort(a.begin(), a.end());
cout << max(a[0] * a[1], a[n - 1] * a[n - 2]) << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
solve();
}
C. Bun Lover
C. Bun Lover
1、问题
给一个肉桂卷,即下图。要求我们出下列图案中棕色线条的长度。
其中,下列图案最外围的正方形边长是
n
n
n,中心的最短线段是
1
1
1。
2、分析
(1)观察样例法
观察样例,我们发现,所有的样例都是 ( n + 1 ) 2 + 1 (n+1)^2+1 (n+1)2+1。所以直接输出这个公式即可,不要忘了开 l o n g l o n g longlong longlong。
(2)正解推导
将棕色线条按上述三种颜色分类:
黄色线:
l
e
n
1
=
1
len1 = 1
len1=1
蓝色线:
l
e
n
2
=
(
n
+
1
)
∗
(
n
)
2
len2=\frac{(n+1)*(n)}{2}
len2=2(n+1)∗(n)
粉色线:
l
e
n
3
=
(
n
+
2
)
∗
(
n
+
1
)
2
len3=\frac{(n+2)*(n+1)}{2}
len3=2(n+2)∗(n+1)
综上:
l
e
n
=
l
e
n
3
+
l
e
n
2
+
l
e
n
1
l
e
n
=
(
n
+
2
)
∗
(
n
+
1
)
2
+
(
n
+
1
)
∗
(
n
)
2
+
1
=
(
2
n
+
2
)
∗
(
n
+
1
)
2
+
1
len=len3+len2+len1\\ len=\frac{(n+2)*(n+1)}{2}+\frac{(n+1)*(n)}{2}+1\\ =\frac{(2n+2)*(n+1)}{2}+1\\
len=len3+len2+len1len=2(n+2)∗(n+1)+2(n+1)∗(n)+1=2(2n+2)∗(n+1)+1
即:
l
e
n
=
(
n
+
1
)
2
+
1
len=(n+1)^2+1
len=(n+1)2+1
3、代码
#include<bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f
#define int long long
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 1e5 + 10;
void solve()
{
int n;
cin >> n;
cout << (n + 1) * (n + 1) + 1 << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
solve();
}
D. Super-Permutation
D. Super-Permutation
1、问题
给定一个排列,我们需要重新排序得到 a [ i ] a[i] a[i],并构造一个前缀和数列 s [ i ] s[i] s[i]。题目中的 b [ i ] b[i] b[i]即 s [ i ] % n s[i]\%n s[i]%n。此时,我们需要判断 b [ i ] + 1 b[i]+1 b[i]+1数组是否是一个排列?如果是,则构造一个 a [ i ] a[i] a[i]数组输出,如果不存在这样的数组,则输出 − 1 -1 −1。
2、分析
(1)观察样例构造
这种题型属于构造题,我们可以直接观察样例,按照样例的样子去构造。比如,我们发现如果排列的和是 n n n的倍数,则最后不存在这样的数列。反之,则存在。
(长度为 n n n的排列是指从 1 1 1到 n n n不重不漏的出现在数组内。)
但是需要特判 1 1 1这种情况,虽然 1 1 1对 1 1 1取模是 0 0 0,但是最终存在答案,即 1 1 1。
在存在的时候,我们只需要仿照最后一个样例构造:
即
n
,
n
−
1
,
2
,
n
−
3
,
4
,
n
−
5....
n,\ \ n-1,\ \ 2,\ \ n-3,\ \ 4,\ \ n-5....
n, n−1, 2, n−3, 4, n−5....
(2)构造的简单推导
假设
n
n
n在
a
a
a数组中的位置是
k
k
k,则
b
k
=
b
k
−
1
+
a
k
=
b
k
−
1
+
n
b_k=b_{k-1}+a_k=b_{k-1}+n
bk=bk−1+ak=bk−1+n
等式两边同时取模:
b
k
m
o
d
n
=
(
b
k
−
1
+
n
)
m
o
d
n
=
b
k
−
1
b_k\ mod\ n = (b_{k-1}+n)mod\ n = b_{k-1}
bk mod n=(bk−1+n)mod n=bk−1
即:
b
k
=
b
k
−
1
b_k=b_{k-1}
bk=bk−1,说明前后元素重复,必定不是排序,所以
n
n
n必须在第一个元素才行。
从上面可得到结论 b [ 1 ] = a [ 1 ] m o d n = 0 b[1]=a[1]mod\ n=0 b[1]=a[1]mod n=0
无论我们如何构造,我们的 a a a数组的最后一个 a [ n ] a[n] a[n]的数值必定是 s u m sum sum,而 s u m = 1 + 2 + 3 + . . + n = n ∗ ( n + 1 ) 2 sum=1+2+3+..+n=\frac{n*(n+1)}{2} sum=1+2+3+..+n=2n∗(n+1)。
当 n n n为奇数的时候, ( n + 1 ) 2 \frac{(n+1)}{2} 2(n+1)是正整数,即 s u m sum sum是 n n n的整数倍,即 b [ n ] = 0 b[n]=0 b[n]=0,此时说明 b [ 1 ] = b [ n ] b[1]=b[n] b[1]=b[n],二者重复,必定不是排列。
所以, n n n为奇数的时候不合法。
当 n n n为偶数的时候合法,构造方法直接仿造最后一个样例即可(构造方案不唯一)。
3、代码
#include<bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f
#define int long long
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 1e5 + 10;
void solve()
{
int n;
cin >> n;
int sum = 0;
sum = (n + 1) * n / 2;
if(n == 1)
{
cout << 1 << endl;
return;
}
if(sum % n == 0)
cout << -1 << endl;
else
{
cout << n << " ";
bool flag = true;
for(int i = 1; i < n; i ++)
{
if(flag)
{
cout << n - i << " ";
flag = false;
}
else
{
cout << i << " ";
flag = true;
}
}
cout << endl;
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
solve();
}
E. Making Anti-Palindromes
E. Making Anti-Palindromes
1、问题
目的是构造反回文串。反回文串满足: s [ i ] ≠ s [ n − i + 1 ] s[i]\neq s[n-i+1] s[i]=s[n−i+1]。如果存在,我们可以选择和其他字符交换位置。
现在给我们一个字符串,我们需要通过最小的操作次数将其调整为一个反回文串。如果存在这样的操作,输出最小的操作次数,如果无法调整为反回文串,输出 − 1 -1 −1。
2、分析
先来分析什么时候不存在答案。
如果字符串长度是奇数,则不满足。当走到中间的字符的时候,首尾指向了同一个字符,故上述等式恒成立。所以奇数的时候不存在答案。
如果某个字符数量超过了一半,也不存在解。
证明:假设字符
x
x
x的出现次数超过了一半,则必定存在
x
x
x和
x
x
x配对的情况,所以这种情况也是无解的。
其余情况均有解,现在考虑如何求出最优解。
我们从两端向中间扫描,统计出所有配对的字符对,并利用一个 m a p map map存储该字符对中的字符,即该字符对出现的次数。
我们想求的是最小的操作次数,即我们要尽量让不同类型的,已经匹配的字符对之间实现字符交换。比如现在存在aa配对,bb配对,我们交换一次,变成ab,ab,使得两组都实现了反回文。相当于1个顶2个。
当我们配对的字符对间实现两两交换后,如果还有剩余,则需要和其余不配对的字符交换。
综合上述,为了答案最小,我们就要让尽可能多的交换实现一个顶俩的效果,即配对字符对之间的交换。
为了方便理解,抽象为下面的数学模型。
我们用不同的颜色代表不同类型的字符对,线段的长度则代表了该类型字符对的个数。
那么最小的操作次数就转化成了对折后,最小的长度。
上图的情况中,最小长度就是黑色线的长度。
同时,上图仅仅是最长线段超过一半的情况,如果小于一半呢?就会出现下面的情况。
这种情况下,对折后的最小长度又是怎么样的呢?
很明显上图不是最优解。
我们可以做出如下调整:
即将下侧的一部分挪到上面去,此时就相当于正好对折。
因此,只要给出上面的情况,我们总能调整为恰好对折的情况(如果是奇数,则有一侧会多出一个)。
如果考虑奇数的问题,此时的最小长度就是 ( s u m + 1 ) / 2 (sum+1)/2 (sum+1)/2,即 s u m / 2 sum/2 sum/2上取整的结果。
综上所述:
我们不妨将最大的同类型的配对字符对数量记作 m a x max max,所有配对的字符对记作 s u m sum sum。
当
m
a
x
<
s
u
m
+
1
2
max<\frac{sum+1}{2}
max<2sum+1的时候,输出
(
s
u
m
+
1
)
/
2
(sum+1)/2
(sum+1)/2。
当
m
a
x
≥
s
u
m
+
1
2
max\geq \frac{sum+1}{2}
max≥2sum+1的时候,输出
m
a
x
max
max。
3、代码
#include<bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 2e5 + 10;
int st[N], n;
char s[N];
void solve()
{
memset(st, 0, sizeof st);
map<char, int>cnt;
cin >> n >> s + 1;
if(n % 2)
{
cout << -1 << endl;
return;
}
for(int i = 1; i <= n; i ++ )
st[s[i] - 'a'] ++;
int maxv = 0;
for(int i = 0; i <= 26; i ++ )
maxv = max(st[i], maxv);
if(maxv > n / 2)
{
cout << -1 << endl;
return;
}
int sum = 0;
for(int i = 1; i <= n / 2; i ++ )
{
if(s[i] == s[n - i + 1])
{
cnt[s[i]]++;
sum++;
}
}
maxv = 0;
for(auto x :cnt)
{
maxv = max(maxv, x.second);
}
if(maxv > (sum + 1) / 2)
cout << maxv <<endl;
else
cout << (sum + 1) / 2 << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
solve();
}
F. Gardening Friends
F. Gardening Friends
1、问题
给定一棵以1为根节点的有根树,距离根最远的点到根的距离为这棵树的价值,同时我们可以花费一定代价让根的子节点当根节点,得到一棵新树。将价值记作
v
a
l
val
val,交换的代价记作
c
o
s
t
cost
cost。
我们要求的就是
v
a
l
−
c
o
s
t
val-cost
val−cost的最大值。
2、分析
这道题考察树的直径。
树的直径即树中两个最远点之间的路径。
树的直径有一个性质,距离任何一个点最远的点是直径两个端点的其中一个。
我们利用这个性质可以求出树的直径,只需要写两次DFS。
第一次DFS可以找到其中一个端点 c c c,再从 c c c出发DFS,得到另一个端点 c c cc cc。
有了上面的前置知识,再来分析这道题。
先看交换的代价,代价等于以 1 1 1为根的时候,该节点的深度乘以交换一次的代价。
通过刚刚的知识,最远距离是到两个端点的距离的最大值,所以在找到直径端点后,分别以端点为根,DFS一遍,比较出最远距离,即价值。
最后再用价值-代价,求出该式子的最大值。
3、代码
#include<bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f
#define int long long
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 2e5 + 10;
int dep[N], c, cost[N], dep2[N],cc;
vector<int>edge[N];
void deep(int u, int father)
{
for(auto son: edge[u])
{
if(son == father)
continue;
cost[son] = cost[u] + 1;
deep(son, u);
}
}
void dfs(int u, int father)
{
for(auto son : edge[u])
{
if(son == father)
continue;
dep[son] = dep[u] + 1;
if(dep[son] > dep[c])
c = son;
dfs(son, u);
}
}
void dfs2(int u, int father)
{
for(auto son : edge[u])
{
if(son == father)
continue;
dep2[son] = dep2[u] + 1;
if(dep2[son] > dep2[cc])
cc = son;
dfs2(son, u);
}
}
void solve()
{
int n, k, m;
cin >> n >> k >> m;
for(int i = 0; i < n - 1; i ++ )
{
int a, b;
cin >> a >> b;
edge[a].push_back(b);
edge[b].push_back(a);
}
cost[1] = 0;
deep(1, -1);
dep[1] = 0;
dfs(1, -1);
dep2[c] = 0;
dfs2(c, -1);
dep[cc] = 0;
dfs(cc, -1);
int maxv = 0;
for(int i = 1; i <= n; i ++ )
maxv = max(max(dep[i], dep2[i]) * k - cost[i] * m, maxv);
cout << maxv << endl;
for(int i = 1; i <= n; i ++)
{
dep[i] = dep2[i] = cost[i] = 0;
edge[i].clear();
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
solve();
}
G1. Magic Triples (Easy Version)
1、问题
给定一个数组,从中挑出三个不同位置的数字,构成一个等比数列,求方案数。
2、分析
G 1 G1 G1是简单版本,所以这道题的数据范围比较小。
首先我们利用一个数组存储数字 x x x的出现次数,记作 c n t [ x ] cnt[x] cnt[x]。
我们先看公比是1的情况,这种情况下,三个数字相等。当出现次数大于2的时候,就会有这种方案。方案数为:
(
c
n
t
[
x
]
)
∗
(
c
n
t
[
x
]
−
1
)
∗
(
c
n
t
[
x
]
−
2
)
(cnt[x])*(cnt[x]-1)*(cnt[x]-2)
(cnt[x])∗(cnt[x]−1)∗(cnt[x]−2)
当公比不是 1 1 1的时候,我们只需要去遍历数组,然后对于任何一个数组元素 a [ i ] a[i] a[i],再去枚举所有可能的公比,然后找到对应的 a [ i ] ∗ b a[i]*b a[i]∗b和 a [ i ] ∗ b 2 a[i]*b^2 a[i]∗b2的出现次数。此时对答案的贡献就是: c n t [ a [ i ] ] ∗ c n t [ a [ i ] ∗ b ] ∗ c n t [ a [ i ] ∗ b 2 ] cnt[a[i]]*cnt[a[i]*b]*cnt[a[i]*b^2] cnt[a[i]]∗cnt[a[i]∗b]∗cnt[a[i]∗b2]
3、代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define int long long
using namespace std;
const int N = 1e6 + 10;
int cnt[N];
void solve()
{
int n, ans = 0;
cin >> n;
vector<int>a(n);
set<int>nums;
int maxv = -INF;
for(int i = 0; i < n; i ++)
{
cin >> a[i];
nums.insert(a[i]);
cnt[a[i]]++;
maxv = max(a[i], maxv);
}
for(auto x : nums)
if(cnt[x] > 2)
ans += (cnt[x] - 1) * (cnt[x]) * (cnt[x] - 2);
for(auto x : nums)
{
for(int j = 2; j * j <= maxv; j ++)
{
if(x * j * j <= maxv)
ans += (cnt[x] * cnt[x * j] * cnt[x * j * j]);
}
}
for(auto x : nums)
cnt[x] = 0;
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
solve();
}
G2. Magic Triples (Hard Version)
1、问题
困难版本即将数据范围从 1 0 6 10^6 106提升到了 1 0 9 10^9 109。
2、分析
此时我们的
c
n
t
cnt
cnt数组无法用数组来存储,只能使用
m
a
p
map
map。
接下来我们要讨论一下如何求方案。
求方案数必定需要遍历每一个元素,该过程需要花费的时间是 1 e 5 1e5 1e5的,所以留给我们判断的时间只有 1 e 3 1e3 1e3
当公比是1的时候,依然采用: ( c n t [ x ] ) ∗ ( c n t [ x ] − 1 ) ∗ ( c n t [ x ] − 2 ) (cnt[x])*(cnt[x]-1)*(cnt[x]-2) (cnt[x])∗(cnt[x]−1)∗(cnt[x]−2)
当公比不是1的时候,怎么办呢?
刚刚的方案是去枚举三个数当中最小的那个,现在我们则是要去枚举中间大的那个数字,即 x ∗ b x*b x∗b。很明显,如果去枚举中间这个数字的话,公比就必须是这个数的因数,所以我们的公比只需要去枚举这个数的因数。
枚举 a [ i ] a[i] a[i]的因数,我们只需要去枚举1到 a [ i ] \sqrt {a[i]} a[i],即求因数的复杂度是 O ( a [ i ] ) O(\sqrt {a[i]}) O(a[i])的,由于我们刚刚分析出求方案的时间要控制在 1 e 3 1e3 1e3之内,即只有当 a [ i ] a[i] a[i]小于 1 0 6 10^6 106的时候,我们才能用这种算法。
那么当 a [ i ] ≥ 1 0 6 a[i]\geq10^6 a[i]≥106的时候,我们可以采用刚刚的方法,即枚举 b b b。因为 a [ i ] < 1 e 9 a[i]<1e9 a[i]<1e9,所以我们的 b b b只需要从 1 1 1枚举到 1 e 3 1e3 1e3。恰好符合我们的时间要求。
上述这种按照数据大小分类的算法,叫做分块算法。
3、代码
#include<bits/stdc++.h>
#define int long long
#define INF 0x3f3f3f3f
using namespace std;
const int N = 2e5 + 10;
int a[N];
void solve()
{
int n;
cin >> n;
int maxv = -INF;
map<int,int>cnt;
for(int i = 0; i < n; i ++)
{
cin >> a[i];
maxv = max(maxv, a[i]);
cnt[a[i]] ++;
}
int ans = 0;
for(auto x : cnt)
if(x.second > 2)
ans += (cnt[x.first]) * (cnt[x.first] - 1) * (cnt[x.first] - 2);
for(auto x: cnt)
{
int num = x.first;
int val = x.second;
if(num > 1e6)
{
for(int b = 2; b * num <= maxv; b ++)
if(num % b == 0 && cnt.find(num / b) != cnt.end() && cnt.find(num * b) != cnt.end())
ans += (val) * cnt[num / b] * cnt[num * b];
}
else
{
for(int b = 1; b * b <= num; b ++)
if(num % b == 0)
{
if(b != 1 && cnt.count(num / b) && cnt.count(num * b))
ans += val * cnt[num / b] * cnt[num * b];
int nex = num / b;
if( nex != 1 && nex != b && cnt.count(num / nex) && cnt.count(num * nex))
ans += val * cnt[num / nex] * cnt[num * nex];
}
}
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
solve();
}