基科集训总结(9.23-10.5)

集训总结

10a8beef-ac3e-4a9a-9bb4-9e6f23eb0d82

Content

0923比赛小结

A 防签到题(NKOJ P9158)

题目描述

系统中有一个神奇函数:

unsigned func(unsigned n) {
    unsigned x = n & -n;
    return (n + x) | ((n ^ (n + x)) / x) >> 2;
}

但由于C++数据类型的限制,这个函数只适用于32位无符号整数(即unsigned类型)的计算。

你的任务是,实现同样功能但支持k位无符号整数的函数。

数据范围:$2<=k<=64,$ $0<n<2^k$

考点:二进制/模拟

思路

这道题就一个字: ,答案要$mod$ $2^k$,不是将函数直接复制下来就行了,还要注意一下$k=64$的时候要特判一下。函数实现直接用unsigned long long存一下就行了,模拟即可。

Code

#include <cstdio>
#include <algorithm>
#include <cstring>
#define ull unsigned long long
using namespace std;

unsigned func(unsigned n) {
    unsigned x = n & -n;
    return (n + x) | ((n ^ (n + x)) / x) >> 2;
}

ull f(ull n, int k)
{
    n <<= (64 - k);
    n >>= (64 - k);
    return n;
}

int main()
{
//  freopen("anti.in", "r", stdin);
//  freopen("anti.out", "w", stdout);

    int T;
    scanf("%d", &T);
    while (T--)
    {
        ull n;
        int k;
        scanf("%llu %d", &n, &k);
        ull x;
        x = n & -n;
        ull y = f(x + n, k);
        ull z = f(n ^ y, k);
        z = z / x;
        z >>= 2;
        ull ans = f(y | z, k);
        printf("%llu\n", ans);
    }
    return 0;
}

其实题目给出的代码就是一个数 $n$的二进制数中的1个数相等的比它大的第一个二进制排列的数是多少。

心得:当时看到这道题感觉很懵,随便打了一个代码最后只得了30分,不知道怎么做和考的什么,以前也没见过这样类似的题,赛后感觉又很简单,证明还是需要多见多识广。

B 勇往直前(NKOJ P10646)

做法:显然DP

设$f_i$表示经过$i$时而且$i$激活时传送到原来的位置时到$i$的所需的步数。 则转移式为:$f_i=\sum\limits_{j=1}^{i-1}f_j(a_j>b_i)$

意思是在返回的时候把区间内的所有被激活状态又重新加一次。

时间复杂度:$O(n^2)$ 可以看出$f_i$转移的时候可以用前缀和和二分维护,即为$f_i=\sum\limits_{i=1}^{n}f_i(a_i=1)$

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
const int N=2e5+50;
int f[N],p[N],n,m,ans;
struct node{
    int a,b,t;
}x[N];
int s[N];
signed main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)scanf("%lld%lld%lld",&x[i].a,&x[i].b,&x[i].t),p[i]=x[i].a;
    for(int i=1;i<=n;i++){
        f[i]=(x[i].a%mod-x[i].b%mod)%mod;
        int l=upper_bound(p+1,p+1+i,x[i].b)-p;
        f[i]=((f[i]%mod+s[i-1]%mod)%mod-s[l-1]%mod)%mod;
        s[i]=(s[i-1]%mod+f[i]%mod)%mod;
        s[i]%=mod;
        f[i]%=mod;
    }
    ans=m%mod;
    for(int i=1;i<=n;i++){
        if(x[i].t==1){
            ans=(ans%mod+f[i]%mod)%mod;
        }
    }
    cout<<(ans+mod)%mod<<endl;
    return 0;
}

心得

当时我这道题1分也没有得到,这么明显的$dp$我却没想到,问题还是在平时的刷题太少了,需要勤加练习。

C 排列(NKOJ P10703)

做法

先引入一个广为人知的定理(哥德巴赫定理):

任一大于2的整数都可写成两个质数之和。

