Codeforces Round 922 (Div. 2)

Preface

因为三个号都打上橙了,现在就懒得开新号打CF了,索性直接遇到Div2直接摆烂,从C题开始做

好家伙结果被这场E题疯狂腐乳,假做法因为越界问题给我一种能过的错觉,后面发现原来是痴人说梦

感觉没有队友好多题目都想不清楚或者没啥思路,怎么回事呢


C. XOR-distance

首先不妨令\(c=a\oplus b\),按位考虑二进制下每一位的贡献,显然\(c\)\(0\)的那些位是不会产生影响的

否则对于\(c\)\(1\)的那些位,根据这一位上\(a,b\)的取值以及\(x\)这一位的取值绝对值内的符号可以取正取负

贪心地考虑把最高位带来的贡献划分为一种符号,剩余低位的贡献划分为另一种符号是最优的

但还要考虑\(x\le r\)的限制,因此不妨假设最高位\(x\)这一位取\(0\),后面的代价也就讨论出来了

#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;
int t,a,b,r;
inline int highbit(CI x)
{
	for (RI i=60;i>=0;--i) if ((x>>i)&1LL) return i; return -1;
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		scanf("%lld%lld%lld",&a,&b,&r);
		int c=a^b,hc=highbit(c);
		if (c==0) { puts("0"); continue; }
		int x=(1LL<<hc),y=0,sum=0,sd=((a>>hc)&1LL)<((b>>hc)&1LL);
		for (RI i=hc-1;i>=0;--i) if ((c>>i)&1LL)
		{
			int pd=((a>>i)&1LL)<((b>>i)&1LL),val=pd==sd?(1LL<<i):0LL;
			if (sum+val<=r) sum+=val,y+=(1LL<<i); else x+=(1LL<<i);
		}
		printf("%lld\n",x-y);
	}
	return 0;
}

D. Blocking Elements

这种问题看一眼就感觉要二分答案,不妨假设当前的划分方案中每一段的和都要\(\le x\),求一个使得所有划分点之和最小的合法方案

容易想到DP,设\(f_i\)表示处理了前\(i\)个位置,且钦定第\(i\)个位置为划分点时,划分点之和的最小值

转移考虑从\(f_j\)转移过来,要求\([j+1,i-1]\)这段区间的和不能超过\(x\)

显然对于一个确定的\(i\),可能的合法转移点\(j\)是一个连续的区间,用单调队列/线段树优化一下这个DP即可

#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=100005,INF=1e18;
int t,n,a[N],pfx[N],l[N],f[N];
class Segment_Tree
{
	private:
		int mn[N<<2];
	public:
		#define TN CI now=1,CI l=0,CI r=n+1
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void build(TN)
		{
			mn[now]=INF; if (l==r) return; int mid=l+r>>1; build(LS); build(RS);
		}
		inline void updata(CI pos,CI mv,TN)
		{
			if (l==r) return (void)(mn[now]=mv); int mid=l+r>>1;
			if (pos<=mid) updata(pos,mv,LS); else updata(pos,mv,RS);
			mn[now]=min(mn[now<<1],mn[now<<1|1]);
		}
		inline int query(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return mn[now]; int mid=l+r>>1,ret=INF;
			if (beg<=mid) ret=min(ret,query(beg,end,LS)); if (end>mid) ret=min(ret,query(beg,end,RS)); return ret;
		}
		#undef TN
		#undef LS
		#undef RS
}SEG;
inline bool check(CI lim)
{
	RI i,j; for (i=1,j=0;i<=n;++i)
	{
		while (pfx[i]-pfx[j]>lim) ++j; l[i]=j;
	}
	for (SEG.build(),SEG.updata(0,f[0]=0),i=1;i<=n+1;++i)
	SEG.updata(i,f[i]=SEG.query(l[i-1],i-1)+a[i]);
	return f[n+1]<=lim;
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&a[i]),pfx[i]=pfx[i-1]+a[i];
		a[0]=a[n+1]=0; int l=0,r=pfx[n],mid,ret; while (l<=r)
		if (check(mid=l+r>>1)) ret=mid,r=mid-1; else l=mid+1;
		printf("%lld\n",ret);
	}
	return 0;
}

E. ace5 and Task Order

我是究极煞笔,被这种丁真题关了1h还没做出来

比赛的时候写了个随机定序,然后依次问出相邻两数的差值,以此确定排列的做法,喜提询问次数超限

赛后祁神跟我讲了一个很有启发性的思路,后面当我意识到可以很快地完成比较和确定基准两个操作后就会做了

考虑如果我们把题目转化为:用给定的操作将一个未知的排列排序,显然得到了这个问题的答案我们就解决了原问题

