题解:P6700 [PA 2015 Final] Edycja
挺好的题,模拟赛只写出来了一个搜子。
首先先有显然的性质。
- 必然存在一种操作顺序,使得所有一操作在二操作后面。
证明显然,首先,一个位置最多做一次一操作。那么我们假设一种方案中位置
这个性质让我们可以针对二操作进行思考。
我们不妨用图上连边的方式来描述这个性质。不妨用
但是显然这样一个图和我们的二操作执行是有非常紧密的联系的。首先你发现这个图显然是基环树,因为每个点显然只有一条出边。然后观察一下。
首先如果这个图类似于一颗“树”:
你发现从上往下操作即可。
进一步的,如果他是一个基环树,并且有树枝:
你发现可以先这样做一次二操作:
此时的图就和这个图等价了:
然后像刚才一样做即可。
但是你发现这个必须要依赖于有一个“树枝”,那没有树枝的纯环呢?你发现,此时必须依赖一个外部的、不在环上的点来进行“周转”。比如说 aaabbb 要变成 bbbaaa,必须先把 aaa 变成 ccc,然后像刚才那么做。
也就是说,对于一般的基环树,要变成这样的图的代价是边数(抛开自环)乘
那你考虑怎么刻画这件事。不妨先不看这个纯环多出的代价。一种方式是直接爆搜出边。但是你发现,这个一操作的次数实际上是极其容易计算的。
我们设
那此时我们不妨直接把基环树上边赋上边权,
那基环树的总代价就是边权之和。
考虑先不看纯环多出来的代价应该怎么选。你发现此时你直接每个位置贪心选边权最小出边即可。这个图显然是极其优秀的,我们考虑在上面改一改。
你发现我们实际上环数(抛开自环)是很少的,因此我们想到可以对于环数进行状压。设
转移就是枚举新加边,像背包一样转移。
但是你发现这样还是有些问题,比如说有一个 a 到 x 的大环,还有一个 y 和 z 的自环,你发现这时也是无法做的。
具体的你规约一下,如果这个基环树森林是一堆纯环(包括自环),一定不行。
那你发现这个条件如果就只有目前这一位其实是不好刻画的,因此不妨引入第二维,我们重新定义
简单转移即可。
可能会有疑问,会不会改着改着把两个环拼成一个环了,这样显然一定是不优的,不会被保留。
AC Code
#include<bits/stdc++.h>
using namespace std ;
const int MAXN = 1e6 + 7 ;
const int MAXK = 30 ;
const int INF = 1e9 + 7 ;
const int MAXG = 14 ;
int n , k ;
int cnt[MAXK] ;
int sum[MAXK][MAXK] ;
char d[MAXK] , lst ; //字符集
int vis[MAXK] ;
int a[MAXN] , b[MAXN] ;
char s[MAXN] , t[MAXN] ;
int c[MAXK][MAXK] ;
int bel[MAXK] ; //属于哪个环
int nxt[MAXK] ; //初始贪心选边
vector<int> dir[MAXN] ;
int dfn[MAXN] , low[MAXN] , lst1 , lst2 ;
bool instack[MAXN] ;
stack<int> g ;
void tarjan(int u) {
dfn[u] = low[u] = ++lst1 ;
instack[u] = 1 ;
g.push(u) ;
int v = nxt[u] ;
if (!dfn[v]) {
tarjan(v) ;
low[u] = min(low[u] , low[v]) ;
}
else {
low[u] = min(low[u] , dfn[v]) ;
}
if (low[u] == dfn[u]) {
if (g.top() != u) {
lst2 ++ ;
while (1) {
int p = g.top() ;
g.pop() ;
bel[p] = lst2 ;
if (p == u) {
break ;
}
}
}
else {
g.pop() ;
}
}
return ;
}
int dp[1 << MAXG][2] ;
int f[1 << MAXG][2] ;
signed main()
{
// freopen("" , "r" , stdin) ;
// freopen("" , "w" , stdout) ;
ios::sync_with_stdio(0) ;
cin.tie(0) ;
cout.tie(0) ;
cin >> n >> k ;
cin >> s + 1 >> t + 1 ;
// cout << s + 1 << "\n" << t + 1 ;
for (int i = 1 ; i <= n ; i ++) {
if (!vis[s[i] - 'a']) {
d[++lst] = s[i] ;
vis[s[i] - 'a'] = lst ;
}
if (!vis[t[i] - 'a']) {
d[++lst] = t[i] ;
vis[t[i] - 'a'] = lst ;
}
a[i] = vis[s[i] - 'a'] ;
b[i] = vis[t[i] - 'a'] ;
}
for (int i = 1 ; i <= n ; i ++) {
cnt[a[i]] ++ ;
sum[a[i]][b[i]] ++ ;
}
for (int i = 0 ; i < 26 ; i ++) {
if (!vis[i]) {
d[++lst] = (char)(i + 'a') ;
}
}
int s = 0 ;
for (int i = 1 ; i <= lst ; i ++) {
int mi = 0 ;
c[i][0] = INF ;
for (int j = 1 ; j <= lst ; j ++) {
c[i][j] = cnt[i] - sum[i][j] + (i != j ? k : 0) ;
if (c[i][j] < c[i][mi]) {
mi = j ;
}
}
nxt[i] = mi ;
dir[i].push_back(mi) ;
dir[mi].push_back(i) ;
s += c[i][mi] ;
}
for (int i = 1 ; i <= lst ; i ++) {
if (!dfn[i]) {
tarjan(i) ;
}
}
if (!lst2) {
cout << s ;
return 0 ;
}
bool g = 0 ;
for (int i = 1 ; i <= lst ; i ++) {
if (dir[i].size() != 2) {
g = 1 ;
}
}
memset(dp , 0x3f , sizeof(dp)) ;
dp[0][g] = 0 ;
for (int i = 1 ; i <= lst ; i ++) {
memcpy(f , dp ,sizeof(dp)) ;
for (int j = 1 ; j <= lst ; j ++) {
int w = c[i][j] - c[i][nxt[i]] ;
for (int k = 0 ; k < (1 << lst2) ; k ++) {
int g = k ;
if (bel[i] and j != nxt[i]) {
g |= (1 << (bel[i] - 1)) ;
}
if (bel[j] and bel[j] != bel[i]) {
g |= (1 << (bel[j] - 1)) ;
}
for (int p = 0 ; p <= 1 ; p ++) {
bool flag = p | (j != nxt[i]) ;
f[g][flag] = min(f[g][flag] , dp[k][p] + w) ;
}
}
}
memcpy(dp , f , sizeof(dp)) ;
}
int ans = INF ;
for (int i = 0 ; i < (1 << lst2) ; i ++) {
ans = min(ans , s + dp[i][1] + (lst2 - __builtin_popcount(i)) * k) ;
}
cout << ans ;
return 0 ;
}