Codeforces Round 930 (Div. 1)

Preface

虽然难得有Div1的CF,但由于在周四晚上学校要断电就没打

这两天有空去补了下发现好像错过了最适合我的一场,AB都是一眼题,C是经典图论建模,D是DS典题

没有Counting我直接爽飞,不过估计现场打的话写个ABC差不多了,D的码量还是挺大的


A. Bitwise Operation Wizard

很有意思的一个交互,要稍微想一下才行

首先不难发现必然存在某对最优的pair包含\(n-1\),我们可以先用\(n-1\)次操作找出所有数中最大的那个,即为\(n-1\)

现在考虑要找出某个数\(x\)使得\(x\oplus (n-1)\)的每一位都是\(1\),但题目中没给异或操作而是只有或操作

不妨先找出所有\(\{x_i\}\)使得\(x_i|(n-1)\)的值最大,注意到这些\(x_i\)都是我们要求的那个\(x\)的超集

因此最后找出\(\{x_i\}\)中最小的数即可,总询问次数是\(3n\)级别的

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
int t,n;
inline char query(CI a,CI b,CI c,CI d)
{
	printf("? %d %d %d %d\n",a,b,c,d); fflush(stdout);
	char res[10]; scanf("%s",res); return res[0];
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; scanf("%d",&n); int mxpos=1;
		for (i=0;i<n;++i) if (query(i,i,mxpos,mxpos)=='>') mxpos=i;
		vector <int> vec; vec.push_back(mxpos==0?1:0);
		for (i=0;i<n;++i) if (i!=mxpos)
		{
			char tmp=query(i,mxpos,vec.back(),mxpos);
			if (tmp=='>') vec.clear(),vec.push_back(i);
			else if (tmp=='=') vec.push_back(i);
		}
		int mnpos=vec[0]; for (i=1;i<vec.size();++i)
		if (query(vec[i],vec[i],mnpos,mnpos)=='<') mnpos=vec[i];
		printf("! %d %d\n",mxpos,mnpos); fflush(stdout);
	}
	return 0;
}

B. Pinball

感觉是个很典的题,好像之前也是在CF上做过类似的题

手玩一波会发现当前位置左侧的>会阻碍球从左边出去;同理当前位置左侧的<会阻碍球从右边出去

不妨把这些位置上的板都看作反弹板,中间其它的位置都看作空地,然后此时问题等价于给定一个初始方向发射小球,每当小球遇到反弹板时都会反弹并且摧毁反弹板,问小球最后出去需要的时间

首先我们需要确定小球出去的方向,显然这个由反弹板数量较少的那边决定(当二者数量相同时由初始方向决定)

然后贡献的形式就是右侧反弹板的下标和减去左侧反弹板下标和的形式,拿个前缀和维护一下即可,具体的Case需要手动讨论一下

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define int long long
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=500005;
int t,n; char s[N];
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; scanf("%lld%s",&n,s+1); vector <int> A,B;
		for (i=n;i>=1;--i) if (s[i]=='<') if (B.push_back(i),B.size()>1) B.back()+=B[B.size()-2];
		for (i=1;i<=n;++i)
		{
			if (s[i]=='<') B.pop_back();
			auto get=[&](vector <int>& vec,CI len)
			{
				if (vec.empty()) return 0LL;
				return vec.back()-(len==vec.size()?0LL:vec[vec.size()-len-1]);
			};
			int len=min(A.size(),B.size()),ret=2LL*(get(B,len)-get(A,len)),ways=0;
			if (s[i]=='<') ret+=i,++ways; else ret-=i,--ways;
			if (A.size()!=B.size())
			{
				if (A.size()<B.size()) ret-=0,--ways; else ret+=n+1,++ways;
			} else
			{
				if (ways>0) ret-=0,--ways; else ret+=n+1,++ways;
			}
			if (ways!=0)
			{
				if (ways>0) ret-=2LL*(A[A.size()-len-1]-(A.size()-len-1==0?0LL:A[A.size()-len-2]));
				else ret+=2LL*(B[B.size()-len-1]-(B.size()-len-1==0?0LL:B[B.size()-len-2]));
			}
			printf("%lld%c",ret," \n"[i==n]);
			if (s[i]=='>') if (A.push_back(i),A.size()>1) A.back()+=A[A.size()-2];
		}
	}
	return 0;
}

C. Pokémon Arena

你们有这样的典题吗,真实经经又典典啊

考虑把这个题转成一个图论最短路来做,暴力的想法就是枚举点对连边,但这样边数是\(O(n^2)\)的显然就炸了

