2021牛客暑期多校训练营8题解

D OR

对深刻理解位运算比较好的题目,首先题目中一个是或运算,一个是加法运算。???加法,我去你四则运算和位运算搞一块是什么玩意?还是将加法转化一下,\(c=a+b=a|b+a\)&\(b\),其实这也很好理解,或运算就是两个数拆成二进制后每一位的并集,而与运算则是交集,这个大概用ven图的形式大概就更好了解了。
那么现在的条件就变了,\(b_i=a_{i-1}|a_i,c_i=a_{i-1}|a_i+a_{i-1}\)&\(a_i\),我们简单的代换一下,令\(d_i=c_i-b_i=a_{i-1}\)&\(a_i\)这样我们就把讨厌的加法给替换掉了,也就是说现在变成了每两位之间或起来是一个定值,与起来是另一个定值,我们考虑二进制按位考虑一下。
然后我们可以发现其实第一位确定以后这之后的所有数都确定了,我们只需要枚举下第一位的二进制下的每一位,考虑下这一位上选0/1是否合法,因为没有加法,都变成|和&之后,每一位都是独立的,我们可以分开统计答案。接下来进行各个情况的讨论,
我们先对\(b_i,d_i的不同情况进行讨论\)
\(b_i=0,d_i=1,则该情况不合法\)
\(b_i=1,d_i=1,则a_{i-1}=1,a_i=1\)
\(b_i=1,d_i=0,则a_{i-1}=1,a_i=0或a_{i-1}=0,a_i=1\)
\(b_i=0,d_i=0,则a_{i-1}=0,a_i=0\)
这样我们直接根据\(a_1\)的每一位上是0还是1,扫一遍整个序列,判断合法不合法即可。复杂度\(O(nlogn)\)

查看代码

//不等,不问,不犹豫,不回头.
#include
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair
#define PII pair
#define ull unsigned long long
#define put(x) printf("%d\n",x)
#define putl(x) printf("%lld\n",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(RE int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=1e5+10;
int b[N],d[N],n; 

inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}

int main()
{
// freopen("1.in","r",stdin);
get(n);
rep(i,2,n) get(b[i]);
rep(i,2,n)
{
int get(x);
d[i]=x-b[i];
if(d[i]<0) {puts("0");return 0;}
}
int ans=1;
rep(j,0,30)
{
int now=0,ct=0;//now为当前的值
rep(i,2,n)
{
int s1=b[i]&1<<j;
int s2=d[i]&1<<j;
if(!s1&&s2) {now=-1;break;}
else if(s1&&s2)
{
if(!now) {now=-1;break;}
}
else if(s1&&!s2) now^=1;
else if(!s1&&!s2)
{
if(now) {now=-1;break;}
}
}
if(now!=-1) ct++;
now=1;
rep(i,2,n)
{
int s1=b[i]&1<<j;
int s2=d[i]&1<<j;
if(!s1&&s2) {now=-1;break;}
else if(s1&&s2)
{
if(!now) {now=-1;break;}
}
else if(s1&&!s2) now^=1;
else if(!s1&&!s2)
{
if(now) {now=-1;break;}
}
}
if(now!=-1) ct++;
ans*=ct;
}
put(ans);
return (0_0);
}
//以吾之血,铸吾最后的亡魂.

K Yet Another Problem About Pi

就是比较细节的坑爹题...很显然的我们可以发现,无论w和d是多大的我们总可以到达四个城市,之后可以发现越过一个方格,可以多到达两个城市,越过一个斜线可以多到达三个城市,并且考虑性价比的话,显然是先选择性价比高的一直选,然后最后不足整个的部分,选择另一种方式,或者倒数一个不用,正好可以用另一个的两倍等等。所以我们可以枚举一下直线型和斜线型的次数,不用枚举太多,从0枚举到2大概就可以了。因为考虑到性价比的话,只有最后几次再换才是最佳的。

查看代码

//不等,不问,不犹豫,不回头.
#include
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair
#define PII pair
#define ull unsigned long long
#define put(x) printf("%d\n",x)
#define putl(x) printf("%lld\n",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(RE int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const db pai=acos(-1);

inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}

int main()
{
//freopen("1.in","r",stdin);
int get(T);
while(T--)
{
db x,d;
scanf("%lf%lf",&x,&d);
if(min(x,d)>pai) {put(4);continue;}
else
{
int ans=0;
db xie=sqrt(xx+dd);
x=min(x,d);
rep(i,0,2)
{
if(ix<=pai) ans=max(ans,i2+(int)((pai-xi)/xie)3);
if(ixie<=pai) ans=max(ans,i3+(int)((pai-xiei)/x)2);
}
put(ans+4);
}
}
return (0_0);
}
//以吾之血,铸吾最后的亡魂.

F Robots

看起来很简单,但做起来就.....
操作一,操作二就不说了,唯一有点难度的就是操作三了,每个机器人都可以向右,下走...这怎么处理呢?
首先先看下问题的规模,询问很多,\(5e5\),但总的地图大小也就\(500*500=250000\),说明有很多的点时重复的,对于这种问题,显然离线做更优的。
若是离线做的话我们考虑一下每个点,比较朴素的想法就是将每个点能到达的每个点记录下来,然后直接看询问的点能否到达就行了。但考虑怎么记录,我们可以开大小为\(500*500的bitset\)将坐标(i,j)一一对应到bitset中去,若该位为1则表示可以到达,若为0则无法到达。但这样我们的空间有点吃不消,只能开500个这样的bitset,既然如此那我们就定义\(f[i]\)表示当前行第j行的可达性点集。我们尝试行,列倒着枚举我们的每个点,转移状态,发现转移状态时当前行只与下一行有关,bitset开个滚动数组即可。每到一个点时我们就处理该点的所有询问。考虑时间复杂度是500500500*500/32,问题中允许的c++时间为5秒,常数写小点可以过。

查看代码

	//不等,不问,不犹豫,不回头.
#include
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair
#define PII pair
#define ull unsigned long long
#define put(x) printf("%d\n",x)
#define putl(x) printf("%lld\n",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(RE int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=505,maxn=5e5+10;
int n,m,q,Q,hang[N][N],line[N][N];
bool ans[maxn];
char c[N][N];
struct wy{int x,y,id,op;};
vectorv[N][N];
bitsetf[2][N];

inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}

int main()
{
//freopen("1.in","r",stdin);
get(n);get(m);
rep(i,1,n) scanf("%s",c[i]+1);
get(Q);
rep(i,1,Q)
{
int get(t),get(x1),get(y1),get(x2),get(y2);
v[x1][y1].push_back((wy){x2,y2,i,t});
}
int u=0;
fep(i,n,1)
{
u=u1;//u表示当前行,u1表示上一行。
fep(j,m,1)
{
f[u][j].reset();
if(c[i][j]'0')
{
hang[i][j]=j;
line[i][j]=i;
f[u][j][(i-1)*m+j]=1;
if(j+1<=m&&c[i][j+1]
'0')
{
hang[i][j]=hang[i][j+1];
f[u][j]|=f[u][j+1];
}
if(i+1<=n&&c[i+1][j]'0')
{
line[i][j]=line[i+1][j];
f[u][j]|=f[u^1][j];
}
for(auto x:v[i][j])
{
if(x.op
1&&x.yj&&x.x>=i)
{
ans[x.id]=(line[i][j]>=x.x)?1:0;
}
else if(x.op
2&&x.xi&&x.y>=j)
{
ans[x.id]=(hang[i][j]>=x.y)?1:0;
}
else if(x.op
3&&x.x>=i&&x.y>=j)
{
ans[x.id]=f[u][j][(x.x-1)*m+x.y];
}
}
}
}
}
rep(i,1,Q)
{
if(ans[i]) puts("yes");
else puts("no");
}
return (0_0);
}
//以吾之血,铸吾最后的亡魂.

J Tree

