Codeforces Round #815 (Div. 2)

Preface

休假强续了一天?刚好找一场题目少的Div.2做一下

感觉今天状态不是很好啊,各种傻逼题秒不掉想各种奇怪东西……


A. Burenka Plays with Fractions

首先不难发现答案只可能是\({0,1,2}\)\(0\)的情况非常显然,考虑找出\(1\)的情况

此时显然有\(ad|cb\)\(cb|ad\),直接判断即可,注意\(a/c=0\)的情形

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
int t,a,b,c,d;
inline int gcd(CI n,CI m)
{
	return m?gcd(m,n%m):n;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		scanf("%d%d%d%d",&a,&b,&c,&d); 
		int g=gcd(a,b); a/=g; b/=g; g=gcd(c,d); c/=g; d/=g;
		if (a==c&&b==d) { puts("0"); continue; }
		if (!a||!c) { puts("1"); continue; }
		if (1LL*a*d%(1LL*c*b)==0||1LL*c*b%(1LL*a*d)==0) puts("1"); else puts("2");
	}
	return 0;
}

B. Interesting Sum

考虑找出最大的两个数和最小的两个数,答案就由它们构成

证明:设这四个数的下标为\(i,j,p,q\),不难发现必然存在某种情形使得\((i,p).(i,q),(j,p),(j,q)\)中间不存在这四个数中的其它下标,选取这个区间即可,同时这个答案必然是最大的

#include<cstdio>
#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; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
		sort(a+1,a+n+1); printf("%d\n",a[n]+a[n-1]-a[1]-a[2]);
	}
	return 0;
}

C. Corners

不难发现在进行了第一次操作后,我们必然可以在接下来的每一次操作中每次只减少一个1来做到清空整个矩阵

那么现在问题就是枚举找出第一次操作的位置使得其1的个数尽量少即可,注意一个技巧我们每次可以枚举\(2\times 2\)的矩形,具体实现看代码

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=505;
int t,n,m,sum,a[N][N];
inline int getnum(void)
{
	char ch; while (ch=getchar(),ch!='0'&&ch!='1'); return ch-48;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d%d",&n,&m),sum=0,i=1;i<=n;++i)
		for (j=1;j<=m;++j) a[i][j]=getnum(),sum+=a[i][j];
		int cur=4; for (i=1;i<=n-1;++i) for (j=1;j<=m-1;++j)
		cur=min(cur,a[i][j]+a[i+1][j]+a[i][j+1]+a[i+1][j+1]);
		printf("%d\n",sum-max(0,cur-2));
	}
	return 0;
}

D1. Xor-Subsequence (easy version)

首先容易想到一个DP,设\(f_i\)表示以\(i\)结尾时最长长度是多少,显然有转移:

\[f_j=\max(f_j,f_i+1)\ \ \ \ \ \ (j>i\and a_i\oplus j<a_j\oplus i) \]

朴素的转移是\(O(n^2)\)的,但我们稍加观察就会发现由于异或\(x-y\le x\oplus y\le x+y\),因此由于\(a_i\le 200\),所以\(j\)只要枚举到\(i+400\)即可

单组数据复杂度\(O(400\times n)\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005;
int t,n,a[N],f[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=0;i<n;++i) scanf("%d",&a[i]),f[i]=1;
		for (i=0;i<n-1;++i) for (j=i+1;j<min(n,i+400);++j)
		if ((a[i]^j)<(a[j]^i)) f[j]=max(f[j],f[i]+1);
		int ans=f[0]; for (i=1;i<n;++i) ans=max(ans,f[i]);
		printf("%d\n",ans);
	}
}

D2. Xor-Subsequence (hard version)

考虑到\(a_i\oplus j<a_j\oplus i\)中的小于号很麻烦,因此不妨考虑,在二进制下若\(x<y\),则必然存在一个\(k\),满足\(x,y\)的前\(k\)位相等,在第\(k+1\)位上\(x\)\(0\)\(y\)\(1\)

那么我们发现当前\(k\)\(a_i\oplus j=a_j\oplus i\)时,转化一下就有\(a_i\oplus i=a_j\oplus j\),这样我们就可以用0/1Trie来维护\(a_i\oplus i\)

接下来我们发现在第\(k+1\)位上,\(a_i=j\)\(a_j\ne i\),我们可以枚举一下所有可能的四种情况,发现此时在第\(k+1\)位上,\(a_i\oplus i\ne a_j\oplus j\)

那么就可以很容易地在0/1Trie上跑DP了,对于树上的每个点维护下这一位上\(i\)\(0/1\)的最大值,询问的时候在比较下\(a_i\)的这一位即可

