2024昆明邀请赛做题补题记录
观前提醒,本题解仅针对本人写过的题,题目思路仅供参考
若有新思路欢迎分享
G
题目描述
题解
首先观察样例可知\(n==1\)时肯定无解,数组只有\(0\)元素
之后来到\(n==2\),这时,[\(1\) \(0\)]数组便构造成了符合题意的满足各个前缀异或和不为\(0\)的数组
接下来看看\(n==3\),若要构造最小字典序的符合题目要求的数组,那么只要在\(n==2\)的数组追加\(2\)就可以了
然后观察\(n==3\)时数组的总异或和,会发现已经成为\(3\)了,到\(n==4\)的时候只有\(3\)没有添加,如果添加\(3\),那么数组的总异或和为0,不符合题目要求,输出"impossible"
那么当\(n==5\)的时候怎么办呢?这时候我们需要利用异或的性质————相同的数字异或和是\(0\),既然我们已经知道\(n==3\)的情况下数组总异或和是3,对于\(n==5\)的情况下,我们有\(4\)和\(3\)两个数字可以填充
这时我们为了满足需求选择先填充\(4\)再填充\(3\),此时形成数组[\(1\) \(0\) \(2\) \(4\) \(3\)],可以证明这个数组是可行的
于是根据上述推导我们可以多列举几个\(n\)的不同取值,结果会发现当\(n \geq 2\)时并且当\(n \mod 4 ==0\)时或者当\(n==1\)时,总是无法构成满足题意的数组的,输出"impossible"即可
那么对于其他\(n\)的情况我们该如何构造数组呢?
这时候我们根据前序的分析以及前面所要求的打表找规律我们可以知道,当前缀异或值是\(3,7,11,15,19,23...\)(我们称其为临界数)时,我们会发现这里对应的数组往后推两个数组需要做如\(n==5\)样式的变换
(如果你能观察出来的话,会发现这些临界数所对应的数组的后一个数组正好对应\(n \mod 4==0\)的情况,并且这些临界数恰好构成了以\(3\)为首项,\(4\)为公差的等差数列)
那么对于给定的\(n\),我们所需要构造数组的思路就清晰了.我们设置一个循环,当循环进行到临界点的下一位时进行变换即可
时间复杂度是\(O(n)\),对于\(n==1e6\)来说这是可以接受的
示例代码
#include<bits/stdc++.h>
#define push_back push_back
#define elif else if
using namespace std;
void solve()
{
int n;
cin>>n;
if(n==1||n%4==0)
{
cout<<"impossible"<<endl;//这里是不可能的情况
return;
}
vector<int> a={1,0};//初始构造数列
for(int i=3;i<=n;i++)
{
if(i%4==0)//注意,这里我的循环是1-based,所以这样写并没问题
{
a.push_back(i);
}
elif(i%4==1)
{
a.push_back(i-2);
}
//看不懂请自行转换0-based使用捏
else
{
a.push_back(i-1);
}
}
for(int i=0;i<n;i++)
{
cout<<a[i]<<' ';
}
cout<<endl;
}
int main()
{
int t;
cin>>t;
while(t--)
{
solve();
}
}
B
题目描述
题解
简单的贪心题,要使得为每场比赛所被分配到的队伍而得到的金牌数最多,那么就尽量使得原先不能整除\(k\)的\(a_i\),在加上所分配的名额之后变得能够整除\(k\).这时只需要遍历一般数组\(a\)去计算每个比赛所需要的队伍的数量,使得其变得能够整除\(k\).若使得金牌数量足够多那就优先分配需求队伍数量较少的————这样可以在较小的代价下得到更多的金牌
时间复杂度是O(n),对于\(n==100\)是完全可行的
示例代码
#include<bits/stdc++.h>
using namespace std;
void solve()
{
long long int n,k;
cin>>n>>k;
vector<long long> a(n+5);
vector<long long> need;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
long long m;
cin>>m;
long long sum=0;
for(int i=1;i<=n;i++)
{
sum+=a[i]/k;
long long base=a[i]%k;
if(base==0)
{
need.push_back(k);
}
else
{
need.push_back(k-base);
}
}
sort(need.begin(),need.end());
for(auto x:need)
{
if(x<=m)
{
m-=x;
sum++;
}
else
{
break;
}
}
sum+=m/k;
cout<<sum<<endl;
}
int main()
{
int t;
cin>>t;
while(t--)
{
solve();
}
}
E
题目描述
题解
这道题不涉及打表什么的,所以题解尽量简短干练
首先先注意本题要改变什么————\(i \in [l,r]\)的所有\(a_i\)的区间需要改变,那么,整个数组关于此区间的补集所在的区间的最大公因数便不会改变
这时候,我们就想到了最朴素的做法————暴力枚举区间,然后去计算对应区间内的最大公因数在加\(k\)后以及它的前面对应的区间的最大公因数的变化还有它的后面的对应的区间的最大公因数的变化。但是,本题的\(n\)最大达到了\(3e5\),而我们上述所描述的办法会达到预计为\(O(n^2)\)的时间复杂度,对于这道题来说时间复杂度过大了,显然不行。那么,我们应当去思考如何优化上述思路,以减少程序预计运行时间呢?
注意到如果对应的前缀区间的最大公因数与前一个前缀区间的最大公因数相等的话,那么,对于这个区间的右端点所对应的数加上\(k\)的话,不会贡献一个新的质数,所以这样的剪枝是合理的,所以根据最大公因数的性质,就会有\(gcd==(gcd(a[1->l-1]),gcd(a[l->r]),gcd(a[r+1->n]))\),所以处理好前缀总公因数和后缀总公因数之后开始枚举区间就行了
本题的时间复杂度是\(O(n^2)\),但是由于进行了适当剪枝,所以这个方法是可行的
示例代码
#include<bits/stdc++.h>
using namespace std;
void solve()
{
long long n,k;
cin>>n>>k;
vector<long long> a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<long long> b(n+1,0);
vector<long long> c(n+2,0);
for(int i=1;i<=n;i++)
{
b[i]=__gcd(b[i-1],a[i]);
}
for(int i=n;i>=1;i--)
{
c[i]=__gcd(c[i+1],a[i]);
}
long long ans=b[n];
for(int i=1;i<=n;i++)
{
if(b[i]==b[i-1])
{
continue;
}
long long s=b[i-1];
for(int j=i;j<=n;j++)
{
s=__gcd(s,a[j]+k);
ans=max(ans,__gcd(s,c[j+1]));
}
}
cout<<ans<<endl;
}
int main()
{
int t;
cin>>t;
while(t--)
{
solve();
}
}
未完待续

浙公网安备 33010602011771号