Codeforces Global Round 18
F. LEGOndary Grandmaster
題目描述
解法
我手玩這個題都感覺很難受,其實是相鄰兩個相同才能操作這個限制特別噁心。一種常見的轉化思路是使得不符合限制的操作沒有意義,那麼我們把偶數位置翻轉,然後操作變成交換原來的兩個數,那麼 \(01,10\)(原來是 \(00,11\))交換之後有意義,但是 \(00,11\)(原來是 \(01,10\))交換之後無意義。
所以我們在對字串進行上述操作之後,當且僅當 \(1\) 的個數相同才可以變換。設 \(x_i,y_i\) 分別表示兩個字串第 \(i\) 個 \(1\) 的位置,那麼最優操作是一個匹配問題,此種情況的貢獻是:
\(\sum |x_i-y_i|\)
但是這樣還是難以優化到 \(O(n^2)\),我們切換算貢獻的主體,設 \(a_i,b_i\) 分別表示兩個字串前 \(i\) 位中 \(1\) 的個數,那麼每一種情況的貢獻是這樣的:
\(\sum |a_i-b_i|\)
那麼可以用計數 \(dp\) 預處理出 \(pre(i,j),suf(i,j)\),分別表示兩個字串前 \(i\) 位\(/\)後 \(i\) 位的 \(1\) 的個數差為 \(j\) 的方案數,那麼最終的答案是:
\[\sum_{i=1}^n\sum_{j=-i}^ipre(i,j)\times suf(i+1,-j)\times |j| \]時間複雜度 \(O(n^2)\)
#include <cstdio> const int M = 2005; const int MOD = 1e9+7; #define int long long int read() { int x=0,f=1;char c; while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;} while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();} return x*f; } int T,n,ans,pre[M][M<<1],suf[M][M<<1];char s[M],t[M]; void add(int &x,int y) {x=(x+y)%MOD;} int Abs(int x) {return x>0?x:-x;} int match(char c,int x) {return c=='?' || c==x+'0';} void work() { n=read();scanf("%s%s",s+1,t+1); for(int i=1;i<=n;i++) { if(s[i]!='?' && i%2) s[i]=((s[i]-'0')^1)+'0'; if(t[i]!='?' && i%2) t[i]=((t[i]-'0')^1)+'0'; } for(int i=0;i<=n+1;i++) for(int j=-n;j<=n;j++) pre[i][j+M]=suf[i][j+M]=0; pre[0][0+M]=suf[n+1][0+M]=1;ans=0; for(int i=1;i<=n;i++) for(int j=-n;j<=n;j++) for(int x=0;x<2;x++) for(int y=0;y<2;y++) if(match(s[i],x) && match(t[i],y)) add(pre[i][j+x-y+M],pre[i-1][j+M]); for(int i=n;i>=1;i--) for(int j=-n;j<=n;j++) for(int x=0;x<2;x++) for(int y=0;y<2;y++) if(match(s[i],x) && match(t[i],y)) add(suf[i][j+x-y+M],suf[i+1][j+M]); for(int i=1;i<=n;i++) for(int j=-n;j<=n;j++) add(ans,pre[i][j+M]*suf[i+1][-j+M]%MOD*Abs(j)); printf("%d\n",ans); } signed main() { T=read(); while(T--) work(); }
G. Maximum Adjacent Pairs
題目描述
給你一個長度為 \(n\) 的整數序列,你需要把其中所有的 \(0\) 替換成 \([1,n]\) 中的一個數,使得最終序列上相鄰相同值對的數量最大(出現位置不同的相同值對只計算一次)
舉例:\(1\ 1 \ 2 \ 2 \ 2 \ 1\) 的價值是 \(2\),值 \(1,2\) 都貢獻了一次。
\(n\leq 3\cdot 10^5,0\leq a_i\leq \min(n,600)\)
解法
建圖還是挺簡單的吧,我輕鬆想到的事情官方題解說了這麼久,所以我是圖論大師?
我把我建圖的思路將給你們聽:本題的題目很簡單,難點只有一個值只計算一次貢獻,這是一個難以解決的全侷限制,而且這個限制不便於拆分,所以我們考慮用圖論描述這個問題。
那麼我們要思考原問題中各元素在圖上的含義,一個值只貢獻一次告訴我們把值建成點會好一些,同時我們把 \(0\) 也建成點,\(0\) 的填法產生貢獻相當於和對應的值匹配,我們建立邊就可以決策這個過程。更具體地可以考慮原序列上連續的一段 \(0\),根據貪心原理只有連續段邊上的 \(0\) 才會和值匹配,其他的 \(0\) 都另尋它路了,簡單討論一下:
- 如果連續段的長度為偶數,那麼我們建立兩個代表 \(0\) 的點 \(x,y\),首先將 \(x,y\) 連一條邊代表他們可以自己匹配,然後我們將 \(x\) 連向左邊的值,\(y\) 連向右邊的值。
- 如果連續段的長度為奇數,那麼我們建立一個代表 \(0\) 的點 \(x\),把它和左邊的值和右邊的值都連邊。
然後我們跑一般最大圖匹配就行了,因為圖很稀疏所以可以信仰跑。
這時候寫一發帶花樹就會發現自己 \(\tt T\) 了,這是因為每次 \(\tt bfs\) 的時候暴力清空使你的複雜度達到了穩定 \(O(n^2)\),我們可以把所有經過修改的點存在 \(\tt vector\) 裡面,最後再還原即可,這樣複雜度就變成了玄學,然後隨便跑過。
官方題解給出了一種更為穩定的做法,我們可以首先忽略偶數段 \((x,y)\) 的邊,然後拿這個圖去跑二分圖最大匹配,然後把在二分圖最大匹配中的點拎出來考慮 \((x,y)\) 的邊跑一般圖最大匹配,這樣點數和邊數是 \(600\) 級別的就很舒服。
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 500005;
#define pb push_back
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,tot,tim,a[M],f[M],use[M],hav[M],id[M];
int d[M],pre[M],mat[M],fa[M],bz[M],bp[M];
vector<int> V;
struct edge
{
int v,next;
}e[M<<2];
void add(int u,int v)
{
e[++tot]=edge{v,f[u]},f[u]=tot;
e[++tot]=edge{u,f[v]},f[v]=tot;
}
int find(int x)
{
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
int lca(int x,int y)
{
tim++;x=find(x);y=find(y);
while(bp[x]!=tim)
{
bp[x]=tim;
x=find(pre[mat[x]]);
if(y) swap(x,y);
}
return x;
}
void make(int x,int y,int w)
{
while(find(x)!=w)
{
pre[x]=y;y=mat[x];
if(bz[y]==2) bz[y]=1,d[++d[0]]=y,V.pb(y);
if(find(x)==x) fa[x]=w,V.pb(x);
if(find(y)==y) fa[y]=w,V.pb(y);
x=pre[y];
}
}
void match(int x,int y)
{
mat[x]=y;mat[y]=x;
V.pb(x);V.pb(y);
}
int bfs(int rt)
{
for(auto x:V) fa[x]=x,bz[x]=pre[x]=0;V.clear();
d[d[0]=1]=rt;bz[rt]=1;int l=0;V.pb(rt);
while(l<d[0])
{
int u=d[++l];
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(find(u)==find(v) || bz[v]==2) continue;
if(!bz[v])
{
bz[v]=2;pre[v]=u;V.pb(v);
if(!mat[v])
{
for(int x=v,y;x;x=y)
y=mat[pre[x]],match(x,pre[x]);
return 1;
}
V.pb(mat[v]);
bz[mat[v]]=1;d[++d[0]]=mat[v];
}
else
{
int w=lca(u,v);
make(u,v,w);
make(v,u,w);
}
}
}
return 0;
}
void rep(int l,int r)
{
for(int i=l;i<=r;i+=2)
{
while(hav[k]) k++;
a[i]=a[i+1]=k++;
}
}
signed main()
{
n=read();k=1;m=600;
for(int i=1;i<=n;i++)
{
a[i]=read();hav[a[i]]=1;
if(a[i]==a[i-1]) use[a[i]]=1;
}
for(int i=1,j;i<=n;i=j)
{
if(a[i]) {j=i+1;continue;}j=i;
while(j<=n && a[j]==0) j++;
if((j-i)%2==0)
{
m++;if(i>1 && !use[a[i-1]]) add(m,a[i-1]);
m++;if(j<=n && !use[a[j]]) add(m,a[j]);
add(m-1,m);
}
else
{
m++;if(i>1 && !use[a[i-1]]) add(m,a[i-1]);
if(j<=n && !use[a[j]]) add(m,a[j]);
}
id[i]=m;
}
for(int i=1;i<=m;i++) fa[i]=i;
for(int i=1;i<=m;i++)
if(!mat[i]) bfs(i);
for(int i=1,j=1;i<=n;i=j+1)
{
j=i;if(!id[i]) continue;
while(j<=n && a[j]==0) j++;
j--;int o=id[i];
if((j-i+1)%2==0)
{
if(mat[o-1]==o) rep(i,j);
else//matched with color
{
a[i]=a[i-1];a[j]=a[j+1];
rep(i+1,j-1);
}
}
else
{
if(i>1 && mat[o]==a[i-1])
a[i]=a[i-1],rep(i+1,j);
else a[j]=a[j+1],rep(i,j-1);
}
}
for(int i=1;i<=n;i++)
printf("%d ",a[i]?a[i]:1);
puts("");
}