51Nod 算法马拉松22 代码能力捉急记

A : 贪心+栈
B : 期望
C : Unfinished
D : trie树
E : 平衡树+启发式合并
F : Unfinished

考场上只脑补出了ABDE,然而只写出了ABD。
E题从周日晚上18:30懵逼地写到22:20没调出来就弃疗了,平衡树启发式合并写太不熟练了。结果第二天早上半个多小时就调出来了?早知道周日就早点去写了……(都怪作业太多)
因此排名很低,说多了都是泪。如果早点去写E说不准就翻到20名了呢2333......


A 1821 最优集合

有点类似FJOI某年的一道叫神秘数的省选题,这题我还跟同学讲过- -。
思想差不多,我们只考虑第一个不能被子集表示的数。在不考虑S2的情况下,这个数就是第一个这样的a[i],满足:(a[i-1]的前缀和 + 1) < a[i]。
这时候为了让答案更大我们需要加入S2中的一些数字,方便起见记sum表示a[i-1]的前缀和。显然我们一定是贪心地加入一个小于等于sum+1的最大值,如果还不足够到a[i]就加第二大的……正确性易证,这里就不赘述了。那么这个过程用一个栈就可以维护。

#include<cstdio>
#include<algorithm>
#define N 1005
#define ll long long
using namespace std;
namespace runzhe2000
{
    int read()
    {
        int r = 0, p = 0; char c = getchar();
        for(; c < '0' || c > '9'; c = getchar()) c == '-' ? p = 1 : 0;
        for(; c >='0' && c <='9'; r = r*10+c-'0', c = getchar());
        return p?-r:r;
    }
    const ll INF = 1ll<<60;
    int n, siz[N], sta[N], top;
    ll a[N][N];
    void main()
    {
        n = read();
        for(int i = 1; i <= n; i++)
        {
            siz[i] = read();
            for(int j = 1; j <= siz[i]; j++)
                a[i][j] = read();
            sort(a[i]+1, a[i]+1+siz[i]);
            a[i][++siz[i]] = INF;
        }
        for(int T = read(), s1, s2, k, i, j; T--; )
        {
            s1 = read(), s2 = read(), k = read();
            j = 0; top = 0; 
            ll ans = 0, sum = 0, *as1 = a[s1], *as2 = a[s2];
            for(i = 1; i <= siz[s1]; i++)
            {
                for(; sum + 1 < as1[i]; )
                {
                    for(; j < siz[s2] && as2[j+1] <= sum + 1; sta[++top] = as2[++j]);
                    if(!top || !k)break;
                    else sum += sta[top--], k--;
                }
                if(sum + 1 < as1[i]) {ans = sum; break;}
                else sum += as1[i];
            }
            printf("%lld\n",ans);
        }
    }
}
int main()
{
    runzhe2000::main();
}
B 1705 七星剑

刚开始懵逼了很久,因为如果记一些奇怪的状态,则因为炼化可能会失败,也就是这个状态可能会转移到之前的状态里去,然后又可能转移到自己,乱七八糟的。乱想了一下高斯消元之类的算法发现都不行,还是得入手DP,然后一不小心搞出来了。
显然有一个性质,对于从i颗星升到i+1颗星,肯定只会用一种魔法石,这个魔法石的期望是最小的。
记f[i]表示从i-1升到i颗星的最小期望代价。
枚举魔法石,设p表示用的这块魔法石的成功概率,c表示失败掉星之后升回i-1颗的最小期望代价加上这块魔法石的费用,根据期望的定义,求出期望
E = f[i-1-lose] + pc + (1-p)p(2c) + (1-p)^2*p*(3c) + (1-p)^3*p*(4c) + …
= f[i-1-lose] + pc(1+2(1-p)+3(1-p)^2+4(1-p)^3+…)
括号里的东西显然是收敛的,要不然没的求。扰动法求和之后取极限即可。对于升i颗星,这样子枚举所有魔法石最后取min即是f[i]。
注意这题非常坑!交上去之后疯狂WA第8个点,尝试了long double,手写分数之类的都不行,调到凌晨才发现小数点后面输出12位就过了,真的好生气啊…怒。所以这个故事告诉我们做到有spj的小数题,不妨多输出几位…