分两种情况讨论:

$1.n$为偶数 通过枚举尝试$n$以内的质数$i$使得$n-i$也为质数,在依次乘$j$在$mod n$。

$2.n$为奇数$n-1$内用处理偶数的方式处理,剩下的一个数就直接暴力枚举。

但需要注意,$n<8$时需要的特判一下不然会错。

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+50;
int t,n,primes[N],cnt,p[N],used[N];
bool isprime[N];
void euler(){
    memset(isprime,1,sizeof(isprime));
    isprime[1]=1;
    for(int i=2;i<=N;i++){
        if(isprime[i]){
            primes[++cnt]=i;
        }
        for(int j=1;j<=cnt&&primes[j]*i<=N;j++){
            isprime[i*primes[j]]=0;
            if(i%primes[j]==0)break;
        }
    }
}
bool is_prime(int x){
    if(x<=2)return false;
    for(int i=2;i*i<=x;i++){
        if(x%i==0)return false;
    }
    return true;
}
signed main(){
    scanf("%lld",&t);
    euler();
    while(t--){
        scanf("%lld",&n);
        if(n==2){
            cout<<"YES"<<endl;
            cout<<"1 2"<<endl;
            continue;
        }
        if(n==3){
            cout<<"YES"<<endl;
            cout<<"1 2 3"<<endl;
            continue;
        }
        if(n==4){
            cout<<"YES"<<endl;
            cout<<"1 2 3 4"<<endl;
            continue;
        }
        if(n==5){
            cout<<"YES"<<endl;
            cout<<"5 2 1 4 3"<<endl;
            continue;
        }
        if(n==6){
            cout<<"YES"<<endl;
            cout<<"6 5 2 1 4 3"<<endl;
            continue;
        }
        if(n==7){
            cout<<"YES"<<endl;
            cout<<"7 6 5 2 1 4 3"<<endl;
            continue;
        }
        int p1=0;
        if(n%2==0){
            for(int i=2;i<=n;i++)if(isprime[i]&&isprime[n-i]){p1=i;break;}
            cout<<"YES"<<endl;
            for(int i=1;i<=n;i++)p[i]=(i*p1)%n+1;
            for(int i=1;i<=n;i++)printf("%lld ",p[i]);
            printf("\n");
        }else{
            memset(used,0,sizeof(used));
            n--;
            for(int i=2;i<=n;i++)if(isprime[i]&&isprime[n-i]){p1=i;break;}
            cout<<"YES"<<endl;
            for(int i=1;i<=n;i++)p[i]=(i*p1)%n+1,used[p[i]]=1;
            int cntt=0,tot=0;
            for(int j=2;j<=n-1;j++){
                if(is_prime(p[j-1]+n+1)||is_prime(abs(p[j-1]-n-1))){
                    if(is_prime(n+1+p[j])||is_prime(abs(p[j]-n-1))){
                        tot=j-1;
                    }
                }
            }
            if(is_prime(n+1+p[1])||is_prime(n+1-p[1]))tot=0;
            if(is_prime(n+1+p[n])||is_prime(n+1-p[n]))tot=n;
            if(tot==0)printf("%lld ",n+1);
            for(int i=1;i<=n;i++){
                if(i==tot)printf("%lld %lld ",p[i],cntt);
                else printf("%lld ",p[i]);
            }
            if(tot==n)printf("%lld ",n+1);
            printf("\n");
        }
    }
    return 0;
}

心得

遇到构造体,首先不要慌,先打SPJ,在找规律,多刷题,就一定可以把它做出来

D 排版

思路 分三种情况讨论,在图片的上面;在图片的左右;在图片的下面,用倍增来做

代码