一看博弈论的题,嗯,劝退,退退退...
仔细思考这个题,我们先不考虑两个人怎么走,就考虑只有一个人的情况下,它在一个点上,怎么走才能使得移动的步数最大?走过一个点后这个点及其子树都消失了,也就是没有回头路了,那不就是直径吗?终点肯定是到直径端点中的其中一个。考虑两个人博弈的过程。由于走一步就会断点的特殊性质,我们发现s和t之间的链是十分关键的。只要s和t中的一个脱离了链,那么他们两个就处于两个连通块内,最长的路就是他们所处的点到两个树中直径端点的max。那我们可以直接将这条链抽出来,模拟他们博弈的过程,每次从s开始,s可以选择继续走这条链,也可以选择走这条链连接的其他节点,脱离这条链,之后轮到t,进行同样的操作。两个人都同样聪明的话,显然对于s来说,他会选择两个选择中的比较大的值,对于t来说,他会选择两种选择中代价比较小的一个。这个过程可以用递归来处理。另外我们还需要预处理出链上的每个点在不经过链的情况下能走的最长链的长度。这个dfs一下就可以。在决策的过程中我们涉及到大跟堆,及其大跟堆中删除元素,我们可以另外建一个大跟堆,当做我们删除元素的堆,当两个堆的堆顶元素相同时,我们同时对两个堆都进行pop操作就行。

查看代码

//不等,不问,不犹豫,不回头.
#include
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair
#define PII pair
#define ull unsigned long long
#define put(x) printf("%d\n",x)
#define putl(x) printf("%lld\n",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=5e5+10;
int n,s,t,link[N],tot,fa[N],path[N],p,vis[N],Long[N];
struct wy{int y,next;}a[N<<1];
priority_queueHt,Hs,Dt,Ds;

inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}

inline void add(int x,int y)
{
a[++tot].y=y;a[tot].next=link[x];link[x]=tot;
}

inline void dfs1(int x,int father)
{
go(x)
{
if(y==father) continue;
fa[y]=x;
dfs1(y,x);
}
}

inline void get_path(int x)
{
path[++p]=x;vis[x]=1;
if(x==t) return;
get_path(fa[x]);
}

inline int dfs2(int x,int d)
{
int ans=d;
go(x)
{
if(vis[y]) continue;
vis[y]=1;
ans=max(ans,dfs2(y,d+1));
}
return ans;
}

int DPs(int l,int r);
//构写函数时一定要牢记该函数的目的是什么,譬如,这里的DPt,DP是,表示的是当前情况下算上之前走过
//的步数,s比t多走的步数。
int DPt(int l,int r)
{
if(l+1r) return min(Long[l]+l-1-(Long[r]+p-r),Long[l]+l-1-(p-r+1));
Ds.push(Long[r]+r-1);Dt.push(Long[r]+p-r);
while(!Hs.empty()&&!Ds.empty()&&Hs.top()
Ds.top()) Hs.pop(),Ds.pop();
while(!Ht.empty()&&!Dt.empty()&&Ht.top()==Dt.top()) Ht.pop(),Dt.pop();
return min(Hs.top()-Long[r]-p+r,DPs(l,r-1));
}

int DPs(int l,int r)
{
if(l+1r) return max(Long[l]+l-1-(Long[r]+p-r),l-(Long[r]+p-r));
Ds.push(Long[l]+l-1);Dt.push(Long[l]+p-l);
while(!Hs.empty()&&!Ds.empty()&&Hs.top()
Ds.top()) Hs.pop(),Ds.pop();
while(!Ht.empty()&&!Dt.empty()&&Ht.top()==Dt.top()) Ht.pop(),Dt.pop();
return max(Long[l]+l-1-Ht.top(),DPt(l+1,r));
}

int main()
{
// freopen("1.in","r",stdin);
get(n);get(s);get(t);
rep(i,1,n-1)
{
int get(x),get(y);
add(x,y);add(y,x);
}
dfs1(t,0);get_path(s);
rep(i,1,p) Long[i]=dfs2(path[i],0);//记录链上每个点在不经过链的情况下的最长链。
rep(i,1,p) Ht.push(Long[i]+p-i);
rep(i,1,p) Hs.push(Long[i]+i-1);
put(DPs(1,p));
return (0_0);
}
//以吾之血,铸吾最后的亡魂.

posted @ 2021-08-12 09:21  逆天峰  阅读(118)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//