[题解]CLYZ2018省选训(bao)练(zha)模拟赛 Day 3

找规律大法好啊!

题目

T1:BZOJ 4518 / SDOI 2016 征途 (journey)(斜率优化DP)
T2:BZOJ 1434 / ZJOI 2009 染色游戏 (game)(SG函数+找规律)
T3:BZOJ 2286 / SDOI 2011 消耗战 (repair)(虚树+树形DP)

T1

分析

首先拆开方差的式子得到:
1mmi=1(xix¯)2=1mmi=1(x2i+x¯22x¯xi) 1 m ∑ i = 1 m ( x i − x ¯ ) 2 = 1 m ∑ i = 1 m ( x i 2 + x ¯ 2 − 2 x ¯ x i )
=mi=1x2ix¯mi=1xim = ∑ i = 1 m x i 2 − x ¯ ∑ i = 1 m x i m
乘上 m2 m 2 后得到 mmi=1x2i(mi=1xi)2 m ∑ i = 1 m x i 2 − ( ∑ i = 1 m x i ) 2
所以问题转化为,每一天走的路的长度的平方和尽可能小。
先预处理路长度的前缀和 sum[] s u m [ ] 。设 f[i,k] f [ i , k ] 为到了第 i i 段路,走了k天的最小平方和,其中 ik i ≥ k
容易得出转移:
f[i,k]=mini1j=k1(f[j,k1]+(sum[i]sum[j])2) f [ i , k ] = min j = k − 1 i − 1 ( f [ j , k − 1 ] + ( s u m [ i ] − s u m [ j ] ) 2 )
可以发现,上面方程的状态数为 O(nm) O ( n m ) ,转移数为 O(n) O ( n ) ,总复杂度为 O(n2m) O ( n 2 m ) ,TLE。
看到 (sum[i]sum[j])2 ( s u m [ i ] − s u m [ j ] ) 2 这样的式子,容易知道往斜率优化的方向去思考。
由于 f[...,k] f [ . . . , k ] 只能从 f[...,k1] f [ . . . , k − 1 ] 转移,所以这里第一层枚举 k k 进行DP,设f[i,k] F[i] F [ i ] f[i,k1] f [ i , k − 1 ] G[i] G [ i ]
那么转移方程化为 F[i]=mini1j=k1(G[j]+(sum[i]sum[j])2) F [ i ] = min j = k − 1 i − 1 ( G [ j ] + ( s u m [ i ] − s u m [ j ] ) 2 )
G[j]+(sum[i]sum[j])2 G [ j ] + ( s u m [ i ] − s u m [ j ] ) 2 展开得到:
G[j]+sum[i]2+sum[j]22sum[i]sum[j] G [ j ] + s u m [ i ] 2 + s u m [ j ] 2 − 2 s u m [ i ] s u m [ j ]
又发现 sum[i]2 s u m [ i ] 2 是和决策( j j )无关的东西,所以:
F[i]=minj=k1i1(G[j]+sum[j]22sum[i]sum[j])+sum[i]2
设:
K[i]=2sum[i] K [ i ] = 2 s u m [ i ]
B[i]=G[i]+sum[i]2 B [ i ] = G [ i ] + s u m [ i ] 2
那么 min min 内的式子可以表示为 K[j]sum[i]+B[j] − K [ j ] s u m [ i ] + B [ j ]
把每一个决策都 j j 都看作一条直线y=K[j]xB[j],那么可以看出,现在的最优决策就是在 x=sum[i] x = s u m [ i ] 的条件下, y y 值最大的直线(因为K[j]xB[j]有最大值则 K[j]x+B[j] − K [ j ] x + B [ j ] 有最小值)。
不难发现, K[j] K [ j ] B[j] B [ j ] 都随 j j 不降,并且每一个i对应的 x=sum[i] x = s u m [ i ] 也不降。
下面用一个单调队列维护所有决策直线组成的凸壳。
首先介绍怎样加入新决策。
假设单调队列的队尾存在两个决策 p1,p2 p 1 , p 2 ,那么新决策 j j 存在着两种可能:
(1)
这里写图片描述
(2)
这里写图片描述
(1)中,可以发现,x为不同的值时, p1,p2,j p 1 , p 2 , j 这三个决策都有可能是最优决策,并且 x x 越大,最优决策越往后走。所以这时直接把j加入单调队列的队尾。
(2)中,可以发现, x x 不管是多少,p2都绝对不是最优决策。所以这时候把 p2 p 2 出队后再把 j j 入队。
判断(1)(2)中的哪一种情况,可以用叉积实现。
对于寻找最优决策,因为x递增并且 x x 越大最优决策越往后走,所以在队首不断出队以找到最优决策。
这样,总复杂度O(nm)完美解决。

