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\) 的方案数,那么最终的答案是:
时间复杂度 \(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("");
}