牛客NOIP暑期七天营-TG3 赛后题解

牛客NOIP暑期七天营-提高组3

比赛链接

官方题解

A-破碎的矩阵

题解

标签:推结论+快速幂

挺妙的一道题??比赛的时候想到了但不敢确定正确性。

首先很容易想到,对于一个二进制数它的每一位都是独立的。那么下面的结论对于\(x=1\)\(x=2^{30}-1\)\(2^{60}-1\)这三组都同样适用。

大致思路就是对于一个\(n*m\)的矩阵,它左上角的子矩阵\((n-1)*(m-1)\)先随便怎么填,剩下的第\(n\)行,第\(m\)列根据剩余的异或值来进行调整,那么对于子矩阵中的任意一组填法,剩下的一列一行都有且仅有一组相应的填法。那么最终答案就为\(ans=(x+1)^{(n-1)*(m-1)}\),底是\(x+1\)是因为对于子矩阵来说,每个元素的取值范围为\([0,x]\),共\(x+1\)个数。

当然大前提是该组数据有解。如何判呢,只要把每行的值异或起来,把每列的值异或起来,若两者不相等则无解,输出0。

则总的时间复杂度为\(O(case*log(nm))\)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
ll x,mod;
ll ksm(ll r,ll d){
    ll res=1;
    while(d){
        if(d&1)res=res*r%mod;
        r=r*r%mod;d>>=1;
    }
    return res;
}
int main(){
    int T;scanf("%d",&T);
    while(T--){
        scanf("%d%d%lld%lld",&n,&m,&x,&mod);
        ll sx=0,sy=0;
        for(int i=1;i<=n;i++){
            ll w;scanf("%lld",&w);
            sx^=w;
        }
        for(int i=1;i<=m;i++){
            ll w;scanf("%lld",&w);
            sy^=w;
        }
        if(sx!=sy){puts("0");continue;}
        ll ans=ksm((x+1)%mod,1ll*(n-1)*(m-1));
        printf("%lld\n",ans);
    }
}

B-点与面

题解

标签:树状数组裸题

就是计个数的事,感觉难度比第一题简单很多。。

维护4个树状数组,然后循环一遍边计数边往上加就完事了,注意顺序应该是从\(5->4->3->2->1\)不然可能会出现重复计数。

时间复杂度为\(O(NlogN)\)

代码

#include<bits/stdc++.h>
using namespace std;
#define debug(x) cout<<"### "<<x<<endl;
#define For(a,b,c) for(int a=b;a<=c;++a)
#define Dor(a,b,c) for(int a=b;a>=c;--a)
typedef long long ll;
const int N=1e5+10;
const ll mod=998244353;
inline int read(){
    int x=0;char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x;
}
int n,num,a[N],b[N];
ll c[5][N];
inline ll askA(int x,int id){
	ll res=0;
	while(x){
		res+=c[id][x],res%=mod;
		x-=x&(-x);	
	}
	return res;
}
inline ll askB(int x,int id){
	ll res=0;
	while(x<=num){
		res+=c[id][x],res%=mod;
		x+=x&(-x);
	}
	return res;
}
inline void addA(int x,ll d,int id){
	while(x<=num){
		c[id][x]+=d,c[id][x]%=mod;
		x+=x&(-x);
	}
}
inline void addB(int x,ll d,int id){
	while(x){
		c[id][x]+=d,c[id][x]%=mod;
		x-=x&(-x);
	}	
}
int main(){
	n=read();
	for(int i=1;i<=n;i++)a[i]=read(),b[i]=a[i];	
	sort(b+1,b+n+1);
	num=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+num+1,a[i])-b+1;
	num++;
	ll ans=0;
	for(int i=1;i<=n;i++){
		//5
		ll tmp=askA(a[i]-1,4)%mod;
		ans+=tmp,ans%=mod;
		//4
		tmp=askB(a[i]+1,3)%mod;
		addA(a[i],tmp,4);
		//3
		tmp=askA(a[i]-1,2)%mod;
		addB(a[i],tmp,3);
		//2
		tmp=askB(a[i]+1,1)%mod;
		addA(a[i],tmp,2);
		//1
		addB(a[i],1,1);
	}	
	printf("%lld\n",ans%mod);
}

C-信息传递

题解

标签:断环为链+线段树区间维护最值+倍增

算法一:

适用于:60%数据,\(li,ri<=n<=10^3\)

构图:很容易想到将问题转化为图上的最短路。对于某个人\(i\),它的传播范围为\([li,ri]\),我们由\(i\)向这个区间的所有点连一条边;

计算:对于每个点\(i\),我们以其为源点跑一遍Dijkstra求出单源最短路,然后再枚举n个点,找出其中最远的距离\(dis_j\),这个值就是答案。

