部分CF Div2 E/F题解合集

CF1391E

一道挺套路的构造。

先选出一棵\(dfs\)树,我们把根为链顶的重链弄下来,如果长度大于\(\lceil \frac{n}{2}\rceil\)就输出。

否则我们把重链的前某一半删掉,并且使得剩下的重链上的深度最小点的子树大小小于等于\(\lceil \frac{n}{2}\rceil\)

这样树就裂成了若干子树,每棵子树大小都小于总点数一半。因为\(dfs\)树上全是返祖边,所以这些子树中没有边相连。

这样我们每次从两棵不同子树中拿两个点凑一对,容易发现这是合法的,每次从两个点数最大的子树拿两个点就能最大化点的对数了。用堆存一下即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1000005;
int n,m,k,t,u,v,head[N],Next[N*2],adj[N*2],son[N],siz[N],vis[N],tot,i,j,fa[N];
vector<int> g[N];
struct str{
	int x;
};
bool operator <(str a,str b)
{
	return g[a.x].size()<g[b.x].size();
}
priority_queue<str> q;
void Push(int u,int v)
{
	Next[++k]=head[u];
	head[u]=k;
	adj[k]=v;
}
void dfs(int i,int f)
{
	int j;
	siz[i]=vis[i]=1;
	son[i]=0;
	fa[i]=f;
	for(j=head[i];j;j=Next[j])
		if(!vis[adj[j]])
		{
			dfs(adj[j],i);
			siz[i]+=siz[adj[j]];
			if(siz[adj[j]]>siz[son[i]])
				son[i]=adj[j];
		}
}
void dfs2(int i)
{
	int j;
	vis[i]=1;
	g[tot].push_back(i);
	for(j=head[i];j;j=Next[j])
		if(!vis[adj[j]])
			dfs2(adj[j]);
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d %d",&n,&m);
		for(i=1;i<=n;++i)
			vis[i]=head[i]=0;
		k=0;
		for(i=1;i<=m;++i)
		{
			scanf("%d %d",&u,&v);
			Push(u,v);
			Push(v,u);
		}
		dfs(1,0);
		for(i=1;i<=n;++i)
			vis[i]=0;
		int len=0;
		for(i=1;i;i=son[i])
			++len;
		if(len>=(n+1)/2)
		{
			puts("PATH");
			printf("%d\n",len);
			for(i=1;i;i=son[i])
				printf("%d ",i);
			printf("\n");
			continue;
		}
		tot=len=0;
		for(i=1;i;i=son[i])
		{
			if(siz[i]<=(n-len)/2)
			{
				++tot;
				g[tot].clear();
				dfs2(i);
				break;
			}
			++len;
			vis[i]=1;
			for(j=head[i];j;j=Next[j])
				if(!vis[adj[j]]&&fa[adj[j]]==i&&adj[j]!=son[i])
				{
					++tot;
					g[tot].clear();
					dfs2(adj[j]);
				}
		}
		for(i=1;i<=tot;++i)
			q.push((str){i});
		puts("PAIRING");
		printf("%d\n",((n+1)/2+1)/2);
		int t=(n+1)/2;
		while(q.size()>=2&&t>0)
		{
			str x=q.top();
			q.pop();
			str y=q.top();
			q.pop();
			printf("%d %d\n",*g[x.x].rbegin(),*g[y.x].rbegin());
			g[x.x].pop_back();
			g[y.x].pop_back();
			if(g[x.x].size())
				q.push(x);
			if(g[y.x].size())
				q.push(y);
			t-=2;
		}
		while(!q.empty())
			q.pop();
	}
}

CF1393E2

我们可以设计一个简单的\(DP\),设\(f_{ij}\)表示到第\(i\)个字符串为止,删去了第\(j\)个字符,且满足条件的方案数。

这样就能过E1。

对于E2我们要给每个字符串删掉一个字符后排序,并且对于相邻字符串,假设分别删掉了第\(i\)个和第\(j\)个字符(或不删),快速比较它们的大小。

我们直接看第二个问题,显然第一个是第二个问题的弱化版。

假设\(i<j\),我们的比较分为三部分:

1.比较\(a[1..i-1]\)\(b[1..i-1]\)

2.比较\(a[i+1..j]\)\(b[i..j-1]\)

3.比较\(a[j+1..|a|]\)\(b[j+1..|b|]\)

以上三者只要求出\(lcp\)就能找到两字符串不同的第一个位置。

