部分CF Div2 E/F题解合集
一道挺套路的构造。
先选出一棵\(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();
}
}
我们可以设计一个简单的\(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;
}
\(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其实就是套一个数据结构,线段树/平衡树就能处理
玄学题
讲一个感觉很乱搞的做法
我们设\(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]);
}
我们发现当两列之间有一种多种区间同时跨越它们时,显然这些区间全部选择同一列,比分散地选择两列要优。
我们可以设计一个\(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);
}