题解:P6700 [PA 2015 Final] Edycja
更好的阅读体验
打模拟赛打到的,实在是神题阿。
出于一些惯例,我们假设读入的两个字符串分别是
假设存在一种 1 在 2 前面的操作序列,比如先令
S_k = x ,然后将所有x 变成y 。那么这两个操作可以等效成,先将所有x 变成y ,然后将S_k 变成y 。
那么考虑按照字母建点。我们假设
刚刚那句话很拗口,举一个例子来帮助理解。如
注意当
那么我们假设字母
那么假如已经知道了这个内基环树森林的形态,要如何计算这个局面的代价呢?我们根据每个连通块的形态来分类讨论。
Case 1:树
当连通块中存在一个自环,基环树退化成一个有根树,自环的节点为根,如图。
那么这种情况是容易的。首先我们操作根节点,然后剩余的节点按照逆拓扑序操作即可。除了根的儿子以外,其他节点
假设
Case 2:未退化基环树
当连通块的环不是自环,且存在入度为
在这种情况下,环以外的部分同样可以像树的情况一样,按照逆拓扑序操作;但是由于存在一个大小
那么我们找到一个
分析一下代价:由于我们砍掉了一条边,少花费了
Case 3:未退化的纯环
当这个连通块的大小
我们仍然希望找到一个中转点,将环断成链。但是由于该连通块中不存在环以外的点,因此我们必须从其他连通块选择任意一个被空出来过的点,记为
容易发现我们在环上少进行了一次 2 操作,但是我们在环外多进行了两次 2 操作,因此一个环的代价是环上所有边的边权之和,再加
注意在这种情况下,我们要选择一个被空出来过的点作为中转点。当这种点不存在的时候无解。因此图中必须存在至少一个未退化的树或基环树。
Case 4:自环
这种情况非常简单,直接移动即可,代价为自环的边权。
综上所述,整张图的权值就是,所有
直接构造一个上述权值最小的环并不容易。我们考虑先找出一个代价较小的图,再在其上调整。
我们选
考虑调整法:假设产生了一个新环,则环的个数一定不会减少;而选择了一些
to_i 使to_i 不是i 的最小出边。那么我们将这些边改回去,总代价一定不会变小。
既然只有原有的环,我们就可以 dp 了。我们称一个环被破坏,就是说它被断成了链,或者挂上了一些东西变成了基环树。假设
注意到我们 dp 的环一定大小
那么这道题就做完了,复杂度
#include<bits/stdc++.h>
#define endl '\n'
#define N 1000006
using namespace std;
template <typename T> inline void chkmax(T &x,T y) {x=x<y?y:x;}
template <typename T> inline void chkmin(T &x,T y) {x=x<y?x:y;}
using i64=long long;
int n,c,tot,no_leaf=1,bel[30],cnt[30][30],w[30][30],to[30],in[30];
i64 f[30][1<<16][2];
char s[N],t[N];
main()
{
scanf("%d%d%s%s",&n,&c,s+1,t+1);
for(int i=1;i<=n;i++)cnt[s[i]-'a'][t[i]-'a']++;
for(int i=0;i<26;i++)
{
int sum=0;
for(int j=0;j<26;j++)sum+=cnt[i][j];
for(int j=0;j<26;j++)
w[i][j]=sum-cnt[i][j]+(i==j?0:c);
for(int j=1;j<26;j++)
if(w[i][j]<w[i][to[i]])to[i]=j;
in[to[i]]++;
}
queue<int> q;
for(int i=0;i<26;i++)
if(!in[i])q.push(i),no_leaf=0;
while(q.size())
{
int u=q.front(); q.pop();
if(!--in[to[u]])q.push(to[u]);
}
memset(bel,-1,sizeof(bel));
for(int i=0;i<26;i++)if(in[i]&&to[i]!=i)
{
int j=i;
while(in[j])in[j]=0,bel[j]=tot,j=to[j];
tot++;
}
if(!tot)
{
i64 ans=0;
for(int i=0;i<26;i++)ans+=w[i][to[i]];
printf("%lld\n",ans);
return 0;
}
memset(f,0x3f,sizeof(f)),f[0][0][0]=0;
for(int i=0;i<26;i++)
for(int S=0;S<(1<<tot);S++)
{
for(int o=0;o<2;o++)
for(int j=0;j<26;j++)
{
int nxt=S;
if(bel[i]!=-1&&to[i]!=j)nxt|=1<<bel[i];
if(bel[j]!=-1&&(to[i]!=j||bel[i]!=bel[j]))nxt|=1<<bel[j];
chkmin(f[i+1][nxt][o|(to[i]!=j)],f[i][S][o]+w[i][j]);
}
}
i64 ans=2e18;
for(int S=0;S<(1<<tot);S++)
for(int o=no_leaf;o<2;o++)chkmin(ans,f[26][S][o]+(tot-__builtin_popcount(S))*c);
printf("%lld\n",ans);
return 0;
}