牛客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\)可持久化一下查区间异或最大值了

猜你喜欢

转载自www.cnblogs.com/asuldb/p/11605865.html