【2020-10-1 模拟赛】
今年悲催的在外过国庆 + 中秋
100 + 82 + 70 + 2 = 257
全场Rk8(我太菜辽)
A
回家 (home)
1.1 题目描述
一只袋鼠在数轴上。0 时刻,袋鼠位于 0 处。在时刻 i−1 和 i 之间的时间段中,
袋鼠要么待在当前位置,要么向左或向右跳恰好 i 单位长度。也就是说,如果时
刻 i − 1 时它在 x 位置,那么时刻 i 时它可能的位置是 x − i、x 或 x + i。袋鼠
的家在 X 位置处,它想尽快从 0 走到 X。求袋鼠最早在哪个时刻可以到达 X。
1.2 输入格式
一行一个整数 X。
1.3 输出格式
一个整数,表示袋鼠最早在哪个时刻可以到达 X。
算法
签到题
这题等价于把1~n这个区间分成k个子区间
其中\(k_1 < k_2 < k_3 < k_4······<k_i\)
k_i就是我们的答案
比赛的时候用的二分 检验一个mid 如果\(1 + 2 + 3 + ······ + mid < n\)
代码
#include<iostream>
#include<cstdio>
#include<map>
#define ll long long
using namespace std;
ll n;
ll l,r;
bool cheek(ll num)
{
ll ans = 0;
for(ll i = num;i >= 1;i--)
{
ans += i;
if(ans >= n)
return true;
}
return false;
}
int main()
{
freopen("home.in","r",stdin);
freopen("home.out","w",stdout);
scanf("%lld",&n);
l = 1,r = n;
while(l <= r)
{
ll mid = (l + r)>>1;
if(cheek(mid))
r = mid;
else
l = mid + 1;
if(l == r)
break;
}
cout<<r;
}
B
不必要 不必要 (unnecessary)
2.1 题目描述
小 A 有 N 张写有正整数的牌,第 i 张牌上的数是 a i (1 ≤ i ≤ N) 。因为他很
喜欢大的数,所以他认为一个卡牌的子集是“好”的,当且仅当该子集中的卡
牌上的数之和大于等于 K(卡牌上的数可以相同) 。
接下来,他会判断每张牌 i 是否是“不必要”的,判断方法如下:
• 如果所有包含 i 的“好”的子集去掉 i 后仍然是“好”的,那么 i 是“不
必要”的。
• 否则,i 不是“不必要”的。
请你求出“不必要”的牌的张数。
注意:每张牌的判断是独立的,并且他不会扔掉那些被判断为“不必要”的牌。
2.2 输入格式
第一行,两个正整数 N,K。
第二行,N 个正整数 a i 。
2.3 输出格式
一个数, “不必要”的牌的张数。
心路历程
刚开始看到这道题
大概10min码完了
然后因为题目样例给的太水
我的错误想法水过了全部的样例
我的想法是先把所有元素排序 然后对最后这个数组进行子序列的算法 让每个子序列\(a_i < a_{i+1} < a_{i+2} < ······ < a_j\) 其中
\(\left\{ \begin{array}{**lr**} a_{i} + a_{i+1} + a_{i+2} + a_{i+3} ······ + a_j \geq k\\ a_{i+1} + a_{i+2} + a_{i+3} ······ + a_j < k \end{array} \right.\)
维护这些子序列的并的元素的数量为cnt
最后输出 n - cnt
这样明显是错的
没有考虑到所有的子集
这里提供一组bug数据
stdin:
5 13
2 2 2 2 9
stdout:
0
但是这个错误程序跑了82分 真怀疑数据用脚造的
最后十分钟发现了这个错误但还是无力回天了
难过
算法
暴力的算法就不考虑了
算法一:
我们记\(f_i[s],g_i[s]\)分别表示\(1~i,i~N\)是否存在和为s的子集
然后枚举每张牌\(i\),用\(f_{i - 1} 、g_{i + 1}\)判断是否存在和为\([K-a_i,K)\)的子集。
如果存在 那么这张牌\(i\)是必要的
我们最后减去所有必要的牌就是答案
时间复杂度: \(O(NK)\)
算法二:
我们考虑优化算法一:
对于\(a_i\geq a_j\) 如果\(a_j\)是不必要的 那么\(a_i\)也是不必要的
将a从小到大排序 那么“不必要”的一定是一段前缀和
我们从后往前加入每个元素 我们考虑我们现在加入到第\(i + 1 ~ N\) 此时 考虑 有\(p_{i + 1} 个不必要的牌\)
考虑加入\(i\)如何计算\(p_i\)
先考虑i是否是只考虑\(i ~ N\)时不必要的牌
我们只需要判断\(i + 1 ~ N\)中\(< K\)的子集和的最大值是否大于\(\geq K - a_i\)
记\(s_i\)表示\(i ~ N\)中 \(< K\)的子集和的最大值
则
\(s_i = \left\{
\begin{array}{**lr**}
s_{i + 1} (if a_i + s_{i +1}\geq K)\\
a_i + s_{i + 1} (if a_i + s_{i + 1}< K)
\end{array}
\right.\)
用\(s_i\)可将只考虑\(i ~ N\)时\(i\)不是"不必要"的条件改写成
\(s_{i + 1}\geq K - a_i\)也即 \(a_i + s_{i + 1}\geq K\)
下面分情况讨论:
- 如果\(a_i + s_{i + 1}\geq K\) 那么在只考虑\(i ~ N\)时\(i\)不是不必要的 所以\(p_i = 0\)
- 如果\(a_i + s_{i + 1} < K\) 那么 \(p_i = p_{i + 1} + 1\)
最后答案为 \(p_1\)
时间复杂度:\(O(NlogN)\)
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll n,k,a[1000000],s,p;
bool cmp(ll x,ll y)
{
return x > y;
}
int main()
{
freopen("unnecessary.in","r",stdin);
freopen("unnecessary.out","w",stdout);
scanf("%lld%lld",&n,&k);
for(ll i = 1;i <= n;i++)
scanf("%lld",&a[i]);
sort(a + 1,a + n + 1,cmp);
for(ll i = 1;i <= n;i++)
{
if(s + a[i] >= k)
p = 0;
else
p += 1,s = s + a[i];
}
cout<<p;
}
C
大意:给定了一个数列 \(A_1,A_2,······,A_n\)
一个区间的权值为:
\(W(L,R)=(R−L+1)×gcd(A_l,...,A_r)\)
求\(W_max\)
考场上
打了个暴力 加了个剪枝
水到了70
**#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
ll gcd(ll a,ll b)
{
return a == 0?b:gcd(b%a,a);
}
ll read()
{
ll f = 1,ans = 0;
char a;
a = getchar();
while(a != '-' && (a >'9'||a <'0'))
a = getchar();
if(a == '-')f = -1,a = getchar();
while(a <= '9' && a >= '0')
ans = (ans << 3) + (ans << 1) + (a - '0'),a = getchar();
return ans * f;
}
ll n,a[100005],ans;
int main()
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
n = read();
for(ll i = 1;i <= n;i++)
a[i] = read();
for(ll l = 1;l <= n;l++)
{
ll g = a[l];
for(ll r = l;r <= n;r++)
{
g = gcd(g,a[r]);
ans = max(ans,g * (r - l + 1));
if(g * (n - l + 1) <= ans)//利用gcd向右扩张只会减少不会增加
break;
}
}
cout<<ans;
}
正解:
有分治的做法 但是这里用\(O(nlog^2n)\)的方法
我们对于一个r 我们知道左边的\(gcd(a[l]...a[r])gcd(a[l]...a[r])\)一定小于等于\(gcd(a[l+1]...a[r])gcd(a[l+1]...a[r])\)
同时对于一个数m 如果他和别的数的\(gcd\)不等于m 则必定小于\(m/2\) 所以最多对于一个区间有\(logn\)种\(gcd\)
所以我们枚举r,用一个队列存储这log个不同的gcd的左端点,每次直接枚举这些gcd更新,更新的时候加一些判断处理重复就好了,写起来很容易。
代码
#include<iostream>
#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
ll n;
queue<int> QAQ;
queue<int> lins;
ll g[100005],ans;
ll gcd(ll x,ll y)
{return (x == 0)? y : gcd(y % x,x);}
int main()
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
scanf("%lld",&n);
g[0] = -1;
for(ll i = 1;i <= n;i++)
{
scanf("%lld",&g[i]);//输入
int last = 0;//上一个gcd段的最后的一位
while(!QAQ.empty())
{
ll x = QAQ.front();
QAQ.pop();
g[x] = gcd(g[x],g[i]);
ans = max(ans,g[x] * (i - x + 1));//计算权值
if(g[x] == g[last])continue;//如果和上一段一样则不必存
lins.push(x);
last = x;
}
ans = max(ans,g[i]);
while(!lins.empty())
{
QAQ.push(lins.front());
lins.pop();
}
if(g[last] != g[i]) QAQ.push(i);
}
printf("%lld\n",ans);
return 0;
}
D
4.1 问题描述
沙都子热衷于制造陷阱。她制造了一个 \(n × n\) 的陷阱,其中 \(n\) 为奇数 奇数。第 i 行
第 j 列的区域的伤害值为 \(a_{i,j}\),如果 \(a_{i,j} < 0\) 则说明这一区域会帮其回血。
但沙都子对这个陷阱不太满意,打算对其进行改造。沙都子太懒,不想一个格
子一个格子改造。令 \(m = (n + 1)/2\),每一次沙都子会将一个 \(m × m\)的连续子
矩形的伤害值都乘上 \(−1\)。沙都子想知道,经过若干次改造后,陷阱所有区域的
伤害值之和最大是多少呢?
4.2 任务描述
4.2.1 输入
第一行,一个正整数 n,保证 n 为奇数。
接下来 n 行,每行 n 个整数 a i,j ,表示初始伤害值。
4.2.2 输出
一行,表示进行若干次改造后,陷阱所有区域的伤害值之和的最大值。
考场上:
被样例误导了 以为这个\(m x m\)的矩阵只会在四周
然后码了很久
最后5分。。。
要注意看题
正解(采用STD)
代码
#include <cstdio>
#include <algorithm>
#define N_MAX 35
#define M_MAX ((N_MAX + 1) / 2)
const int INF = 0x3F3F3F3F;
inline int f(int t, int v) { return t ? -v : v; }
int n, m, i, j, s, t, a[N_MAX + 1][N_MAX + 1], sum, tmp, cur, ans = -INF;
#define FIO "trap"
int main()
{
freopen(FIO ".in", "r", stdin);
freopen(FIO ".out", "w", stdout);
scanf("%d", &n);
for (i = 1; i <= n; ++i)
for (j = 1; j <= n; ++j)
scanf("%d", &a[i][j]);
m = n + 1 >> 1;
for (t = 0; t < 1 << m; ++t)
{
cur = f(t & 1, a[m][m]);
for (j = 1; j < m; ++j)
cur += f(t >> j & 1, a[m][j]);
for (j = 1; j < m; ++j)
cur += f(t & 1 ^ t >> j & 1, a[m][j + m]);
for (i = 1; i < m; ++i)
{
tmp = -INF;
for (s = 0; s < 2; ++s)
{
sum = f(s, a[i][m]) + f(s ^ t & 1, a[i + m][m]);
for (j = 1; j < m; ++j)
sum += std::abs(a[i][j] + f(s, a[i][j + m]) + f(t >> j & 1, a[i + m][j]) + f(s ^ t & 1 ^ t >> j & 1, a[i + m][j + m]));
tmp = std::max(tmp, sum);
}
cur += tmp;
}
ans = std::max(ans, cur);
}
printf("%d\n", ans);
return 0;
}