• AHOI2018 Day2


    前言

    day2相对于day1代码上短了不少,但想拿到200+还是有难度的。

    游戏

    思考一下有些点:(1)只有当人和钥匙在门的同一侧,才能通过这道门;(2)如果以房间(x)为起始位置,能到达([l,r])(一定是一个连续区间),那么对于其他房间,只要能到达房间(x),能到达的区间一定包含([l,r])

    结合以上两点:不难发现:每次选择一个房间(这里定义房间为两道门之间所有的区域),看一看左右门能不能打开,如果能到达左边门之后的房间(x),由(1),(x)不可能到右边房间,故只能到达([l,x]),那么在起点就一定能到达([l,x])。此时发现访问区间一定是严格的包含关系,故可以记搜解决。之后继续扩展。能继续拓展当且仅当门的钥匙在能到达的区间内。一开始博主一直在想set合并,后来发现直接判一下在不在区间内就好了,果然我还是tcl

    这道题有点细节需要处理好。最后复杂度(mathcal O(n))

    #include <bits/stdc++.h>
    
    #define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
    #define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
    #define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
    #define per0(i, a) for (int i = a-1; ~i; --i)
    #define chkmin(a, b) a = std::min(a, b)
    #define chkmax(a, b) a = std::max(a, b)
    
    typedef long long ll;
    
    const int maxn = 1145145;
    
    int n, m, p, l[maxn], r[maxn], door[maxn], z[maxn];
    
    void dfs(int i) {
    	if (l[i]) return;
    	l[i] = r[i] = i;
    	while (l[i] > 1 && !door[l[i]-1]) l[i]--;
    	while (r[i] < n && !door[r[i]]) r[i]++;
    	int L = l[i], R = r[i];
    	for (;;) {
    		int flag = 0;
    		while (l[i] > 1 && l[i] <= z[l[i]-1] && z[l[i]-1] <= r[i]) dfs(l[i]-1), l[i] = l[l[i]-1], flag = 1;
    		while (r[i] < n && l[i] <= z[r[i]] && z[r[i]] <= r[i]) dfs(r[i]+1), r[i] = r[r[i]+1], flag = 1;
    		if (!flag) break;
    	}
    	rep(x, L, R) l[x] = l[i], r[x] = r[i];
    }
    
    int main() {
    	scanf("%d%d%d", &n, &m, &p);
    	rep(i, 1, m) {
    		int x, y;
    		scanf("%d%d", &x, &y);
    		z[x] = y; door[x] = 1;
    	}
    	rep(i, 1, n) dfs(i);
    	while (p--) {
    		int s, t;
    		scanf("%d%d", &s, &t);
    		if (l[s] <= t && t <= r[s]) printf("YES
    "); else printf("NO
    ");
    	}
    	return 0;
    }
    

    排列

    这道题自己只会暴力做法,猜的(mathcal O(n^2))做法假了,故看题解学习。

    把题目翻译一下,不难发现实质就是有若干棵树,父亲对儿子约束先后关系,求一个合法排列使得(sumlimits_{i=1}^n i imes w_{p_i})最大。如果有环,答案显然是(-1)

    想一想发现不好来dp。如果没有约束,显然一顿排序从小到大就好了。可是加上约束似乎变得很棘手。现在有这样的一个小结论:如果当前结点是最小的结点,显然在父亲(如果有)选完之后第一个选择这个结点,这个是显然的。这样子这两个结点就被打包到一起了(不会有其它的结点穿插在之间),这点大概是正确性的前提。那对于其它子结点什么时候被选择呢?那么考虑两个被打包的结点,它们之间有先后顺序。假设两个结点对应的序列为({a_1,a_2,dots,a_n})({b_1,b_2,dots,b_m}),前者合并时有在前或者在后两种,故得到

    [left(sum_{i=1}^na_i imes i ight)+left(sum_{i=1}^mb_i imes (n+i) ight) ① ]

    [left(sum_{i=1}^mb_i imes i ight)+left(sum_{i=1}^na_i imes (m+i) ight) ② ]

    (①>②),即

    [left(sum_{i=1}^na_i imes i ight)+left(sum_{i=1}^mb_i imes (n+i) ight)>left(sum_{i=1}^mb_i imes i ight)+left(sum_{i=1}^na_i imes (m+i) ight) ]

    [Rightarrow sum_{i=1}^na_i imes m>sum_{i=1}^mb_i imes n ]

    [Rightarrow overline a>overline b ]

    这里(overline a)表示(a_i)的平均值,即((sum_{i=1}^na_i)/n),另一个同理。我们惊奇地发现合并后可以拿之后的平均值继续合并打包。于是就得到了一个贪心合并的算法,拿个可修改的堆搞一搞就好了,当然也可以像Dijkstra算法类似不用可修改堆。正确性用前面讲的归纳就行了。复杂度(mathcal O(nlog n))

    最后注意数据范围以及unsigned long longlong double就行了(博主当时由于-(ull)是正数WA了许久)

    #include <bits/stdc++.h>
    
    #define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
    #define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
    #define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
    #define per0(i, a) for (int i = a-1; ~i; --i)
    #define chkmin(a, b) a = std::min(a, b)
    #define chkmax(a, b) a = std::max(a, b)
    #define x first
    #define y second
    
    typedef unsigned long long ll;
    
    const int maxn = 555555;
    
    int f[maxn];
    int fset(int x) { return f[x] == x ? x : f[x] = fset(f[x]); }
    void merge(int x, int y) { f[fset(y)] = fset(x); }
    
    int n, a[maxn], inq[maxn], tot[maxn], cnt = 0;
    ll sum[maxn], w[maxn];
    std::priority_queue<std::pair<long double, int> > Q;
    
    int main() {
        scanf("%d", &n);
        rep(i, 0, n) f[i] = i;
        rep(i, 1, n) {
            scanf("%d", &a[i]);
            if (fset(i) == fset(a[i])) { printf("-1"); return 0; }
            merge(i, a[i]);
        }
        rep(i, 1, n) scanf("%llu", &w[i]), Q.push(std::make_pair(-(long double)(sum[i] = w[i]), i)), tot[i] = inq[i] = 1;
        rep(i, 0, n) f[i] = i;
        while (!Q.empty()) {
            int u = Q.top().y; Q.pop();
            if (!inq[u]) continue;
            inq[u] = 0; merge(a[u], u); fset(u);
    		sum[f[u]] += sum[u], w[f[u]] += w[u]+tot[f[u]]*sum[u], tot[f[u]] += tot[u];
    		Q.push(std::make_pair(-(long double)sum[f[u]]/tot[f[u]], f[u]));
        }
        printf("%llu", w[0]);
        return 0;
    }
    

    道路

    一道很水的dp题,按照题意设dp式子。注意到深度不超过40,这个很重要。设(f_{u,x,y})表示结点(u)上面有(x)条铁路没删,(y)条公路没删,然后转移方程(f_{u,x,y}=max{f_{lson,x,y}+f_{rson,x,y+1},f_{lson,x+1,y}+f_{rson,x,y}})表示选择翻修左边铁路或者右边公路两种决策,边界是叶子结点对应的公式(c_i(a_i+x)(b_i+y))。记忆化即可,复杂度(mathcal O(n imes 40^2))

    #include <bits/stdc++.h>
    
    #define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
    #define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
    #define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
    #define per0(i, a) for (int i = a-1; ~i; --i)
    #define chkmin(a, b) a = std::min(a, b)
    #define chkmax(a, b) a = std::max(a, b)
    
    typedef long long ll;
    
    const int maxn = 20000 + 5;
    
    ll f[maxn][41][41];
    int a[maxn], b[maxn], c[maxn], l[maxn], r[maxn], n;
    
    ll dp(int u, int x, int y) {
    	if (u < 0) { u = -u; return 1ll*c[u]*(a[u]+x)*(b[u]+y); }
    	if (~f[u][x][y]) return f[u][x][y];
    	return f[u][x][y] = std::min(dp(l[u], x, y) + dp(r[u], x, y+1), dp(l[u], x+1, y) + dp(r[u], x, y));
    }
    
    int main() {
    	scanf("%d", &n);
    	rep(i, 1, n-1) scanf("%d%d", &l[i], &r[i]);
    	rep(i, 1, n) scanf("%d%d%d", &a[i], &b[i], &c[i]);
    	memset(f, -1, sizeof f);
    	printf("%lld", dp(1, 0, 0));
    	return 0;
    }
    
  • 相关阅读:
    AC日记——红色的幻想乡 洛谷 P3801
    AC日记——Power收集 洛谷 P3800
    AC日记——妖梦拼木棒 洛谷 P3799
    AC日记——妖梦斩木棒 洛谷 P3797
    AC日记——小魔女帕琪 洛谷 P3802
    AC日记——双栈排序 洛谷 P1155
    AC日记——明明的烦恼 bzoj 1005
    AC日记——[HNOI2014]世界树 bzoj 3572
    AC日记——魔法森林 洛谷 P2387
    AC日记——【模板】点分治(聪聪可可) 洛谷 P2634
  • 原文地址:https://www.cnblogs.com/ac-evil/p/12995372.html
Copyright © 2020-2023  润新知