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