不妨假设只有一维属性该怎么做,这其实是个经典问题,我们把所有点按照属性从大到小排序

对于相邻的两个点\(i,i+1\),不妨设它们的属性为\(a_i,a_{i+1}\),则连\(i\to i+1\),边权为\(a_i-a_{i+1}\)的边;以及\(i+1\to i\),边权为\(0\)的边即可

而有\(m\)个维度的情况也是一样的,对于每一维建\(n\)个虚点表示从这一维转移即可,然后由于这题还有点权,因此还需要把原来的每个点拆成两个点,中间连点权\(c_i\)的边

具体实现可以看代码,最后就是跑一个点数/边数都是\(O(n\times m)\)级别的最短路即可

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define int long long
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=400005,NN=1200005,INF=1e18;
int t,n,m,c[N],dis[NN],vis[NN]; vector <int> a[N]; vector <pi> v[NN];
inline int Dijkstra(CI s,CI t)
{
	for (RI i=1;i<=(2+m)*n;++i) dis[i]=INF,vis[i]=0;
	priority_queue <pi,vector <pi>,greater <pi>> hp;
	hp.push(pi(dis[s]=0,s));
	while (!hp.empty())
	{
		int now=hp.top().se; hp.pop();
		if (vis[now]) continue; vis[now]=1;
		for (auto [to,w]:v[now]) if (dis[to]>dis[now]+w)
		hp.push(pi(dis[to]=dis[now]+w,to));
	}
	//for (RI i=1;i<=(2+m)*n;++i) printf("dis[%lld] = %lld\n",i,dis[i]);
	for (RI i=1;i<=(2+m)*n;++i) v[i].clear();
	return dis[t];
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i,j; for (scanf("%lld%lld",&n,&m),i=1;i<=n;++i) scanf("%lld",&c[i]);
		for (i=1;i<=n;++i) for (a[i].resize(m),j=0;j<m;++j) scanf("%lld",&a[i][j]);
		for (i=1;i<=n;++i)
		{
			v[i].push_back(pi(n+i,c[i]));
			for (j=0;j<m;++j)
			{
				v[n+i].push_back(pi((2+j)*n+i,0));
				v[(2+j)*n+i].push_back(pi(i,0));
			}
		}
		for (j=0;j<m;++j)
		{
			vector <pi> vec;
			for (i=1;i<=n;++i) vec.push_back(pi(a[i][j],(2+j)*n+i));
			sort(vec.begin(),vec.end());
			for (i=0;i<n-1;++i)
			{
				v[vec[i].se].push_back(pi(vec[i+1].se,0));
				v[vec[i+1].se].push_back(pi(vec[i].se,vec[i+1].fi-vec[i].fi));
			}
		}
		//for (i=1;i<=(2+m)*n;++i) for (auto [to,w]:v[i]) printf("%lld->%lld (%lld)\n",i,to,w);
		printf("%lld\n",Dijkstra(n+1,n+n));
	}
	return 0;
}

D. Bitwise Paradox

虽然感觉这题融了好多经典trick,但总体来看还是很不错的一个题的说

看到区间询问先考虑线段树,对于每个区间首先肯定要维护其包含的所有合法的子区间中最小的答案,这样在合并的时候,我们还需要统计的就是跨过区间中点的情况

二进制下让某个数\(\ge v\)有个经典的trick,枚举\(v\)中所有\(0\)出现的位置,强制前缀与\(v\)相同并且这一位为\(1\)后,后面位就可以随便选了

因此现在不妨钦定某个位一定要为\(1\),考虑计算其贡献,不难发现随着区间两端向外扩展其贡献一定是不降的,因此我们需要尽可能减少区间长度

不妨找出左侧子区间中,该位为\(1\)的数出现的最靠右的位置;以及右侧子区间中,该位为\(1\)的数出现的最靠左的位置,显然我们需要将区间端点扩展到其中一个位置

讨论一下会发现我们总会选择扩展最大值较小的那一边,因为如果选择扩展较大的一边也一定可以扩展较小的一边,此时一定不优

因此我们需要\(O(\log n\log v)\)的复杂度来维护线段树上的合并区间操作,但万幸的是这题的修改操作只针对\(\{b_i\}\),因此我们可以拿RMQ来\(O(1)\)维护\(\{a_i\}\)的区间最大值

