Codeforces Round 1031 (Div. 2) F
Codeforces Round 1031 (Div. 2) F
F
题意:给定两个长度为\(n\)的数组 \(a\), \(b\),我们能够进行一种操作任意次:在\([1,n]\)选择一个\(i\),然后交换\(a_i,b_i\) 。然后定义\(f(c)\)表示数组\(c\)不同数字的数量。要求找到经过任意次操作后最大的\(f(a)+f(b)\),同时要输出最终得到这个答案的数组\(a,b\)。
我做这题的时候,思路很不好。做的过程我其实很不满意。下面我就说说我认为这题应该怎么去做。
首先,肯定是考虑最大的答案如何得到。我们考虑一个数字在这两个数组中的出现次数的不同情况。
对于数字\(a_i\),假如它只出现过一次,那么毫无疑问的,它对于最终答案的贡献是1 。
假如它出现了两次及以上,那最终我们是否一定有方法让他贡献为2?答案是肯定的。这一点是比较难想到的。但是一旦尝试去证明这一点,就可以发现肯定是能够做到的。
所以我们其实直接就能够计算出最终的答案。那么现在的问题就是,如何构造结果?
下面就是我认为我比较需要学习的思路了。
我们现在需要构造答案,主要需要构造成什么样?那就是不管怎么样,如果一个数字有两个及以上,一定需要最终有至少一个在上面,至少一个在下面。
如此多的位置,如此多的数字,要如何保证最终结果要符合这种要求?
很明显,需要转化为更加抽象的条件
我们考虑图。
我们对于每个\(a_i,b_i\),建立一条从点\(a_i\)指向\(b_i\)的无向边。那么上面的条件就变为了,对于所有度大于1的节点,我们需要给所有边定向来使得它至少有一个入读,一个出度。
依旧尝试考虑最简洁的方法,那就是树。对于一个节点,我们只去尝试保留一个入度,对于剩下的度,就全部定义为出度。
那么,会有哪个度不为1的节点,只有出度?
只可能是根节点。
再基于此去考虑之后的步骤,如果根节点没有入度且度不为1,我们就考虑让他某一个子树内的所有边都反向。就能够确定一定能够满足要求。
然后我们再根据这些边的方向来确定哪个数字在\(a_i\)的位置,哪个数字在\(b_i\)的位置。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int a=0,b=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
return a*b;
}
int n,a[400001],b[400001],vis[400001],deg[400001],ru[400001];
int head[400001],tot=2,dep[400001],vis2[400001];
struct edge
{
int next,to,back,used;
}e[400001];
inline void add(int i,int j,int back)
{
e[tot].next=head[i];
e[tot].to=j;
e[tot].back=back;
head[i]=tot++;
}
void dfs(int x,int fa,int ine)
{
dep[x]=dep[fa]+1;
vis[x]=1;
for(int i=head[x];i!=0;i=e[i].next)
{
int u=e[i].to;
if(i==(ine^1))continue;
if(e[i^1].used==1)continue;
deg[x]++;
e[i].used=1;
// cout<<x<<' '<<u<<endl;
ru[u]++;
if(vis[u])//返程边
continue;
dfs(u,x,i);
}
}
map<int,int> tag;
void change(int x,int fa,int ine)
{
tag[x]=1;
for(int i=head[x];i!=0;i=e[i].next)
{
int u=e[i].to;
if(i==(ine^1))continue;
// cout<<x<<' '<<u<<"???????????????\n";
if(dep[u]<dep[x])continue;
if(tag[u])continue;
// if(u==13)cout<<"!!!!!!????\n";
change(u,x,i);
}
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("2.out","w",stdout);
int T=read();
while(T--)
{
tot=2;
n=read();
for(int i=0;i<=(n<<1)+10;i++)
head[i]=ru[i]=deg[i]=vis[i]=vis2[i]=dep[i]=0,e[i]={0,0,0,0};
tag.clear();
for(int i=1;i<=n;i++)
a[i]=read();
for(int i=1;i<=n;i++)
b[i]=read();
for(int i=1;i<=n;i++)
{
add(a[i],b[i],i);
add(b[i],a[i],i);
}
for(int i=1;i<=n;i++)
{
if(vis[a[i]])continue;
dfs(a[i],0,0);
// cout<<a[i]<<' '<<deg[a[i]]<<' '<<ru[a[i]]<<endl;
if(deg[a[i]]>1&&ru[a[i]]==0)
{
int now=head[a[i]];
vis2[a[i]]=1;
change(e[now].to,a[i],now);
}
}
for(int i=2;i<tot;i++)
{
if(e[i].used==1)
{
if(a[e[i].back]==e[i].to)swap(a[e[i].back],b[e[i].back]);
}
}
for(int i=1;i<=n;i++)
{
if(tag[a[i]]||tag[b[i]])swap(a[i],b[i]);
}
map<int,int> ma1,ma2;
for(int i=1;i<=n;i++)
{
ma1[a[i]]=1;
ma2[b[i]]=1;
}
cout<<ma1.size()+ma2.size()<<endl;
for(int i=1;i<=n;i++)
{
printf("%d ",a[i]);
}
printf("\n");
for(int i=1;i<=n;i++)
{
printf("%d ",b[i]);
}
printf("\n");
}
return 0;
}
想要得到这题的思路,从比较总体的部分上来说,最重要的就是看到这种形式的时候,别急,先去考虑我们能够做什么。我做这题的时候,连想到能够先把答案算出来都没做到。
想到了这一步后面的都会好很多。因为原本的题思路没打开,我只能想到\(O(2^n *n)\)...
那么之后的思路就转变为了,如何让所有至少出现两次的数值保证至少一个在上一个在下?
其实到了这一步就看起来没那么夸张了。