牛客OI月赛12-提高组题解

牛客OI月赛12-提高组

当天晚上被\(loli\)要求去打了某高端oj部分原创的模拟赛,第二天看了牛客的题觉得非常清真,于是就去写了

不难发现现场写出\(260\text{pts}\)并不需要动脑子,而且\(260\text{pts}\)甚至还有\(rk2\),感觉没打非常吃亏

A.小w的进制转换

大概理解一下就是询问\(1\)\(n\)里有多少个数的二进制表示是反回文形式的,即对称位置是相反的,比如\(1001,101010\)

之后根据题意,这个反回文的二进制串长度必须是偶数(因为长度为奇数的串对称中心和对称中心不相反)

也不难发现这个反回文串确定了前一半后面就确定了

考虑枚举这个反回文的串的长度,显然长度小于\(n\)的长度的串,我们只需要让最高填\(1\),最低位填\(0\),之后除去最高位的前\(\frac{len-2}{2}\)随便填即可

对于长度等于\(n\)的串,还是最高填\(1\),最低位填\(0\),除去最高位的前\(\frac{len-2}{2}\)位填一个严格小于\(n\)的前\(\frac{len-2}{2}\)位的数,这样就能保证严格小于\(n\)

之后再特判一下前\(\frac{len-2}{2}\)位和\(n\)的前\(\frac{len-2}{2}\)位相等时的时候,反对称过去的数是否超过\(n\)

代码好像写得有点丑了

#include<bits/stdc++.h>
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
LL b[65];
LL n,ans;
inline void calc(int bit) {
    if(bit&1) return;
    ans+=(1ll<<((bit-2)/2));
}
int a[64];int tot;
inline int solve() {
    b[0]=1;int W=2*tot+1;
    for(re int i=0;i<=tot;++i) b[W-i]=b[i]^1;
    LL m=0;
    for(re int i=0;i<=W;++i)
        m<<=1ll,m|=b[i];
    return m<=n;
}
inline void Calc() {
    scanf("%lld",&n);;
    int now=0;tot=0;ans=0;
    for(re LL i=63;i>=0;--i) {
        if(!now) {
            if(n>>i&1) {
                now=1;
                if((i-1)&1) continue;
                for(re LL j=i-1;j>=0;--j) {
                    if(tot*2>=i-1) continue;
                    b[++tot]=n>>j&1;
                }
                LL k=0;
                for(re LL j=1;j<=tot;++j)
                    k|=b[j],k<<=1ll;
                k>>=1ll;ans=k;
                if(!tot) ++ans;else ans+=solve();
            }
            continue;
        }
        if(i>0) calc(i+1);
    }
    std::cout<<ans;puts("");
}
int main() {
    int T;scanf("%d",&T);
    while(T--) Calc();
    return 0;
}

B.小doge的快乐阳光跑

题意:给一张图,求一个权值和路径最小的移动序列,使得移动序列包含两个给定的子序列。

发现点数只有\(10^3\),边数也只有\(10^4\),图相当稀疏,所以完全可以跑\(n\)遍单源最短路,求出所有点对的之间的最短路

之后搞一个\(dp\)就完事了,设\(dp_{0/1,i,j}\)表示当前在第\(1/2\)个子序列的第\(i\)个位置,另一个子序列已经经过了前\(j\)个位置

转移显然,就是枚举一下下一个点去哪里

