51Nod 算法马拉松22 代码能力捉急记
2017-03-06 08:05
344 查看
A : 贪心+栈
B : 期望
C : Unfinished
D : trie树
E : 平衡树+启发式合并
F : Unfinished
考场上只脑补出了ABDE,然而只写出了ABD。
E题从周日晚上18:30懵逼地写到22:20没调出来就弃疗了,平衡树启发式合并写太不熟练了。结果第二天早上半个多小时就调出来了?早知道周日就早点去写了……(都怪作业太多)
因此排名很低,说多了都是泪。如果早点去写E说不准就翻到20名了呢2333......
思想差不多,我们只考虑第一个不能被子集表示的数。在不考虑S2的情况下,这个数就是第一个这样的a[i],满足:(a[i-1]的前缀和 + 1) < a[i]。
这时候为了让答案更大我们需要加入S2中的一些数字,方便起见记sum表示a[i-1]的前缀和。显然我们一定是贪心地加入一个小于等于sum+1的最大值,如果还不足够到a[i]就加第二大的……正确性易证,这里就不赘述了。那么这个过程用一个栈就可以维护。
显然有一个性质,对于从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的小数题,不妨多输出几位…
考虑把所有元素丢进trie树,对于一个trie树上节点i,如果它既有左儿子又有右儿子。那么一定有且仅有一条树边从它的某一个左儿子的元素连到它的某一个右儿子的元素。(证明:首先没有边就不连通。其次如果有超过一条边,显然跨子树的边的异或最高位很大,用一个子树内的边替换它答案更小而连通性不变)。因此要找这条边只需找到跨左右子树的异或最小值即可,这样左右即连通。递归做下去就行了。trie树深度是log10^9的,因此复杂度O(nlog^2n)。
开内存开爽,结果MLE了。记录子树内有哪些数字最好要用链表并且循环使用。
刚开始陷在树剖+线段树的坑里没爬出来……一条链上加物品,按照套路可以变成四个点的差分关系,也就是类似NOIP2016D1T2的思想(真是一段痛苦的回忆)。然后从下往上做,用平衡树维护当前节点,然后用启发式合并向它父亲合并即可。
听说splay启发式合并可以搞到O(nlogn)?不太懂,我只知道我的写法的复杂度应该不会超过O(nlog2n)。
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 , sta , top; ll a ; 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 , lose[8] ; long double prob[8] , 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 ; int cnt = 1, n, a , 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 , lcnt, head , ans ; int top , siz , son , dep , fa ; struct Pair{int k, t;}; vector<Pair> que ; 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 , *q ; int qtop; map<int, Splay*> vis ; 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(); }
相关文章推荐
- 51Nod 算法马拉松22 开黑记
- 51nod 算法马拉松22 完全图的最小生成树计数 【Trie树+图论】
- 51nod 算法马拉松4
- 51nod算法马拉松12
- 【51Nod 1674】【算法马拉松 19A】区间的价值 V2
- 【51NOD 1674】【51NOD 算法马拉松19】区间的价值 V2
- 51Nod 算法马拉松15 记一次悲壮而又开心的骗分比赛
- 51nod 拉勾专业算法能力测评消灭兔子 优先队列+贪心
- 51nod-算法马拉松19
- [FWT] 51Nod 算法马拉松26 A A国的贸易
- 51nod 算法马拉松25
- 51Nod 算法马拉松7 B选数字
- 51nod 算法马拉松11 A 翻硬币
- 【51nod】【算法马拉松14】1586 约数和
- 51Nod-算法马拉松23 B 谷歌的恐龙 [概率期望]【数学】
- [最短路 主席树 Hash] 51Nod 算法马拉松26 E Travel
- 51Nod 算法马拉松23
- 【51nod】算法马拉松4 F 移数字 【快速求N!%P】【FFT】
- [51nod]算法马拉松18 总结
- 51Nod 斜率最大(拉勾专业算法能力测评)