我们发现我们只要求出每一个\(a\)\(b\)从相同位置开始的后缀的\(lcp\),与\(a\)整体往右移动一格的后缀的\(lcp\),与\(a\)整体往左移动一格的后缀的\(lcp\)

同样以第一种情况为例,我们有一个显然的性质,设\(lcp_i\)表示从\(i\)开始的后缀的\(lcp\),则\(lcp_i\geq lcp_{i-1}-1\)

这样我们暴力按顺序比较就是线性,然后我们就可以\(O(1)\)比较两字符串删去一个字符的大小了。

给每个字符串删掉一个字符后排序可以直接\(sort\),常数很小,但也可以线性。

然后我们给排序后的相邻字符串弄个指针扫一下就行。

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

#include<bits/stdc++.h>
using namespace std;
const int N=1000005;
const int M=1000000007;
int n,i,lp[N],j,a[N],la[N],ln,lcp[3][N],t;
char c[N],lc[N];
int dp[N],ldp[N];
bool cmp(int u,int v)
{
	if(lp[min(u,v)]>=abs(v-u))
		return u<v;
	int fl=0;
	if(u>v)
	{
		swap(u,v);
		fl=1;
	}
	return (c[u+lp[u]+1]<c[u+lp[u]])^fl;
}
int main()
{
	scanf("%d",&t);
	for(int m=1;m<=t;++m)
	{
		for(i=1;i<=n;++i)
			c[i]=0;
		scanf("%s",c+1);
		n=strlen(c+1);
		for(i=1;i<=n;)
		{
			for(j=i;c[i]==c[j];++j);
			int p=j;
			for(j=i;c[i]==c[j];++j)
				lp[j]=p-j-1;
			i=j;
		}
		for(i=1;i<=n;++i)
			a[i]=i;
		sort(a+1,a+1+n,cmp);
		for(i=1;i<=n;++i)
			if(a[i]+lp[a[i]]+1<=n&&c[a[i]+lp[a[i]]+1]>c[a[i]+lp[a[i]]])
			{
				for(j=n+1;j>i;--j)
					a[j]=a[j-1];
				break;
			}
		a[i]=0;
		//for(i=1;i<=n+1;++i)
		//	cout<<a[i]<<' ';
		//cout<<endl;
		if(m==1)
		{
			for(i=1;i<=n+1;++i)
				dp[i]=i;
		}
		else
		{
			for(int f=-1;f<=1;++f)
				for(i=1;i<=ln;++i)
				{
					lcp[f+1][i]=max(lcp[f+1][i-1]-1,0);
					while(i+lcp[f+1][i]<=ln&&i+f+lcp[f+1][i]<=n&&lc[i+lcp[f+1][i]]==c[i+f+lcp[f+1][i]])
						++lcp[f+1][i];
				}
			int l=1;
			for(i=1;i<=n+1;++i)
			{
				while(l<=ln+1)
				{
					//cout<<'#'<<la[l]<<' '<<a[i]<<endl;
					if(la[l]!=0&&a[i]!=0)
					{
						if(la[l]<a[i])
						{
							if(lcp[1][1]<la[l]-1)
							{
								if(lc[1+lcp[1][1]]>c[1+lcp[1][1]])
									break;
							}
							else
								if(lcp[0][la[l]+1]<a[i]-la[l])
								{
									if(lc[la[l]+1+lcp[0][la[l]+1]]>c[la[l]+lcp[0][la[l]+1]])
										break;
								}
								else
									if(lc[a[i]+1+lcp[1][a[i]+1]]>c[a[i]+1+lcp[1][a[i]+1]])
										break;
						}
						if(la[l]==a[i])
						{
							if(lcp[1][1]<la[l]-1)
							{
								if(lc[1+lcp[1][1]]>c[1+lcp[1][1]])
									break;
							}
							else
							{
								if(lc[a[i]+1+lcp[1][a[i]+1]]>c[a[i]+1+lcp[1][a[i]+1]])
									break;
							}
						}
						if(la[l]>a[i])
						{
							if(lcp[1][1]<a[i]-1)
							{
								if(lc[1+lcp[1][1]]>c[1+lcp[1][1]])
									break;
							}
							else
								if(lcp[2][a[i]]<la[l]-a[i])
								{
									if(lc[a[i]+lcp[2][a[i]]]>c[a[i]+1+lcp[2][a[i]]])
										break;
								}
								else
									if(lc[la[l]+1+lcp[1][la[l]+1]]>c[la[l]+1+lcp[1][la[l]+1]])
										break;
						}
					}
					if(la[l]!=0&&a[i]==0)
					{
						if(lcp[1][1]<la[l]-1)
						{
							if(lc[1+lcp[1][1]]>c[1+lcp[1][1]])
								break;
						}
						else
							if(lc[la[l]+1+lcp[0][la[l]+1]]>c[la[l]+lcp[0][la[l]+1]])
								break;
					}
					if(la[l]==0&&a[i]!=0)
					{
						if(lcp[1][1]<a[i]-1)
						{
							if(lc[1+lcp[1][1]]>c[1+lcp[1][1]])
								break;
						}
						else
							if(lc[a[i]+lcp[2][a[i]]]>c[a[i]+1+lcp[2][a[i]]])
								break;
					}
					if(la[l]==0&&a[i]==0)
						if(lc[1+lcp[1][1]]>c[1+lcp[1][1]])
							break;
					++l;
				}
				dp[i]=ldp[l-1];
				//cout<<l<<' ';
			}
			//cout<<endl;
			for(i=1;i<=n+1;++i)
				dp[i]=(dp[i-1]+dp[i])%M;
		}
		for(i=1;i<=ln;++i)
			lc[i]=0;
		for(i=1;i<=n;++i)
			lc[i]=c[i];
		for(i=1;i<=n+1;++i)
		{
			la[i]=a[i];
			ldp[i]=dp[i];
		}
		ln=n;
	}
	cout<<dp[n+1]<<endl;
}