#include<bits/stdc++.h>
#define re register
#define LL long long
#define mp std::make_pair
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
inline int read() {
    char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
typedef std::pair<int,int> pii;
const int maxn=1e3+5;
struct E{int v,nxt,w;}e[maxn*20];
int d[maxn][maxn];
int n,m,num,head[maxn],vis[maxn],a[maxn],b[maxn],A,B;
inline void add(int x,int y,int w) {
    e[++num].v=y;e[num].nxt=head[x];head[x]=num;e[num].w=w;
}
LL dp[2][105][105];
std::priority_queue<pii,std::vector<pii>,std::greater<pii> > q;
inline void Dij(int s) {
    for(re int i=1;i<=n;++i) d[s][i]=1e9,vis[i]=0;
    d[s][s]=0,q.push(mp(d[s][s],s));
    while(!q.empty()) {
        int k=q.top().second;q.pop();
        if(vis[k]) continue;vis[k]=1;
        for(re int i=head[k];i;i=e[i].nxt)
        if(d[s][e[i].v]>d[s][k]+e[i].w) {
            d[s][e[i].v]=d[s][k]+e[i].w;
            q.push(mp(d[s][e[i].v],e[i].v));
        }
    }
}
int main() {
    n=read(),m=read();
    for(re int x,y,z,i=1;i<=m;i++)
        x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
    for(re int i=1;i<=n;i++) Dij(i);
    A=read();for(re int i=1;i<=A;i++) a[i]=read();
    B=read();for(re int i=1;i<=B;++i) b[i]=read();
    memset(dp,20,sizeof(dp));
    dp[0][1][0]=0,dp[1][0][1]=0;
    for(re int t=1;t<A+B;++t)
        for(re int j=0;j<=t&&j<=A;++j) {
            re int k=t-j;
            if(k>B) continue;
            if(j<A) dp[0][j+1][k]=min(dp[0][j+1][k],dp[0][j][k]+d[a[j]][a[j+1]]),
                dp[0][j+1][k]=min(dp[0][j+1][k],dp[1][j][k]+d[b[k]][a[j+1]]);
            if(k<B) dp[1][j][k+1]=min(dp[1][j][k+1],dp[0][j][k]+d[a[j]][b[k+1]]),
            dp[1][j][k+1]=min(dp[1][j][k+1],dp[1][j][k]+d[b[k]][b[k+1]]);
        }
    printf("%lld\n",min(dp[1][A][B],dp[0][A][B]));
    return 0;
}

C.区间异或和异或区间最大值异或区间最小值

题意就是在给定的一个序列里找到一个区间,让上面那个东西最大

\(60\rm pts\)是送的,写几个\(\rm subtask\)拼一拼就有了

考虑正解,考虑率大力分治一波,对于区间\([l,r]\)我们考虑一下如何合并\([l,mid]\)\([mid+1,r]\)

我们把左区间所有后缀以及右区间所有前缀的异或和、最大值、最小值都扫出来,我们提前把最大值和最小值都给异或进去

考虑合并掉两个区间之后,两个最大值中较小的那一个就不是最大值了,我们需要把它异或回来;两个最小值中较大的也不是最小值了,也需要把它异或回来

于是我们考虑从右区间里拿出一个数来,作为最大值较小的去和左区间匹配

显然可以排序之后利用单调性开一个指针,把左区间比这个最大值大的都扫进来,放到一个数据结构里,显然这种数据结构是\(trie\)

之后按照最小值讨论一波

  • 左区间的最小值作为最小值,那么我们需要把右区间的最小值给异或回来,去查最小值小于右区间最小值中,异或上这个数最大的

  • 右区间的最小值作为最小值,那么需要把左区间的最小值给异或回来,我们可以对左区间维护一个把最小值异或回来的\(trie\),在这个\(trie\)里查最小值大于右区间最小值中,异或上这个数最大的

我们发现按照最大值把这些数加入\(trie\)的过程中最小值也是单调的,于是把\(trie\)可持久化一下就可以查区间异或最大值了

代码

#include<bits/stdc++.h>
#define re register
#define LL long long
#pragma GCC optimize(3)
#pragma GCC optimize("-fcse-skip-blocks")
inline int read() {
	char c=getchar();int x=0;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 maxn=1e5+5;
const int Bit=30;
struct Seg {int mn,mx,x;}b[2][maxn];
int n,a[maxn],c[maxn],cnt,tot,ans;
int rt[2][maxn],son[maxn*32][2],d[maxn*32];
inline int max(int a,int b) {return a>b?a:b;}
inline int min(int a,int b) {return a<b?a:b;}
int ins(int pre,int x,int w) {
	int now=++cnt;
	d[now]=d[pre]+1;
	if(w==-1) return now;
	son[now][0]=son[pre][0],son[now][1]=son[pre][1];
	son[now][x>>w&1]=ins(son[pre][x>>w&1],x,w-1);
	return now;
}
int query(int p1,int p2,int x,int w) {
	if(w==-1) return 0;
	int t=x>>w&1;
	int now=d[son[p2][t^1]]-d[son[p1][t^1]];
	if(now) return (1<<w)+query(son[p1][t^1],son[p2][t^1],x,w-1);
	return query(son[p1][t],son[p2][t],x,w-1); 
}
inline int find(int x) {
	int l=1,r=tot,now=0;
	while(l<=r) {
		int mid=l+r>>1;
		if(c[mid]<=x) l=mid+1,now=mid;
		else r=mid-1;
	}
	return now;
}
void solve(int l,int r) {
	if(l==r) return;
	int mid=l+r>>1;
	solve(l,mid),solve(mid+1,r);
	int lenl=mid-l+1,lenr=r-mid;
	for(re int i=mid+1;i<=r;++i) {
		b[0][i-mid].mn=min(a[i],b[0][i-1-mid].mn);
		b[0][i-mid].mx=max(a[i],b[0][i-1-mid].mx);
		b[0][i-mid].x=a[i]^b[0][i-mid-1].x;
	}
	for(re int i=mid;i>=l;--i) {
		b[1][mid+1-i].mn=min(a[i],b[1][mid-i].mn);
		b[1][mid+1-i].mx=max(a[i],b[1][mid-i].mx);
		b[1][mid+1-i].x=a[i]^b[1][mid-i].x;
	}
	for(re int i=1;i<=lenl;++i) b[1][i].x^=(b[1][i].mn^b[1][i].mx);
	for(re int i=1;i<=lenr;++i) b[0][i].x^=(b[0][i].mn^b[0][i].mx);
	cnt=0,tot=0;int now=lenl;
	for(re int i=lenr;i;--i) {
		while(now&&b[1][now].mx>=b[0][i].mx) {
			++tot;
			rt[0][tot]=ins(rt[0][tot-1],b[1][now].x,Bit);
			rt[1][tot]=ins(rt[1][tot-1],b[1][now].x^b[1][now].mn,Bit);
			c[tot]=b[1][now--].mn;
		}
		if(tot) {
			int x=find(b[0][i].mn);
			ans=max(ans,query(rt[0][0],rt[0][x],b[0][i].x^b[0][i].mx^b[0][i].mn,Bit));
			ans=max(ans,query(rt[1][x],rt[1][tot],b[0][i].x^b[0][i].mx,Bit));
		}
	}
	for(re int i=1;i<=tot;i++) rt[0][i]=rt[1][i]=0;
	cnt=0;tot=0;now=lenr;
	for(re int i=lenl;i;--i) {
		while(now&&b[0][now].mx>=b[1][i].mx) {
			++tot;
			rt[0][tot]=ins(rt[0][tot-1],b[0][now].x,Bit);
			rt[1][tot]=ins(rt[1][tot-1],b[0][now].x^b[0][now].mn,Bit);
			c[tot]=b[0][now--].mn;
		}
		if(tot) {
			int x=find(b[1][i].mn);
			ans=max(ans,query(rt[0][0],rt[0][x],b[1][i].x^b[1][i].mx^b[1][i].mn,Bit));
			ans=max(ans,query(rt[1][x],rt[1][tot],b[1][i].x^b[1][i].mx,Bit));
		}
	}
	for(re int i=1;i<=tot;i++) rt[0][i]=rt[1][i]=0;
}
int main() {
	n=read();
	for(re int i=1;i<=n;i++) a[i]=read(),ans=max(ans,a[i]);
	b[0][0].mn=b[1][0].mn=1e9;
	solve(1,n);
	printf("%d\n",ans);
	return 0;
}
posted @ 2019-09-29 09:06  asuldb  阅读(267)  评论(0编辑  收藏  举报