【清华集训 2017 小Y和地铁】【爆搜 + 思维题】

显然,我们可以得到这样的性质:

  1. 只出现一次的数字对答案没有影响。
  2. 相邻数字相同对答案没有影响。

另一个性质是,下面这两张图是等价的:

【清华集训 2017 小Y和地铁】【爆搜 + 思维题】_第1张图片
【清华集训 2017 小Y和地铁】【爆搜 + 思维题】_第2张图片

我们发现两条路线等价当且仅当左右两端点向下或向上方向都一致。
证明:如果以转换站为纵分界,我们可以将图分为 2 * 36 个区域。两个图被切开后,不连通的区域和相连通的区域是相同的(以线路分隔不同的区域),所以对答案的影响也是相同的。

忽略所有只有一个交点的转站,剩下的按左端点排序然后 dfs,维护一下当前右端点朝上和朝下连出去的接头的信息,用个树状数组维护就好了。


像上面那种拐弯的情况,就可以理解为走到头再回来。

#include 

using namespace std;
const int N = 105, inf = 0x7fffffff;

int n, a[N], cnt = 0, ans = 0;
int l[N], r[N];

struct SZSZ {
    int c[N];
    inline void add(int x, int v) { for ( ; x <= n; x += x & -x) c[x] += v; }
    inline int sum(int x) { int ans = 0; for( ; x; x -= x & -x) ans += c[x]; return ans; }
    inline int query(int l, int r) { return (l > r) ? 0 : sum(r) - sum(l - 1); }
} up, down; // 树状数组

void dfs(int now, int nowans) {
    if (nowans >= ans) return ;
    if (now > cnt) { ans = nowans; return ; }

    up.add(r[now], 1); // 右端点从上面走
    dfs(now + 1, nowans + min(up.query(l[now], r[now] - 1), down.query(l[now], n) + up.query(r[now] + 1, n)));
    // up.query(l[now], r[now] - 1) : 左端点从上面走; down.query(l[now], n) + up.query(r[now] + 1, n) : 左端点从下面走
    up.add(r[now], -1); // 回溯 

    down.add(r[now], 1); // 右端点从下面走
    dfs(now + 1, nowans + min(down.query(l[now], r[now] - 1), up.query(l[now], n) + down.query(r[now] + 1, n)));
    down.add(r[now], -1);
}

int main() {
    int T;
    scanf("%d", &T);
    while (T --) {
        ans = inf; cnt = 0;
        scanf("%d", &n);
        for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
        for (int i = 1; i <= n; i ++)
            for (int j = i + 2; j <= n; j ++)
                if (a[i] == a[j]) l[++ cnt] = i, r[cnt] = j;
        dfs(1, 0);
        printf("%d\n", ans);
    } 
    return 0;
} 

你可能感兴趣的:(-----,思维题,-----)