CF1379F1

\(n\times m\)显然是能放的最大值。

我们按\(4\times 4\)的格子分组,显然每一组只能放左上或右下。

我们发现对于任意一种放置方案,我们可以找到一条折线把这些\(4\times 4\)的区域分开,使得左上角都是靠左上放的,右下角都是靠右下放的。

每次ban掉一个位置就相当于钦定只能放左上/右下,显然一个只能放左上的块在只能放右下的块的左上方就是不行的。

我们二分一个答案,然后直接判断,找出YES和NO的分界线即可。

#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int n,m,q,x[N],y[N],i,l,r;
struct str{
	int x,y,c;
}a[N];
bool cmp(str a,str b)
{
	if(a.x!=b.x)
		return a.x<b.x;
	if(a.y!=b.y)
		return a.y<b.y;
	return a.c>b.c;
}
bool check(int m)
{
	int i,mn=1<<30;
	for(i=1;i<=m;++i)
		a[i]=(str){(x[i]+1)/2,(y[i]+1)/2,x[i]&1};
	sort(a+1,a+1+m,cmp);
	for(i=1;i<=m;++i)
		if(a[i].c==1)
			mn=min(mn,a[i].y);
		else
			if(a[i].y>=mn)
				return false;
	return true;
}
int main()
{
	scanf("%d %d %d",&n,&m,&q);
	for(i=1;i<=q;++i)
		scanf("%d %d",&x[i],&y[i]);
	l=1,r=q+1;
	while(l<r)
	{
		int mid=l+r>>1;
		if(check(mid))
			l=mid+1;
		else
			r=mid;
	}
	for(i=1;i<l;++i)
		puts("YES");
	for(i=l;i<=q;++i)
		puts("NO");
}

对于F2其实就是套一个数据结构,线段树/平衡树就能处理

CF1372F

玄学题

讲一个感觉很乱搞的做法

我们设\(f(l,r)\)表示在得知该区间众数的情况下求出\(l~r\)的范围内的答案,设众数出现次数为\(cnt\),则我们每\(cnt\)分一块,每一块分别询问,显然必然有一块能得到\(l\sim r\)的众数,并且众数还是触碰到块的分界线,这样众数的出现位置就确定了。

其余部分我们递归处理即可,这样看似一个颜色会被切很多份,但实际上在被切一刀就出现在首末了,首的颜色不可能被切,末的颜色被切一下直接发现某一整块都是同一颜色,因此实际操作是不多的

合理分析+精细实现可能就是\(4n\)

