Codeforces Round #812 (Div. 2)

Preface

话说最近CF好卡啊,开个网页一般3min还卡爆多次……


A. Traveling Salesman Problem

sb题,四个方向找出走到的最远位置即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
int t,n,x,y,c[4];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d",&n),i=0;i<4;++i) c[i]=0;
		for (i=1;i<=n;++i)
		{
			int tp=0,v=0; scanf("%d%d",&x,&y);
			if (!x&&y>0) tp=0,v=y; else if (!x&&y<0) tp=1,v=-y;
			else if (x>0&&!y) tp=2,v=x; else if (x<0&&!y) tp=3,v=-x;
			c[tp]=max(c[tp],v);
		}
		int ans=0; for (i=0;i<4;++i) ans+=c[i];
		printf("%d\n",ans<<1);
	}
	return 0;
}

B. Optimal Reduction

sb题还WA了一发,那我就是超级大sb

首先考虑什么时候操作次数是最少的,很容易想到是排序后的情况

这是因为我们每次可以对剩下的所有数都进行减\(1\)操作,考虑怎么样会让操作次数变多

显然是当经过一些操作后整个序列被分隔成了若干段,此时即存在一个数\(i\),满足\(\max_\limits{1\le j<i} a_j<a_i\and\max_\limits{i<j\le n} a_j<a_i\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N],pre[N],suf[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
		bool flag=1; pre[1]=a[1]; suf[n]=a[n];
		for (i=2;i<=n;++i) pre[i]=max(pre[i-1],a[i]);
		for (i=n-1;i;--i) suf[i]=max(suf[i+1],a[i]);
		for (i=2;i<n&&flag;++i) if (a[i]<pre[i-1]&&a[i]<suf[i+1]) flag=0;
		puts(flag?"YES":"NO");
	}
	return 0;
}

C. Build Permutation

不知道为什么随便手玩了些情况就知道怎么做了

题目等价于把两个集合\(\{0,1,2,\cdots,n-1\}\)中的元素一一配对使得每对的和为完全平方数

首先从后往前考虑,对于现在没配对的最大的数\(k\),我们直接找到大于等于它的最小的完全平方数\(h\)

发现可以把\(k\)\(h-k\)匹配,\(k-1\)\(h-k+1\)匹配,依此类推

直觉告诉我们这样是一定可以得到解的,换句话说,\(h-k\ge 0\)是恒成立的

证明的话我们要用到这个经典结论:对于任意一个正整数\(x\),在\([x,2x]\)存在至少一个完全平方数,证明参考Proof

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d",&n),i=n-1;~i;i=j)
		{
			int cur=sqrt(i); if (cur*cur<i) ++cur; cur*=cur;
			for (j=i;j>=cur-i;--j) a[j]=cur-j;
		}
		for (i=0;i<n;++i) printf("%d%c",a[i]," \n"[i==n-1]);
	}
	return 0;
}

D. Tournament Countdown

有趣的交互题,睡了一觉起来突然就会做了……

首先不难看出,如果我们直接模拟比赛的全过程问一遍每次PK的对手,是可以在\(2^n-1\)次内得到解的

考虑询问次数上界\(\lceil \frac{2^{n+1}}{3}\rceil=\lceil \frac{2}{3}\times 2^n\rceil\),尝试从中获取提示

换个角度想,如果前面simple的做法是用一次询问淘汰一个人的话,那么我们现在应该做到用两次询问淘汰三个人

那么思路就比较清晰了,我们考虑一次处理四名选手,默认编号为\(1,2,3,4\),其中\([1,2],[2,3],[winner(1,2),winnner(3,4)]\)之间存在比赛

