2021“MINIEYE杯”中国大学生算法设计超级联赛(1)
1001:Mod, Or and Everything
思路:
通过找规律发现,当每次%(n/2+1)时的余数h=n-(n/2+1)是最大的,且比它小的余数会在后面一次出现,即“bitwise OR”后二进制的每一位都会被赋值为1,所以只需计算出h的二进制位数并令每一位都为1即可得出答案。
代码:
#include<iostream>
using namespace std;
int main(){
int T;
cin>>T;
long long n;
while(T--){
scanf("%lld",&n);
long long h=n-(n/2+1);
int cnt=0;
while(h){
cnt++;
h/=2;
}
long long ans=0;
long long hh=1;
for(int i=0;i<cnt;i++){
ans+=hh;
hh*=2;
}
cout<<ans<<endl;
}
return 0;
}
心得:
尽量找统一的规律
1005:Minimum spanning tree
思路:
当质数直接连接2、合数和自己的因数相连的时候,树的边权之和最小。所以最小答案=所有质数*2+所有合数。考虑到时间复杂度,这题要用欧式筛(线性质数筛)。
线性筛:质数的倍数一定是合数,所以就可以每次找到一个质数后就将它的倍数全部标记为合数,这样最后没有被标记的就是质数了。但这样,每个合数就被标记了很多次,如果我们使每个数只会被标记一次,即每个数我们仅在枚举到它的最小素因子时进行标记,那么就可以显著地优化筛法的时间复杂度。
代码:
#include<iostream>
using namespace std;
const int N=10000005;
int p[N],pp[N];
long long sum[10000005];
void P(){ //预处理线性质数筛
long long cnt=0;
p[0]=p[1]=1;
for(int i=2;i<N;i++){
if(!p[i]){
pp[cnt++]=i;
}
for(int j=0;j<cnt;j++){
if(i*pp[j]>=N)
break;
p[i*pp[j]]=1;
if(i%pp[j]==0)
break;
}
}
}
void S(){ //预处理前缀和
for(int i=3;i<N;i++){
if(!p[i])
sum[i]=sum[i-1]+i*2;
else
sum[i]=sum[i-1]+i;
}
}
int main(){
int T;
cin>>T;
P();S();
int n;
while(T--){
scanf("%d",&n);
cout<<sum[n]<<endl;
}
return 0;
}
心得:
板子还是要时常复习一下的。
1006:Xor sum
思路:
通过01trie树实现异或前缀和,插入的时候遍历更新mx数组中存储的每个节点的位置,使所有节点的位置保持在最右端,保证区间最小。然后同时计算区间异或和是否大于等于k,是的话在比较更新答案。
代码:
心得:
我可真菜呀
1008:Maximal submatrix
思路:
通过h数组储存在从上到下每列中连续出现非递减数列的数列长度,然后就可以把题目中数组的每一行都转化为一个单调栈的模板题:直方图中最大的矩形 。如果一个数大于栈顶元素,那么它一定满足大于栈内的所有元素。如果进来一个新数,这个数是小于栈顶元素的,那么就将栈顶弹出,直到栈内元素为空或者栈顶元素小于此元素,每次弹出一个高度就计算一下面积,取最大值。
代码:
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2005;
int n,m;
int a[N][N];
int h[N][N];
int st[N*N],wi[N*N];
int main(){
int T;
cin>>T;
while(T--){
memset(h,0,sizeof(h));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i-1][j]<=a[i][j])
h[i][j]=h[i-1][j]+1;
else
h[i][j]=1;
}
}
for(int i=1;i<=n;i++)
h[i][m+1]=0;
long long ans=0;
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m+1;j++){
if(h[i][j]>st[cnt]){
st[++cnt]=h[i][j];
wi[cnt]=1;
}else{
int width=0;
while(st[cnt]>h[i][j]){
width+=wi[cnt];
ans=max(ans,(long long)st[cnt]*width);
cnt--;
}
st[++cnt]=h[i][j];
wi[cnt]=width+1;
}
}
}
cout<<ans<<endl;
}
return 0;
}
心得:
比赛的时候还是不太敢想啊,还好队友nb
1009:KD-Graph
思路:
类似于Kruskal算法的思想,将边权从小到大排序然后不断取。
若取出的边的两端点在同一个集合则无事;
若不在同一个集合且合并前连通块数cnt > k + 1则正常合并同时cnt–;
若不在同一个集合且合并后连通块数cnt == k + 1说明合并完这条边后连通块数为k且满足所有块内的边<=D,因此可以暂时令D = 该边权,同时合并以及cnt–;
若不在同一个集合且合并后连通块数cnt < k + 1,此时需要判断该边权是否<=D,若是说明取当前的D会让一些权值<=D的边在连通块外,显然这个D是不合法的,但如果不取这个D的话分出来的连通块数必然小于k,因此此时就可以判断无解了。
代码:
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,k;
int fa[500005];
struct Node{
int x,y,z;
}node[500005];
bool cmp(Node a,Node b){
return a.z<b.z;
}
int find(int x){
if(fa[x]!=x)
fa[x]=find(fa[x]);
return fa[x];
}
int main(){
int T;
cin>>T;
while(T--){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&node[i].x,&node[i].y,&node[i].z);
for(int i=1;i<=n;i++)
fa[i]=i;
sort(node+1,node+1+m,cmp);
int cnt=n;
int d=0x3f3f3f3f;
int pd=1;
if(k==n){
d=0;
}
for(int i=1;i<=m;i++){
if(d==0)
break;
int x=node[i].x,y=node[i].y,z=node[i].z;
int fx=find(x),fy=find(y);
if(fx==fy)
continue;
else{
if(cnt==k+1)
d=z;
else if(cnt<k+1){
if(z<=d)
pd=0;
break;
}
fa[fx]=fy;
cnt--;
}
}
if(!pd||d==0x3f3f3f3f)
cout<<"-1"<<endl;
else
cout<<d<<endl;
}
return 0;
}
心得:
大佬的思路真神奇