#include<cstdio> 
#include<cmath> 
#include<algorithm>
#define N 105
using namespace std;
namespace runzhe2000
{
    int n, cost[N], lose[8][N];
    long double prob[8][N], f[8];
    void main()
    {   
        scanf("%d",&n);
        for(int i = 1; i <= n; i++)
            scanf("%d",&cost[i]);
        for(int i = 1; i <= 7; i++)
            for(int j = 1; j <= n; j++)
                scanf("%Lf",&prob[i][j]);
        for(int i = 1; i <= 7; i++)
            for(int j = 1; j <= n; j++)
                scanf("%d",&lose[i][j]);
        for(int i = 1; i <= 7; i++)
        {
            f[i] = 1e20;
            bool flag = 0;
            for(int j = 1; j <= n; j++)
            {
                long double p = prob[i][j], c = cost[j] + (f[i-1] - f[i-1 - lose[i][j]]), r;
                if(p > 0) 
                {
                    flag = 1;
                    r = f[i-1 - lose[i][j]] + (1.0 / p / p) * p * c;
                    f[i] = min(f[i], r);
                }
            }
            if(!flag){puts("-1"); return;}
        }
        printf("%.12Lf\n",f[7]);
    }
}
int main()
{
    runzhe2000::main();
}
D 1601 完全图的最小生成树计数

周四/五的时候刚听过ygg讲这个题的做法,结果51Nod就蜜汁考出来了233333……这种奇怪的脑洞太神了。
考虑把所有元素丢进trie树,对于一个trie树上节点i,如果它既有左儿子又有右儿子。那么一定有且仅有一条树边从它的某一个左儿子的元素连到它的某一个右儿子的元素。(证明:首先没有边就不连通。其次如果有超过一条边,显然跨子树的边的异或最高位很大,用一个子树内的边替换它答案更小而连通性不变)。因此要找这条边只需找到跨左右子树的异或最小值即可,这样左右即连通。递归做下去就行了。trie树深度是log10^9的,因此复杂度O(nlog^2n)。
开内存开爽,结果MLE了。记录子树内有哪些数字最好要用链表并且循环使用。

#include<cstdio> 
#include<map> 
#include<algorithm>
#define N 100005
#define MOD 1000000007
using namespace std;
namespace runzhe2000
{
    typedef long long ll;
    const int INF = 1<<30;
    int read()
    {
        int r = 0; char c = getchar();
        for(; c < '0' || c > '9'; c = getchar());
        for(; c >='0' && c <='9'; r = r*10+c-'0', c = getchar());
        return r;
    }
    int fpow(int a, int b){int r = 1;for(; b; b>>=1){if(b&1) r = (ll) r * a % MOD;a = (ll) a * a % MOD; }return r;}
    struct edge{edge* next; int val;}e[N];
    int cnt = 1, n, a[N], ecnt;
    ll ans = 0;
    map<int,int> t;
    struct trie
    {
        trie *ch[2];
        int siz, dep;
        edge *head;
    }mem[N*18], *tot, *root;
    trie* trie_new()
    {
        trie *p = tot++;
        p->ch[0] = p->ch[1] = NULL;
        p->head = NULL;
        p->siz = 0; return p;
    }
    void trie_init()
    {
        tot = mem;
        root = trie_new();
        root->dep = 30;
    }
    void trie_ins(int x)
    {
        trie *p = root;
        for(int i = 1<<30; i; i >>= 1)
        {
            int v = (x & i) ? 1 : 0;
            p->siz++;
            if(!p->ch[v]) p->ch[v] = trie_new(), p->ch[v]->dep = p->dep - 1;
            p = p->ch[v];
        }
        p->siz++;
        e[++ecnt] = (edge){p->head, x};
        p->head = &e[ecnt];
    }
    int trie_find(int x, trie *p)
    {
        for(; ~p->dep; )
        {
            int v = (x & (1<<(p->dep))) ? 1 : 0;
            if(p->ch[v]) p = p->ch[v];
            else p = p->ch[v^1];
        }
        return p->head->val;
    }
    void solve(trie *p)
    {
        if(!p) return;
        solve(p->ch[0]); solve(p->ch[1]);
        if(p->ch[0] && p->ch[1])
        {
            ll mi = INF; int micnt = 0;
            for(edge *j = p->ch[0]->head; j; j = j->next)
            {
                int x = j->val, y = trie_find(x, p->ch[1]), z = (x^y);
                if(z == mi) (micnt += ((ll)t[x] * t[y]) % MOD ) %= MOD;
                else if(z < mi) mi = z, micnt = (ll)t[x] * t[y] % MOD;
            }
            ans += mi;
            cnt = (ll) cnt * micnt % MOD;
        }
        if(!p->ch[0] && !p->ch[1]) return;
        else if(!p->ch[0]) p->head = p->ch[1]->head;
        else if(!p->ch[1]) p->head = p->ch[0]->head;
        else 
        {
            edge *end; for(edge *i = p->ch[0]->head; i; i = i->next) end = i;
            p->head = p->ch[0]->head;
            end->next = p->ch[1]->head;
        }
    }
    void main()
    {
        trie_init();
        n = read();
        for(int i = 1; i <= n; i++) a[i] = read(), t[a[i]]++;
        sort(a+1, a+1+n);
        n = unique(a+1, a+1+n) - a - 1;
        for(int i = 1; i <= n; i++) trie_ins(a[i]);
        solve(root);
        for(int i = 1; i <= n; i++) if(t[a[i]] > 1) cnt = (ll) cnt * fpow(t[a[i]], t[a[i]] - 2) % MOD; 
        printf("%lld\n%d\n",ans,cnt);
    }
}
int main()
{
    runzhe2000::main();
    return 0;
}
E 1782 圣诞树

