暑假集训0720

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

1001:Mod, Or and Everything

1001题面
思路:

通过找规律发现,当每次%(n/2+1)时的余数h=n-(n/2+1)是最大的,且比它小的余数会在后面一次出现,即“bitwise OR”后二进制的每一位都会被赋值为1,所以只需计算出h的二进制位数并令每一位都为1即可得出答案。

代码:
#include
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
心得:

尽量找统一的规律


1005:Minimum spanning tree

1005题面
思路:

当质数直接连接2、合数和自己的因数相连的时候,树的边权之和最小。所以最小答案=所有质数*2+所有合数。考虑到时间复杂度,这题要用欧式筛(线性质数筛)。
线性筛:质数的倍数一定是合数,所以就可以每次找到一个质数后就将它的倍数全部标记为合数,这样最后没有被标记的就是质数了。但这样,每个合数就被标记了很多次,如果我们使每个数只会被标记一次,即每个数我们仅在枚举到它的最小素因子时进行标记,那么就可以显著地优化筛法的时间复杂度。

代码:
#include
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)
                break;
            p[i*pp[j]]=1;
            if(i%pp[j]==0)
                break;
        }
    }
}
void S(){ //预处理前缀和
    for(int i=3;i>T;
    P();S();
    int n;
    while(T--){
        scanf("%d",&n);
        cout<
心得:

板子还是要时常复习一下的。


1006:Xor sum

1006题面
思路:

01trie树 暂时还不太会啊,后面补。。

代码:

心得:

我可真菜呀


1008:Maximal submatrix

1008题面
思路:

通过h数组储存在从上到下每列中连续出现非递减数列的数列长度,然后就可以把题目中数组的每一行都转化为一个单调栈的模板题:直方图中最大的矩形 。如果一个数大于栈顶元素,那么它一定满足大于栈内的所有元素。如果进来一个新数,这个数是小于栈顶元素的,那么就将栈顶弹出,直到栈内元素为空或者栈顶元素小于此元素,每次弹出一个高度就计算一下面积,取最大值。

代码:
#include
#include
#include
#include
#include
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<
心得:

比赛的时候还是不太敢想啊,还好队友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
#include
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>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
心得:

大佬的思路真神奇

你可能感兴趣的:(暑假集训0720)