Source

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 3005;
int n, m, a[N];
ll sum[N], X[N], Y[N], f[N][N], H, T, que[N];
ll calc(int j, int i) {
    return Y[j] - X[j] * sum[i];
}
bool slope(int p1, int p2, int p3) {
    ll x1 = (Y[p1] - Y[p2]) * (X[p2] - X[p3]),
        x2 = (Y[p2] - Y[p3]) * (X[p1] - X[p2]);
    return x1 >= x2;
}
int main() {
    int i, j; n = read(); m = read();
    for (i = 1; i <= n; i++) sum[i] = sum[i - 1] + (a[i] = read());
    for (i = 1; i <= n; i++) f[i][1] = sum[i] * sum[i];
    for (j = 2; j <= m; j++) {
        H = T = 1; que[1] = j - 1; X[j - 1] = sum[j - 1] * 2;
        Y[j - 1] = f[j - 1][j - 1] + sum[j - 1] * sum[j - 1];
        for (i = j; i <= n; i++) {
            while (H < T && calc(que[H], i) >= calc(que[H + 1], i)) H++;
            f[i][j] = calc(que[H], i) + sum[i] * sum[i];
            X[i] = sum[i] * 2; Y[i] = f[i][j - 1] + sum[i] * sum[i];
            while (H < T && slope(que[T - 1], que[T], i)) T--;
            que[++T] = i;
        }
    }
    cout << f[n][m] * m - sum[n] * sum[n];
    return 0;
}

T2

分析

定义 SG(x,y) S G ( x , y ) 表示只有 (x,y) ( x , y ) 这个位置的硬币为反面朝上,其他硬币都是正面朝上,这种状态下的 SG S G 值(坐标从 0 0 开始计数)。
通过找规律,可以发现:
xy=0时, SG(x,y)=lowbit(x+y+1) S G ( x , y ) = l o w b i t ( x + y + 1 )
xy0 x y ≠ 0 时, SG(x,y)=2x+y S G ( x , y ) = 2 x + y
由于 2x+y 2 x + y 较大,所以求所有反面朝上的硬币的 SG S G 值得异或和时,要用一个数组模拟异或的过程。

Source

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 205;
char s[N][N]; int cnt[N], w[N];
int lowbit(int x) {return x & -x;}
void SG(int x, int y) {
    if (x * y) cnt[x + y] ^= 1;
    else cnt[w[lowbit(x + y + 1)]] ^= 1;
}
int main() {
    int i, j, n, m, T = read();
    for (i = 1, j = 0; i <= 201; i <<= 1, j++) w[i] = j;
    while (T--) {
        n = read(); m = read(); memset(cnt, 0, sizeof(cnt));
        for (i = 0; i < n; i++) scanf("%s", s[i]);
        for (i = 0; i < n; i++) for (j = 0; j < m; j++)
            if (s[i][j] == 'T') SG(i, j);
        bool flag = 0; for (i = 0; i <= 201; i++)
            if (cnt[i]) flag = 1;
        printf(flag ? "-_-\n" : "=_=\n");
    }
    return 0;
}

T3

分析