做这道题充分展现了我代码能力弱得一B这个事实。

刚开始陷在树剖+线段树的坑里没爬出来……一条链上加物品,按照套路可以变成四个点的差分关系,也就是类似NOIP2016D1T2的思想(真是一段痛苦的回忆)。然后从下往上做,用平衡树维护当前节点,然后用启发式合并向它父亲合并即可。

听说splay启发式合并可以搞到 O(nlogn) ?不太懂,我只知道我的写法的复杂度应该不会超过 O(nlog2n)

#include<map>
#include<cstdio>
#include<vector>
#include<algorithm>
#define N 100005
#define mkp(u,v) make_pair(u,v)
using namespace std;
namespace runzhe2000
{
    int read()
    {
        int r = 0; char c = getchar();
        for(; c < '0' || c > '9'; c = getchar());
        for(; c >='0' && c <='9'; r = r*10+c-'0', c = getchar());
        return r;
    }
    int n, m, Q, ecnt, last[N], lcnt, head[N], ans[N];
    int top[N], siz[N], son[N], dep[N], fa[N];
    struct Pair{int k, t;};
    vector<Pair> que[N];
    struct edge{int next, to;}e[N<<1];
    void addedge(int a, int b)
    {
        e[++ecnt] = (edge){last[a], b};
        last[a] = ecnt;
    }
    struct list{int next, a, b;}l[N*4];
    void addlist(int x, int a, int b)
    {
        l[++lcnt] = (list){head[x], a, b};
        head[x] = lcnt;     
    }
    void dfs1(int x)
    {
        siz[x] = 1; dep[x] = dep[fa[x]] + 1;
        for(int i = last[x]; i; i = e[i].next)
        {
            int y = e[i].to;
            if(y == fa[x]) continue;
            fa[y] = x; dfs1(y);
            siz[x] += siz[y];
            if(siz[y] > siz[son[x]]) son[x] = y;
        }
    }
    void dfs2(int x)
    {
        top[x] = son[fa[x]]==x?top[fa[x]]:x;
        for(int i = last[x]; i; i = e[i].next)
        {
            int y = e[i].to;
            if(y == fa[x]) continue;
            dfs2(y);
        }
    }
    int get_lca(int a, int b)
    {
        for(; top[a] != top[b]; )
        {
            if(dep[top[a]] < dep[top[b]]) swap(a, b);
            a = fa[top[a]];
        }
        return dep[a] < dep[b] ? a : b;
    }
    struct Splay
    {
        Splay *ch[2], *fa;
        int sum, val_a, val_b, siz;
        bool operator < (const Splay that){return val_a < that.val_a || (val_a == that.val_a && val_b < that.val_b);}
    }mem[N*20], *tot, *null, *root[N], *q[N]; int qtop;
    map<int, Splay*> vis[N];
    template<class T>void reinit(T&t){t.~T();new(&t)T;}
    void init()
    {
        null = tot = mem; null->fa = null;
        null->ch[0] = null->ch[1] = null;
        null->sum = null->val_a = null->val_b = null->siz = 0;
    }
    Splay *newSplay(int b)
    {
        Splay *p = ++tot;
        *p = *null; p->siz = 1;
        p->sum = p->val_b = b;
        return p;
    }
    void showtree(Splay *p)
    {
        if(p == null) return;
        printf("%lld (siz = %d)(%lld , %lld) : %d %d\n",p-mem,p->siz,p->ch[0]-mem,p->ch[1]-mem,p->val_a,p->val_b);
        showtree(p->ch[0]);showtree(p->ch[1]);
    }
    int type(Splay *p){return p->fa->ch[1]==p;}
    void pushup(Splay *p)
    {
        if(p == null) return;
        p->sum = p->ch[0]->sum ^ p->ch[1]->sum ^ p->val_b;
        p->siz = p->ch[0]->siz + p->ch[1]->siz + 1;
    }
    void rotate(Splay *p)
    {
        Splay *f = p->fa; int d = type(p);
        (p->fa = f->fa) != null ? p->fa->ch[type(f)] = p : 0;
        (f->ch[d] = p->ch[!d]) != null ? p->ch[!d]->fa = f : 0;
        (p->ch[!d] = f) ->fa = p; pushup(f);
    }
    void splay(Splay *p, Splay *to, Splay *&root)
    {
        for(; p->fa != to; )
        {
            if(p->fa->fa == to) rotate(p);
            else if(type(p) == type(p->fa)) rotate(p->fa), rotate(p);
            else rotate(p), rotate(p);
        }
        pushup(p); if(to == null) root = p;
    }
    void fetch(Splay *x)
    {
        if(x == null) return;
        q[++qtop] = x;fetch(x->ch[0]);fetch(x->ch[1]);
    }
    Splay* insert(Splay *x, Splay *p)
    {
        if(x == null) {return p;}
        if((*p) < (*x)) x->ch[0] = insert(x->ch[0], p), x->ch[0]->fa = x;
        else x->ch[1] = insert(x->ch[1], p), x->ch[1]->fa = x;
        pushup(x); return x;
    }
    Splay* select(Splay *x, int k)
    {
        if(x->ch[0]->siz >= k) return select(x->ch[0], k);
        else if(x->ch[0]->siz == k-1) return x;
        else return select(x->ch[1], k- x->ch[0]->siz - 1);
    }
    void delet(Splay *x, Splay *p, Splay *&root)
    {
        splay(p, null, x);
        if(p->ch[0] == null) root = p->ch[1], root->fa = null;
        else if(p->ch[1] == null) root = p->ch[0], root->fa = null;
        else splay(select(p, p->ch[0]->siz+2), p, p),p->ch[0]->fa = p->ch[1], p->ch[1]->ch[0] = p->ch[0], root = p->ch[1], root->fa = null;
        pushup(root);
    }
    void merge(Splay *&x, Splay *&y, int X, int Y)
    {
        if(x->siz < y->siz) swap(x, y), swap(vis[X], vis[Y]);
        qtop = 0; fetch(y);
        for(int i = 1; i <= qtop; i++)
        {
            Splay *p;
            if((p = vis[X][q[i]->val_b]))
            {
                delet(x, p, x);
                p->val_a += q[i]->val_a;
                p->fa = p->ch[0] = p->ch[1] = null;
                p->siz = 1; p->sum = p->val_b;
                if(p->val_a)x = insert(x, p);
            }
            else
            {
                q[i]->fa = q[i]->ch[0] = q[i]->ch[1] = null;
                q[i]->siz = 1; q[i]->sum = q[i]->val_b;
                x = insert(x, q[i]);
                vis[X][q[i]->val_b] = q[i];
            }
        }
    }
    void dfs(int x)
    {
        root[x] = null;
        for(int i = last[x]; i; i = e[i].next)
        {
            int y = e[i].to;
            if(y == fa[x]) continue;
            dfs(y);
            merge(root[x], root[y], x, y);
            reinit(vis[y]);
        }
        for(int i = head[x]; i; i = l[i].next)
        {
            int a = l[i].a, b = l[i].b; Splay *p; 
            if((p = vis[x][b]))
            {
                splay(p, null, root[x]);
                delet(root[x], p, root[x]);
            }
            else p = newSplay(b);
            p->val_a += a;
            p->fa = p->ch[0] = p->ch[1] = null;
            p->siz = 1; p->sum = p->val_b;
            if(p->val_a) root[x] = insert(root[x], p), vis[x][b] = p;
            else vis[x][b] = NULL;
        }
        for(int i = 0, ii = (int)que[x].size(); i < ii; i++)
        {
            int k = que[x][i].k;
            if(root[x]->siz <= k) ans[que[x][i].t] = root[x]->sum;
            else
            {
                Splay *p = select(root[x], k+1);
                splay(p, null, root[x]);
                ans[que[x][i].t] = root[x]->ch[0]->sum;
            }
        }
    }

    void main()
    {
        n = read();
        for(int i = 1, a, b; i < n; i++)
        {
            a = read(); b = read();
            addedge(a, b);
            addedge(b, a);
        }
        dfs1(1);dfs2(1);
        m = read();
        for(int i = 1, u, v, a, b, lca; i <= m; i++)
        {
            u = read(); v = read();
            a = read(); b = read();
            lca = get_lca(u,v);
            addlist(lca,-a,b); addlist(fa[lca],-a,b);
            addlist(u,a,b); addlist(v,a,b);
        }
        Q = read();
        for(int i = 1, w, k; i <= Q; i++)
        {
            w = read(); k = read();
            que[w].push_back((Pair){k, i});
        }
        init(); dfs(1);
        for(int i = 1; i <= Q; i++) printf("%d\n",ans[i]);
    }
}
int main()
{
    runzhe2000::main();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值