当天晚上被\(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\)可持久化一下查区间异或最大值了