看到题目的条件,容易想到是一道树形DP题,设 f[u] f [ u ] 为让 u u 不能到达u的子树内(不包括 u u )的任意关键点(能源丰富的岛屿)的最小代价。如果u是叶子节点,那么 f[u]=0 f [ u ] = 0
转移就是枚举子节点 v v ,设val(u,v)为边 (u,v) ( u , v ) 的权值,则转移为:
如果 v v 是关键点,那么f[u]+=val(u,v)
否则 f[u]+=min(val(u,v),f[v]) f [ u ] + = min ( v a l ( u , v ) , f [ v ] )
考虑到询问次数较大,但给出关键点的总数很少,因此可以构建出一个虚树。注意把 1 1 <script type="math/tex" id="MathJax-Element-114">1</script>号节点加入关键点。
如果对虚树不了解,可参考神犇zzq的博客:https://www.cnblogs.com/zzqsblog/p/5560645.html
构建完虚树后,就可以在虚树上DP了。注意虚树上一条边的边权为原树上这两点的路径上边权的最小值。

Source

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 3e5 + 5, M = 5e5 + 5, LogN = 21;
int n, ecnt, nxt[M], adj[N], go[M], val[M], dep[N], fa[N][LogN], mx[N][LogN],
tn, tmp[N], vn, vir[N], dfn[N], times, top, stk[N], par[N]; ll f[N];
bool isvir[N];
void add_edge(int u, int v, int w) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; val[ecnt] = w;
}
void dfs(int u, int fu) {
    int i; dep[u] = dep[fa[u][0] = fu] + 1; dfn[u] = ++times;
    for (i = 0; i <= 18; i++) {
        fa[u][i + 1] = fa[fa[u][i]][i];
        mx[u][i + 1] = min(mx[u][i], mx[fa[u][i]][i]);
    }
    for (int e = adj[u], v; e; e = nxt[e]) {
        if ((v = go[e]) == fu) continue;
        mx[v][0] = val[e]; dfs(v, u);
    }
}
int lca(int u, int v) {
    int i; if (dep[u] < dep[v]) swap(u, v);
    for (i = 19; i >= 0; i--) {
        if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
        if (u == v) return u;
    }
    for (i = 19; i >= 0; i--)
        if (fa[u][i] != fa[v][i])
            u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}
int dis(int u, int v) {
    int i, d = dep[u] - dep[v], res = 0x3f3f3f3f;
    for (i = 19; i >= 0; i--)
        if ((d >> i) & 1) res = min(res, mx[u][i]), u = fa[u][i];
    return res;
}
inline bool comp(const int &x, const int &y) {
    return dfn[x] < dfn[y];
}
void build_virtree() {
    int i; sort(vir + 1, vir + vn + 1, comp); top = 0;
    for (i = 1; i <= tn; i++) {
        int u = vir[i]; if (!top) {par[stk[++top] = u] = 0; continue;}
        int w = lca(stk[top], u);
        while (dep[stk[top]] > dep[w]) {
            if (dep[stk[top - 1]] < dep[w]) par[stk[top]] = w;
            top--;
        }
        if (w != stk[top]) vir[++vn] = w, par[w] = stk[top], stk[++top] = w;
        par[stk[++top] = u] = w;
    }
    sort(vir + 1, vir + vn + 1, comp);
}
ll orzdalao() {
    int i; for (i = 1; i <= vn; i++) f[vir[i]] = 0;
    for (i = vn; i > 1; i--) {
        int v = vir[i], u = par[v], w = dis(v, u);
        if (isvir[v]) f[u] += w;
        else f[u] += min(1ll * w, f[v]);
    }
    return f[1];
}
int main() {
    int i, x, y, z; n = read();
    for (i = 1; i < n; i++) x = read(), y = read(),
        z = read(), add_edge(x, y, z); dfs(1, 0);
    int q = read(); while (q--) {
        vn = tn = read() + 1; vir[1] = tmp[1] = 1;
        for (i = 2; i <= vn; i++) isvir[vir[i] = tmp[i] = read()] = 1;
        build_virtree(); printf("%I64d\n", orzdalao());
        for (i = 2; i <= tn; i++) isvir[tmp[i]] = 0;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值