#include <algorithm>
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,m,W,pw,lw,rw,a[N],sum[N],f[N][25];
int g[N][25],maxlen;
int midit(int sta,int lim){
    if(sta>n || !lim)return 0;
    int l=sta,r=n,mid,ret=l-1;
    while(l<=r){
        mid=(l+r)>>1;
        if(sum[mid]-sum[sta-1]+mid-sta<=lim)
            ret=mid,l=mid+1;
        else r=mid-1;
    }
    return ret-sta+1;
}
void prework(){
    int x,y;
    for(int i=1;i<=n;i++){
        f[i][0]=midit(i,W);
        x=midit(i,lw);y=midit(i+x,rw);
        g[i][0]=x+y;
    }
    for(int j=1;j<=maxlen;j++){
        for(int i=1;i<=n;i++){
            if(i+f[i][j-1]<=n)
                f[i][j]=f[i][j-1]+f[i+f[i][j-1]][j-1];
            else f[i][j]=N;
            if(i+g[i][j-1]<=n)
                g[i][j]=g[i][j-1]+g[i+g[i][j-1]][j-1];
            else g[i][j]=N;
            if(i+f[i][j]-1>n)f[i][j]=N;
            if(i+g[i][j]-1>n)g[i][j]=N;
        }
    }
}
int solve(int s,int d){
    int res=n,x=1,ans=0,tot=s-1;
    for(int i=maxlen;i>=0;i--){
        if(tot>=(1<<i) && res>=f[x][i] && x<=n)
            tot-=(1<<i),res-=f[x][i],x+=f[x][i],ans+=(1<<i);
    }
    int pre=ans+d;
    if(!res)return pre;
    tot=d;
    for(int i=maxlen;i>=0;i--){
        if(tot>=(1<<i) && res>=g[x][i] && x<=n)
            tot-=(1<<i),res-=g[x][i],x+=g[x][i],ans+=(1<<i);
    }
    ans=max(pre,ans);
    if(!res)return ans;
    for(int i=maxlen;i>=0;i--){
        if(res>=f[x][i] && x<=n)
            res-=f[x][i],x+=f[x][i],ans+=(1<<i);
    }
    return ans;
}
void work()
{
    scanf("%d%d%d%d",&n,&W,&lw,&pw);
    maxlen=log(n)/log(2)+1;rw=W-pw-lw;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
    prework();
    int Q,x,y;cin>>Q;
    while(Q--){
        scanf("%d%d",&x,&y);
        printf("%d\n",solve(x,y));
    }
}

int main()
{
    work();
    return 0;
}

心得

对于以前学过的算法需要及时复习,要不到时考的时候又不会,也要对题目有基本的直觉。

10.5比赛小结

A题 数三角形

做法:

考虑到直接求取不好求,所以我们考虑另外一种方式:正难则反

首先我们可以知道 总三角形的个数为 $C_{n}^{3}$,就是求不是纯三角形的个数

怎么求呢?考虑对于每个点,都有$n-1$条边,不是纯三角形的个数为蓝色的边的个数在乘红色的边的个数。

所以直接求解即可。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
char s[5005][5005];
int cnt1[5005],cnt2[5005],ans1,ans2,ans;
signed main(){
    scanf("%lld",&n);
    int ans2=(n-1)*(n-2)*n/6;
    for(int i=1;i<=n-1;i++){
        scanf("%s",s[i]+1+i);
        for(int j=i+1;j<=n;j++){
            if(s[i][j]=='1')cnt1[i]++,cnt1[j]++;
            else if(s[i][j]=='0')cnt2[i]++,cnt2[j]++;
        }
        ans1+=cnt1[i]*cnt2[i];
    }
    ans1+=cnt1[n]*cnt2[n];
    printf("%lld\n",ans2-ans1/2);
    return 0;
}

B题 翻转

题目大意

给定一个$n*m$的矩阵$A$,$A_i,_j$ $\in$ $ 0,1$,求把序列改成全0矩阵的最小步数。

做法

考虑贪心,每次选最后一个非0数进行翻转即可

C题 果果系统

模拟即可

D题 凸

不会

posted @ 2023-10-08 20:19  CQWYB  阅读(7)  评论(0编辑  收藏  举报  来源