考虑我们现在询问\([1,3]\)之间的胜场多少关系,有以下三种情况:

  • \(1\)胜场数多于\(3\),首先显然可以排除\(3\),同时若\(1\)没有赢得与\(2\)的比赛,那么他的胜场不可能比\(3\)多,因此可以排除\(2\)
  • \(1\)胜场数少于\(3\),首先显然可以排除\(1\),同时若\(3\)没有赢得与\(4\)的比赛,那么他的胜场不可能比\(1\)多,因此可以排除\(4\)
  • \(1\)胜场数和\(3\)相等,那么显然\(1,3\)都没有赢下各自的比赛,因此排除\(1,3\)

不难发现此时我们排除了四个人中的两个,再通过一次询问即可得到这四个人中最后赢下的那位,即完成了用两次询问淘汰三个人的目标

不断重复上述过程即可

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#define RI register int
#define CI const int&
#define pb push_back
using namespace std;
const int N=100005;
int t,n,s[4]; vector <int> A,B;
inline int query2(CI p,CI q)
{
	printf("? %d %d\n",p,q); fflush(stdout);
	int tp; scanf("%d",&tp); return tp==1?p:q;
}
inline int query4(void)
{
	printf("? %d %d\n",s[0],s[2]); fflush(stdout);
	int tp,p,q; scanf("%d",&tp); switch (tp)
	{
		case 1: p=s[0]; q=s[3]; break;
		case 2: p=s[2]; q=s[1]; break;
		case 0: p=s[1]; q=s[3]; break;
	}
	return query2(p,q);
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; scanf("%d",&n); A.clear(); for (i=1;i<=(1<<n);++i) A.pb(i);
		for (;A.size()>2;A=B)
		for (B.clear(),i=0;i<A.size();i+=4)
		{
			for (j=0;j<4;++j) s[j]=A[i+j];
			B.pb(query4());
		}
		if (A.size()==2) printf("! %d\n",query2(A[0],A[1])),fflush(stdout);
		else printf("! %d\n",A[0]),fflush(stdout);
	}
	return 0;
}

E. Cross Swapping

我发现我竟然忘记了一种PJ组难度的DSU问题,太菜太菜

首先一眼发现每个位置\((x,y)\)只有可能与\((y,x)\)交换,这需要通过进行操作\(k=x\)\(k=y\)来实现

同时我们发现一种操作\(k=i\)要么不做要么做一次,多了是没有影响的

接下来我们考虑字典序最小的要求,不难想到我们依次考虑每个\((x,y)\),考虑在不影响前面位置最小的情况下将这个位置的值最小化

此时我们依次枚举\((x,y)\),强制\(x<y\),然后分两种情况:

  • \(a_{x,y}>a_{y,x}\),此时应该交换这两个位置,因此需要满足\(k=x\)\(k=y\)的操作状态不同(一个做另一个不做)
  • \(a_{x,y}<a_{y,x}\),此时不应该交换这两个位置,因此需要满足\(k=x\)\(k=y\)的操作状态相同(两个都做或者都不做)

因此现在我们就要确定这些操作的状态,这看似是个二分图的问题

但由于前面的操作优先级绝对高于后面的,因此是一个经典的DSU模型

我们引入负数的\(fa_x\)数组的值,若\(fa_x=-y(y>0)\),则表示\(x\)和它的的祖先节点\(y\)互斥关系,它们状态时相反的

同理,若\(fa_x=y(y>0)\),则表示\(x\)和它的祖先节点\(y\)的状态是相反的

在处理的时候我们定义\(fa_{-x}=-fa_x(x>0)\)即可

这种做法适用于二元状态下有绝对优先级限制条件下的关系限制问题,因为它通过两次负号会得正的原理来实现“我的敌人的敌人就是我的朋友”的操作,有点精巧

最后我们强制根节点要选,把所有\(getfa(x)>0\)的操作都做了即可