时间复杂度为\(O(N^2logN)\)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
bool nc1;
inline int read(){}
int id[2*N],n;
namespace p60{
    bool f[1010][1010];
    int dis[1010],vis[1010];
    vector<int>e[1010];
    struct node{
        int w,now;
        inline bool operator <(const node &x)const{
            return w>x.w;
        }
    };
    priority_queue<node>q;
    void dijkstra(int s){
        for(register int i=1;i<=n;i++)dis[i]=n,vis[i]=0;
        dis[s]=0;
        q.push((node){0,s});
        while(!q.empty()){
            node x=q.top();
            q.pop();
            int u=x.now;
            if(vis[u])continue;
            vis[u]=1;
            for(register int i=0;i<e[u].size();i++){
                int v=e[u][i];
                if(dis[v]>dis[u]+1){
                    dis[v]=dis[u]+1;
                    q.push((node){dis[v],v});
                }
            }
        }
    }
    void solve(){
        for(register int i=1;i<=n;i++){
            int l=read();
            for(register int j=i+n-1;j>=i+n-l;j--)if(!f[id[i]][id[j]]){
                f[id[i]][id[j]]=1;
                e[id[i]].push_back(id[j]);
            }
        }
        for(register int i=1;i<=n;i++){
            int r=read();
            for(register int j=i+1;j<=i+r;j++)if(!f[id[i]][id[j]]){
                f[id[i]][id[j]]=1;
                e[id[i]].push_back(id[j]);
            }  
        }
        for(register int i=1;i<=n;i++){
            int ans=0;
            dijkstra(i);
            for(register int j=1;j<=n;j++)if(dis[j]>ans)ans=dis[j];
            printf("%d ",ans);
        }
    }
}
int li[N],ri[N];
bool nc2;
int main(){
    n=read();
    for(int i=1;i<=n;i++)id[i]=id[i+n]=i;
    if(n<=1000){
        p60::solve();
        return 0;
    }
}

算法二:

适用于:100%数据,\(li,ri<=n<=10^5\)

首先断环为链,开三倍数组。

我们发现对于每一秒的传递,\(i\)能传到的最左边的人要么不变要么更左,\(i\)能传到的最右边的人要么不变要么更右。并且,[最左的人,最右的人]这一段区间里的所有人在此时都已经收到消息了。

根据这个单调性,我们可以维护两个数组\(li[x][i],ri[x][i]\),表示在\(2^i\)秒时,\(x\)能传递到的最左最右的人,看出用了倍增吧,那么询问时也可以利用倍增跳来快速找出答案。

接下来的任务就是如何维护呢——官方题解中给出了ST表维护最值,下面的代码中用的是线段树。

对于树上的节点\(o\)我们维护两个数组\(b[o][t][0/1]\),表示在\(2^t\)秒内节点\(o\)包括的范围\([l,r]\)中,能跳的最左,能跳的最右。

接下来一个时间一个时间的转移维护,查询时也是在树上进行的。

总的时间复杂度为\(O(Nlog^2N)\)

详见代码8:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x=0;char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x;
}
const int N=3e5+10;
int n,li[N][18],ri[N][18];
int b[N<<2][18][2];
int limL,limR,k,len;
void build(int o,int l,int r){
    if(l==r){
        b[o][k][0]=li[l][k];
        b[o][k][1]=ri[l][k];
        return;
    }
    int mid=l+r>>1;
    build(o<<1,l,mid),build(o<<1|1,mid+1,r);
    b[o][k][0]=min(b[o<<1][k][0],b[o<<1|1][k][0]);
    b[o][k][1]=max(b[o<<1][k][1],b[o<<1|1][k][1]);
}
void query(int o,int l,int r,int ql,int qr){
    if(ql<=l&&r<=qr){
        limL=min(limL,b[o][k][0]);     
        limR=max(limR,b[o][k][1]);
        return;
    }
    int mid=l+r>>1;
    if(ql<=mid)query(o<<1,l,mid,ql,qr);
    if(qr>mid)query(o<<1|1,mid+1,r,ql,qr);
}
int main(){
    n=read();len=3*n;
    for(register int i=1;i<=n;i++){
        int x=read();
        for(register int j=i;j<=len;j+=n)li[j][0]=max(1,j-x);
    }
    for(register int i=1;i<=n;i++){
        int x=read();
        for(register int j=i;j<=len;j+=n)ri[j][0]=min(3*n,j+x);
    }
    if(n==1){puts("0");return 0;}
    build(1,1,len);
     
    for(k=1;k<=17;k++){
        for(register int i=1;i<=len;i++){
            limL=1e9,limR=0;
            k--;
            query(1,1,len,li[i][k],ri[i][k]);  
            k++;
            li[i][k]=limL,ri[i][k]=limR;
        }
        build(1,1,len);
    }
 
    for(register int i=n+1;i<=n+n;i++){
        int l=i,r=i,ans=0;
        for(k=17;k>=0;k--){
            limL=1e9,limR=0;
            query(1,1,len,l,r);
            if(limR-limL+1>=n)continue;
            l=limL,r=limR,ans+=1<<k;
        }
        printf("%d ",ans+1);
    }
}
posted @ 2019-08-21 16:48  real_lyb  阅读(161)  评论(0编辑  收藏  举报