1. 程式人生 > 其它 >Codeforces Global Round 18

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("");
}