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("");
}
posted @ 2022-01-02 20:19  C202044zxy  阅读(427)  评论(0编辑  收藏  举报