越到后来就发现,CDE题对博主的提升更大,而且博主纵观CSDN,几乎没人写E题题解,写A到D的居多,为了提高阅读量以及博客水平,以后就只更新CDE题目的题解了,并且会陆续把之前大约50场div2的题目都更新。。
C题题意:在一个5维坐标系中,如果一个点与其他任意两个点组成的两个向量夹角小于90度,则该点被称为bad,给你n个点的坐标,问有几个good点。
思路:直接三层for循环,如果夹角小于90度,则直接break,如果大于等于90度,则另外两个点不需要考虑了。
#include<iostream>
using namespace std;
const int maxn = 1005;
struct node
{
int a, b, c, d, e;
}nodes[maxn];
bool vis[maxn];
int main()
{
int n;
cin >> n;
for (int i = 1;i <= n;i++)
{
scanf("%d%d%d%d%d", &nodes[i].a, &nodes[i].b, &nodes[i].c, &nodes[i].d, &nodes[i].e);
}
long long temp;
int cnt = 0;
int ans[maxn];
for (int i = 1;i <= n;i++)if (!vis[i])
{
bool flag = true;
for(int j=1;j<=n;j++)if(j!=i)
for (int k = 1;k <= n;k++)if(k!=i&&k!=j)
{
temp = (nodes[j].a - nodes[i].a)*(nodes[k].a - nodes[i].a) + (nodes[j].b - nodes[i].b)*(nodes[k].b - nodes[i].b) + (nodes[j].c - nodes[i].c)*(nodes[k].c - nodes[i].c) + (nodes[j].d - nodes[i].d)*(nodes[k].d - nodes[i].d) + (nodes[j].e - nodes[i].e)*(nodes[k].e - nodes[i].e);
if (temp >0)
{
k = n + 1;
j = n + 1;
flag = false;
}
else
{
vis[j] = 1;
vis[k] = 1;
}
}
if (flag)
{
cnt++;
ans[cnt] = i;
}
}
cout << cnt << endl;
for (int i = 1;i <= cnt;i++)
cout << ans[i] << endl;
return 0;
}
这题其实有一个结论,即n如果大于11个,则没有good点,即输出0.稍后会带来证明。。
D题题意:给你一个序列如果这个序列是非空并且gcd为1则该序列被称为bad,现在有两个操作,1是将序列中的一个元素删掉,需花费x,2是将一个元素值加1,花费y,可以对同一个元素操作多次。
问最小花费是多少可以使序列变成good。
思路:枚举序列的gcd,然后将所有不能整除它的元素进行1操作或者2操作(贪心),这个思路没错,但是元素个数有5*10^5,枚举gcd的范围也达到了10^6,可想而知,肯定TLE。这里就要进行一点脑筋急转弯。
假如说,现在枚举的gcd为k,现在要对a这个元素操作,操作1的花费为x,而操作2的花费为(a-k)*y。操作2的花费小于1时的不等式为
设x/y为p,则该不等式可变成a-k<=p(除法向下取整之后就从小于变成了小于等于了,想一想为什么)。所以从这个式子看出,我们只需要以元素和k的差值来进行判断选哪个操作了,这时候就可以用前缀和来优化了。
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2000006;
ll s[maxn], sum[maxn];
ll n, x, y;
int main()
{
cin >> n >> x >> y;
int p;
p = x / y;
ll temp;
for (int i = 1;i <= n;i++)
{
scanf("%lld", &temp);
s[temp]++;
sum[temp] += temp;
}
for (int i = 1;i <= 2000000;i++)
{
s[i] += s[i - 1];
sum[i] += sum[i - 1];
}
ll ans = n*x;
for (int i = 2;i <= 1000000;i++)//以i为gcd
{
ll temp = 0;
for (int j = i;j <=i+1000000&&temp<ans;j += i)//分块来计算
{
int k = max(j - i + 1, j - p);
temp +=1LL*((s[j] - s[k - 1])*j - (sum[j] - sum[k - 1]))*y;
temp += 1LL * (s[k - 1] - s[j - i])*x;
}
ans = min(ans, temp);
}
cout << ans << endl;
return 0;
}
读者注意循环的区间,为啥第二个循环是i+1000000,因为有些点将其变成i+1000000的代价更低,这也是为什么maxn会开比题意大两倍的空间,因为要计算元素到i+1000000的代价,而i最大为1000000,所以要开2倍。这些小细节希望读者注意。
E题题意:Mojtaba和Arpa玩游戏,有一个序列,当一个人操作时,需要选择一个素数p,和k组成p^k,然后序列中必须有可整除它的数,将这些可以整除它的数除以它。如果一个人不能找出p和k,则代表他输了。
思路:第一次写这么难的博弈论的题,搞了半天才差不多明白了。直接讲做法吧,其实这场游戏可以分成若干个子游戏,即每个素数可以单独考虑胜负,然后用dfs计算sg函数值,判断该状态是必胜态还是必败态。然后将所有子游戏状态合并,看最终是谁胜利。
如何dfs,如何计算sg?,合并关系是怎么样的?
大家可以看代码自己思考思考。
#include<iostream>
#include<map>
#include<math.h>
using namespace std;
map<int, int>M, sg;
int dfs(int num)
{
if (!num)return 0;
if (sg.find(num) != sg.end())return sg[num];
int mx;
for(int i=30;i>=0;i--)
if(num>>i&1)
{
mx = i;break;
}
int vis = 0;
int temp;
for (int i = 1;i <= mx;i++)
{
temp = ((num >> (i + 1)) << 1) | (num &((1<<i)-1));
vis |= (1 << dfs(temp));
}
for (int i = 0;i <= 30;i++)
if ((~vis >> i) & 1)
return sg[num] = i;
}
int main()
{
int n;
cin >> n;
int x;
for (int i = 1;i <= n;i++)
{
scanf("%d", &x);
for (int j = 2;j*j<=x;j++)
{
int cnt = 0;
while (x%j==0)
{
cnt++;
x /= j;
}
if (cnt != 0)
M[j] |= (1 << cnt);
}
if (x != 1)
M[x] |= (1<<1);
}
int sum = 0;
for (auto it = M.begin();it != M.end();it++)
{
sum ^= dfs(it->second);
}
if (!sum)
puts("Arpa");
else
puts("Mojtaba");
return 0;
}
其中有许多位运算,希望读者能自主弄清楚其中的含义,毕竟博主也是这么搞懂了。。。
如真遇到难点,欢迎留言。