总复杂度\(O((n+q)\times \log n\log v)\)

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=200005,INF=2e9;
int t,n,v,a[N],b[N],q,opt,x,y;
namespace RMQ
{
	int f[N][20],_log[N];
	inline void init(CI n)
	{
		RI i,j; for (_log[0]=-1,i=1;i<=n;++i) _log[i]=_log[i>>1]+1;
		for (i=1;i<=n;++i) f[i][0]=a[i];
		for (j=1;(1<<j)<=n;++j) for (i=1;i+(1<<j)-1<=n;++i)
		f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
	}
	inline int querymax(CI l,CI r)
	{
		if (l>r) return INF; int k=_log[r-l+1];
		return max(f[l][k],f[r-(1<<k)+1][k]);
	}
};
using namespace RMQ;
class Segment_Tree
{
	private:
		struct segment
		{
			int res,fir[30],lst[30];
			inline segment(void)
			{
				res=INF; memset(fir,0,sizeof(fir)); memset(lst,0,sizeof(lst));
			}
		}O[N<<2];
		inline segment merge(const segment& A,const segment& B,CI mid)
		{
			segment C=segment(); C.res=min(A.res,B.res); int L=mid+1,R=mid; bool flag=1;
			for (RI i=0;i<30;++i) C.fir[i]=A.fir[i]?A.fir[i]:B.fir[i],C.lst[i]=B.lst[i]?B.lst[i]:A.lst[i];
			for (RI i=29;i>=0;--i) if ((v>>i)&1)
			{
				int l=A.lst[i]?A.lst[i]:mid+1,r=B.fir[i]?B.fir[i]:mid;
				int mxl=querymax(l,mid),mxr=querymax(mid+1,r);
				if (mxl==INF&&mxr==INF) { flag=0; break; }
				if (mxl<=mxr) L=min(L,l); else R=max(R,r);
			} else
			{
				int l=A.lst[i]?A.lst[i]:mid+1,r=B.fir[i]?B.fir[i]:mid,tl=L,tr=R;
				int mxl=querymax(l,mid),mxr=querymax(mid+1,r);
				if (mxl==INF&&mxr==INF) continue;
				if (mxl<=mxr) tl=min(L,l); else tr=max(R,r);
				C.res=min(C.res,querymax(tl,tr));
			}
			if (flag) C.res=min(C.res,querymax(L,R)); return C;
		}
	public:
		#define TN CI now=1,CI l=1,CI r=n
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void build(TN)
		{
			O[now]=segment(); if (l==r)
			{
				for (RI i=0;i<30;++i)
				if ((b[l]>>i)&1) O[now].fir[i]=O[now].lst[i]=l;
				if (b[l]>=v) O[now].res=a[l]; return;
			}
			int mid=l+r>>1; build(LS); build(RS);
			O[now]=merge(O[now<<1],O[now<<1|1],mid);
		}
		inline void modify(CI pos,CI mv,TN)
		{
			if (l==r)
			{
				O[now]=segment(); b[l]=mv;
				for (RI i=0;i<30;++i)
				if ((b[l]>>i)&1) O[now].fir[i]=O[now].lst[i]=l;
				if (b[l]>=v) O[now].res=a[l]; return;
			}
			int mid=l+r>>1; if (pos<=mid) modify(pos,mv,LS); else modify(pos,mv,RS);
			O[now]=merge(O[now<<1],O[now<<1|1],mid);
		}
		inline segment query(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return O[now]; int mid=l+r>>1;
			if (end<=mid) return query(beg,end,LS); else
			if (beg>mid) return query(beg,end,RS); else
			return merge(query(beg,end,LS),query(beg,end,RS),mid);
		}
		inline int Query(CI beg,CI end)
		{
			return query(beg,end).res;
		}
		#undef TN
		#undef LS
		#undef RS
}SEG;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d%d",&n,&v),i=1;i<=n;++i) scanf("%d",&a[i]);
		for (init(n),i=1;i<=n;++i) scanf("%d",&b[i]);
		for (SEG.build(),scanf("%d",&q),i=1;i<=q;++i)
		{
			scanf("%d%d%d",&opt,&x,&y);
			if (opt==1) SEG.modify(x,y); else
			{
				int tmp=SEG.Query(x,y);
				printf("%d%c",tmp!=INF?tmp:-1," \n"[i==q]);
			}
		}
	}
	return 0;
}

Postscript

最近CF比赛好多啊,但怎么都不挑周末的时间全是要熄灯的说

posted @ 2024-03-05 16:13  空気力学の詩  阅读(68)  评论(0编辑  收藏  举报