#include<bits/stdc++.h>
using namespace std;
int n,x,y,i,ans[200005];
void color(int l,int r,int x)
{
	for(int i=l;i<=r;++i)
		ans[i]=x;
}
void dfs(int l,int r,int x,int y)
{
	int j;
	if(l>r)
		return;
	if(y==r-l+1)
	{
		color(l,r,x);
		return;
	}
	int a[(r-l+1)/y+5],b[(r-l+1)/y+5],k=0;
	for(j=l;j<=r;j+=y)
	{
		printf("? %d %d\n",j,min(j+y-1,r));
		fflush(stdout);
		int p,q;
		scanf("%d %d",&p,&q);
		a[++k]=p,b[k]=q;
	}
	a[k+1]=b[k+1]=0;
	int al,ar;
	for(j=1;j<=k;++j)
		if(a[j]==x)
		{
			if(a[j+1]==x||b[j]==y)
			{
				al=l+j*y-b[j],ar=l+j*y-b[j]+y-1;
				color(al,ar,x);
			}
			else
			if(j==k&&b[j]==(r-l)%y+1)
			{
				al=r-y+1,ar=r;
				color(al,ar,x);
			}
			else
			{
				printf("? %d %d\n",l+j*y-1,l+j*y-1);
				fflush(stdout);
				int p,q;
				scanf("%d %d",&p,&q);
				if(p==x)
				{
					al=l+j*y-b[j],ar=l+j*y-b[j]+y-1;
					color(al,ar,x);
				}
				else
				{
					al=l+j*y-y+b[j]-y,ar=l+j*y-y+b[j]-1;
					color(al,ar,x);
				}
			}
			break;
		}
	for(j=1;j<=k;++j)
	{
		int ul=l+j*y-y,ur=min(l+j*y-1,r);
		if(ul>=al&&ur<=ar)
			continue;
		if(ul<=al&&ur>=al)
		{
			if(a[j]==x)
			{
				printf("? %d %d\n",ul,al-1);
				fflush(stdout);
				int p,q;
				scanf("%d %d",&p,&q);
				dfs(ul,al-1,p,q);
			}
			else
				dfs(ul,al-1,a[j],b[j]);
			continue;
		}
		if(ul<=ar&&ur>=ar)
		{
			if(a[j]==x)
			{
				printf("? %d %d\n",ar+1,ur);
				fflush(stdout);
				int p,q;
				scanf("%d %d",&p,&q);
				dfs(ar+1,ur,p,q);
			}
			else
				dfs(ar+1,ur,a[j],b[j]);
			continue;
		}
		dfs(ul,ur,a[j],b[j]);
	}
}
int main()
{
	scanf("%d",&n);
	printf("? %d %d\n",1,n);
	fflush(stdout);
	scanf("%d %d",&x,&y);
	dfs(1,n,x,y);
	printf("! ");
	for(i=1;i<=n;++i)
		printf("%d ",ans[i]);
}

CF1372E

我们发现当两列之间有一种多种区间同时跨越它们时,显然这些区间全部选择同一列,比分散地选择两列要优。

我们可以设计一个\(DP\),设\(f_{ij}\)表示当\(i-1\)列与\(j+1\)列全是1时,\(i\sim j\)的最大价值

我们枚举一个断点,把这一列中不跨越\(i-1\)\(j+1\)的区间全填1,然后分成了两个子问题,复杂度\(O(n^3)\)

#include<bits/stdc++.h>
using namespace std;
typedef long double ld;
const int N=1005;
const ld pi=3.1415926535897932384626;
int n,i,j,k,f[105][105],m,gl[105][105],gr[105][105],p,l,r;
int dfs(int l,int r)
{
	if(l>r)
		return 0;
	if(f[l][r]!=-1)
		return f[l][r];
	int i,mx=0;
	for(i=l;i<=r;++i)
	{
		int s=0;
		for(j=1;j<=n;++j)
			if(gl[j][i]>=l&&gr[j][i]<=r)
				++s;
		mx=max(mx,s*s+dfs(l,i-1)+dfs(i+1,r));
	}
	return f[l][r]=mx;
}
int main()
{
	scanf("%d %d",&n,&m);
	memset(f,-1,sizeof(f));
	for(i=1;i<=n;++i)
	{
		scanf("%d",&p);
		for(j=1;j<=p;++j)
		{
			scanf("%d %d",&l,&r);
			for(k=l;k<=r;++k)
			{
				gl[i][k]=l;
				gr[i][k]=r;
			}
		}
	}
	memset(f,-1,sizeof(f));
	cout<<dfs(1,m);
}
posted @ 2020-08-11 13:42  夜螢光  阅读(276)  评论(0编辑  收藏  举报