2021“MINIEYE杯”中国大学生算法设计超级联赛(1)

2021“MINIEYE杯”中国大学生算法设计超级联赛(1)

1001:Mod, Or and Everything

1001题面

思路:

通过找规律发现,当每次%(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

1005题面

思路:

当质数直接连接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

1006题面

思路:

通过01trie树实现异或前缀和,插入的时候遍历更新mx数组中存储的每个节点的位置,使所有节点的位置保持在最右端,保证区间最小。然后同时计算区间异或和是否大于等于k,是的话在比较更新答案。

代码:

心得:

我可真菜呀


1008:Maximal submatrix

1008题面

思路:

通过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

1009题面

思路:

类似于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;
}
心得:

大佬的思路真神奇

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值