• 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("");
    }
    
  • 相关阅读:
    BZOJ 4886 Lydsy1705月赛 叠塔游戏
    BZOJ 4552 TJOI2016&&HEOI2016 排序
    BZOJ 3702 二叉树
    BZOJ 4756 Usaco2017 Jan Promotion Counting
    BZOJ 4842 Neerc2016 Delight for a Cat
    BZOJ 1283 序列
    BZOJ 4819 SDOI2017 新生舞会
    BZOJ 1531 POI2005 Bank notes
    BZOJ 1925 SDOI2010 地精部落
    BZOJ WC2006 水管局长数据加强版
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15758201.html
Copyright © 2020-2023  润新知