复杂度\(O(n^2\cdot alpha(n))\)

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#define RI register int
#define CI const int&
#define pb push_back
using namespace std;
const int N=1005;
int t,n,a[N][N],fa[N];
inline int getfa(CI x)
{
	if (x<0) return -getfa(-x); return fa[x]==x?x:fa[x]=getfa(fa[x]);
}
inline void merge(int x,int y)
{
	x=getfa(x); y=getfa(y); if (x<0) x=-x,y=-y;
	if (abs(x)!=abs(y)) fa[x]=y;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) fa[i]=i;
		for (i=1;i<=n;++i) for (j=1;j<=n;++j) scanf("%d",&a[i][j]);
		for (i=1;i<=n;++i) for (j=i;j<=n;++j)
		{
			if (a[i][j]>a[j][i]) merge(i,-j);
			if (a[i][j]<a[j][i]) merge(i,j);
		}
		for (i=1;i<=n;++i) if (getfa(i)>0)
		for (j=1;j<=n;++j) swap(a[i][j],a[j][i]);
		for (i=1;i<=n;++i) for (j=1;j<=n;++j)
		printf("%d%c",a[i][j]," \n"[j==n]);
	}
	return 0;
}

F. Lost Array

唤醒了尘封的记忆,随即发现FMT真是一个冷门又好用的东西

因为之前学OI的时候\(O(n\cdot 2^n)\)复杂度为标算的题写枚举子集的\(O(3^n)\)有时也能过(参考CTSC2017某题貌似)或者能拿很多部分分因此一直不会,直到今天花了大半个下午系统性地看了下快速变换之Fast Mobius Transform

回到这题,首先我们一眼发现单独考虑每个\(a_i\)\(b_{j,n}\)的贡献,根据类似于坐标系走路的组合数我们发现贡献次数是\(C_{(n-i)+(j-1)}^{j-1}\)

由于是异或,因此只要考虑其奇偶性,利用经典的卢卡斯定理我们有\(j-1\)\((n-i)+(j-1)\)的子集

但是这样不方便倒推,我们考虑转化一下,要求\((n-i)\operatorname{AND}(j-1)=0\),即\(n-i\)\(\sim(j-1)\)的子集

此时可以直接暴力枚举子集做到\(O(3^{\log_2 n})=O(n^{1.585})\)的复杂度,但是显然无法通过此题

考虑设\(m=2^k\)满足\(m\)为最小的满足\(m\ge n\)的数,不妨设\(a'_i=a_{n-i},b'_j = b_{\sim(j - 1)} = b_{(m - 1) - (j - 1)}\),于是我们发现\(b'\)\(a'\)Zeta-Transform后的结果,因此可以直接用Mobius Transform变回去

但是我们发现此时\(b'\)有一段\([0,m-n)\)是没有值的,我们需要找出它的值

在NTT的相关题目中也有类似的情况,我们可以用FMT本身来帮我们算出它们

具体地,设\(c_i\)\(b'_{(m-1)-i}\)的Zeta-Transform,先做一边FMT后再令\(c_k=0,k\in [m-n,m)\),在重新做一遍Mobius Transform就可以解出\(b'\)剩下的值了

复杂度\(O(n\log n)\)

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1000005;
int n,x,a[N],b[N],c[N],m;
inline void FMT(int* f)
{
	for (RI i=1;i<=m;i<<=1) for (RI j=0;j<=m;++j) if (i&j) f[j]^=f[i^j];
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d",&n),m=1;m<n;m<<=1);
	for (--m,i=1;i<=n;++i) scanf("%d",&x),c[i-1]=b[m^(i-1)]=x;
	for (FMT(c),i=0;i<=m-n;++i) c[m^i]=0;
	for (FMT(c),i=0;i<=m-n;++i) b[i]=c[m^i];
	for (FMT(b),i=1;i<=n;++i) a[i]=b[n-i];
	for (i=1;i<=n;++i) printf("%d ",a[i]);
	return 0;
}

Postscript

暑假快要结束了,感觉还是浑浑噩噩不知道干点什么

过两天要回EZ划划水,期待能有不一样的发现吧:)

posted @ 2022-08-08 23:07  空気力学の詩  阅读(29)  评论(0编辑  收藏  举报