单组数据复杂度\(O(n\log a_i)\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005;
int t,n,x,mx,ans;
class Trie
{
	private:
		int ch[N*32][2],f[N*32][2],tot;
	public:
		inline void clear(void)
		{
			for (RI i=0;i<=tot;++i) ch[i][0]=ch[i][1]=f[i][0]=f[i][1]=0; tot=0;
		}
		inline void insert(CI x,CI y,CI v)
		{
			int now=0; for (RI i=30;~i;--i)
			{
				int bit=(x>>i)&1; if (!ch[now][bit]) ch[now][bit]=++tot; now=ch[now][bit];
				f[now][(y>>i)&1]=max(f[now][(y>>i)&1],v);
			}
		}
		inline int query(CI x,CI y,int ret=1)
		{
			int now=0; for (RI i=30;~i;--i)
			{
				int bit=(x>>i)&1,trs=ch[now][bit^1]; ret=max(ret,f[trs][(y>>i)&1^1]+1);
				if (!ch[now][bit]) break; now=ch[now][bit];
			}
			return ret;
		}
}T;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d",&n),T.clear(),ans=i=0;i<n;++i)
		scanf("%d",&x),mx=T.query(x^i,x),T.insert(x^i,i,mx),ans=max(ans,mx);
		printf("%d\n",ans);
	}
}

E. Misha and Paintings

没想到关键性质直接无从下手了的说

首先统计出原来矩阵中的颜色数目,设为\(cnt\),考虑当\(cnt\le k\)时答案就是\(k-cnt\),因为每次操作至多增加一种颜色

\(cnt>k\),那么答案只可能是\(1/2\),下面给出证明

首先我们找到一个最大的边长为\(L\)的方阵,使得方阵外部的颜色种类大于等于\(k-1\)

若恰好为\(k-1/k\),则答案为\(1\),因为我们可以通过修改这个方阵的颜色是否和外部的某种颜色相同来完成

若种类大于\(k\),我们设这个方阵的右下角坐标为\((x,y)\),那么我们取\((x+1,y+1)\)为右下角来构造一个新的方阵

逐渐增大第二个方阵的边长,显然每次在两个方阵外部的颜色数量会减少\(0/1/2\),那么必然会存在某个方阵使得外部的颜色数量等于\(k-1/k\)(以下图片引自知乎dalaoAnder题解

那么接下来我们就要考虑怎么判断答案为\(1\)的情况了,即判断是否存在某个方阵,使得方阵外部的颜色种类恰好等于\(k-1/k\)

直接暴力枚举方阵的端点和边长再判断显然是不现实的,我们考虑对于每种颜色,找出它的四个边界(即横坐标的最大最小值,纵坐标的最大最小值),那么若某个正方形要完全包含这种颜色,那么它至少要包含这个矩形

再稍作考虑,我们先枚举一个正方形的边长\(len\),然后我们可以尝试求出对于每种颜色,要完全包含它的正方形的左上角的可能位置

如下图黑色点是某种颜色出现的所有位,当\(len=5\)时,蓝色的部分就是可能的位置

由于这些可能的位置一定时某个子矩形,因此我们可以用二维前缀和来记录,最后得到每个格子的数字就表示以该点为左上角做边长为\(len\)的正方形能完全覆盖的颜色种类数目,就可以解决问题了

总复杂度\(O(n^3)\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=505;
int n,k,a[N][N],s[N][N],minx[N*N],maxx[N*N],miny[N*N],maxy[N*N],cnt; bool vis[N*N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j,len; for (scanf("%d%d",&n,&k),i=1;i<=n*n;++i)
	vis[i]=maxx[i]=maxy[i]=0,minx[i]=miny[i]=n;
	for (i=1;i<=n;++i) for (j=1;j<=n;++j)
	{
		scanf("%d",&a[i][j]); int c=a[i][j]; if (!vis[c]) vis[c]=1,++cnt;
		minx[c]=min(minx[c],i); maxx[c]=max(maxx[c],i);
		miny[c]=min(miny[c],j); maxy[c]=max(maxy[c],j);
	}		
	if (cnt<=k) return printf("%d\n",k-cnt),0;
	for (len=0;len<=n;++len)
	{
		for (i=1;i<=n;++i) for (j=1;j<=n;++j) s[i][j]=0;
		for (i=1;i<=n*n;++i) if (vis[i]&&maxx[i]-minx[i]<=len&&maxy[i]-miny[i]<=len)
		{
			int Minx=max(1,maxx[i]-len),Maxx=minx[i],Miny=max(1,maxy[i]-len),Maxy=miny[i];
			++s[Minx][Miny]; --s[Minx][Maxy+1]; --s[Maxx+1][Miny]; ++s[Maxx+1][Maxy+1];
		}
		for (i=1;i<=n;++i) for (j=1;j<=n;++j) s[i][j]+=s[i-1][j];
		for (i=1;i<=n;++i) for (j=1;j<=n;++j) s[i][j]+=s[i][j-1];
		for (i=1;i<=n-len;++i) for (j=1;j<=n-len;++j)
		if (cnt-s[i][j]==k||cnt-s[i][j]==k-1) return puts("1"),0;
	}
	return puts("2"),0;
}

Postscript

明天要上网课7:20还要早自习是真的淦,完全不知道该读什么的说

不过据说你电的管理和高中类似,早晚自习和周测都是有的,确实有点难绷

希望能像学校期望(压力)的那样大一把四六级给过了吧,说实话我是真的对英语无感,早丢早解脱

但还是逃不掉做题和看题解得啃英文版本的痛苦捏

posted @ 2022-09-05 22:04  空気力学の詩  阅读(41)  评论(1编辑  收藏  举报