1. 程式人生 > 實用技巧 >部分CF Div2 E/F題解合集

部分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);
}