而题目中给出的询问操作我们可以这样利用:

  • 定基准:选定某个数\(a_{bs}\)并一直询问,直到返回=为止,这样可以把\(x\)的值置为\(a_{bs}\)
  • 比较:在确定\(x=a_{bs}\)后,询问\(a_i\),则可以得到\(a_i\)\(a_{bs}\)的大小关系,每次询问后在问一次\(a_{bs}\)即可将\(x\)置回为\(a_{bs}\)

看到这两种操作,则很容易想到用快速排序的思路求解了,为了不被卡掉,只需要随机选择基准或者直接把初始排列random_shuffle一下即可

快速排序的比较次数为\(O(n\log n)\)级别,这里每次比较需要花费两次询问次数

而定基准所需的操作次数总体也是\(O(n\log n)\)级别,因此总询问次数约为\(O(3n\log n)\)级别,可以通过此题

#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=2005;
int t,n,id[N],a[N],d[N];
inline char ask(CI x)
{
	printf("? %d\n\n",x); fflush(stdout); char s[10]; scanf("%s",s); return s[0];
}
inline void output(void)
{
	printf("! "); for (RI i=1;i<=n;++i) printf("%d%c",a[i]," \n"[i==n]); printf("\n"); fflush(stdout);
}
inline void quick_sort(CI l,CI r)
{
	if (l>=r) return; while (ask(id[l])!='=');
	RI i; vector <int> ls,gt; int bs=id[l];
	for (i=l+1;i<=r;++i)
	{
		if (ask(id[i])=='<') ls.push_back(id[i]);
		else gt.push_back(id[i]); ask(bs);
	}
	i=l; for (auto x:ls) id[i++]=x;
	id[i++]=bs; for (auto x:gt) id[i++]=x;
	quick_sort(l,l+ls.size()-1); quick_sort(r-gt.size()+1,r);
}
int main()
{
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) id[i]=i;
		if (n==1) { a[1]=1; output(); continue; }
		srand(time(0)); random_shuffle(id+1,id+n+1);
		for (quick_sort(1,n),i=1;i<=n;++i) a[id[i]]=i;
		output();
	}
	return 0;
}

F. Caterpillar on a Tree

比赛的时候题目都没看,今天一看怎么和我之前做过的一个题好像

首先如果没有回到根节点的操作那么就是经典结论,最小步数为\(2(n-1)-\max dep_i\),即每条边都会经过两次,除了最后走到的深度最大的节点对应的路径

考虑能回到根节点会有什么影响,不难发现我们总是要访问完一个点子树内的所有点才可能会传送回根节点,不然肯定是亏的

那么容易想到对于每个点,先将其所有子节点按照深度最深的点从小到大排序,按这个顺序来访问,则最后只要考虑欧拉序中第一次出现的位置相邻的两个点之间的影响即可

不难发现这样的可能的选择是\(O(n)\)级别的,可以直接全部找出来贪心选择使答案减少最多的\(k\)

Upt(一些原理补充):因为如果要利用传送操作减少操作次数,一定是从最深的点操作最省次数

然后因为每次一定要把子树遍历完了再去操作,所以等价于把深度最深的点放到最后去遍历

然后根据新排序的儿子关系把欧拉序求出来,本来最优策略是按照欧拉序的顺序走,现在多了传送操作就相当于可以快速省略掉两点间的路径

但我们又不能跳过子树中的某个点,因此只能在相邻的两个点之间跳(这个相邻指欧拉序中第一次出现的位置相邻的两个节点)

#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=400005;
int n,k,x,len[N],dep[N],seq[N],idx,ans; vector <int> v[N]; vector <pi> fir;
inline void DFS1(CI now=1,CI fa=0)
{
	for (auto to:v[now]) if (to!=fa)
	dep[to]=dep[now]+1,DFS1(to,now),len[now]=max(len[now],len[to]+1);
	auto cmp=[&](CI x,CI y)
	{
		return len[x]<len[y];
	};
	sort(v[now].begin(),v[now].end(),cmp);
}
inline void DFS2(CI now=1,CI fa=0)
{
	seq[++idx]=now; fir.push_back(pi(now,idx));
	for (auto to:v[now]) if (to!=fa) DFS2(to,now),seq[++idx]=now;
}
int main()
{
	RI i; for (scanf("%d%d",&n,&k),i=2;i<=n;++i)
	scanf("%d",&x),v[x].push_back(i);
	DFS1(); DFS2(); ans=2*(n-1)-len[1];
	vector <int> dec; for (i=0;i+1<fir.size();++i)
	dec.push_back(fir[i+1].se-fir[i].se-dep[fir[i+1].fi]);
	sort(dec.begin(),dec.end(),greater <int>());
	for (i=0;i<min(k,(int)dec.size());++i) ans-=max(0,dec[i]);
	return printf("%d",ans),0;
}

Postscript

话说好像前两天打的那场CF都没补完,新的一场又来了,只能说还得加倍努力吧

posted @ 2024-01-31 17:21  空気力学の詩  阅读(103)  评论(0编辑  收藏  举报