学了FHQTreap来试试模板,发现就这道题我的代码而言,比Treap和Scp慢,比Splay快。 当然一年过去了代码风格也有变化,等有空把各种平衡树的性能测试一番。
Code
1 | //Code by Lucida |
学了FHQTreap来试试模板,发现就这道题我的代码而言,比Treap和Scp慢,比Splay快。 当然一年过去了代码风格也有变化,等有空把各种平衡树的性能测试一番。
1 | //Code by Lucida |
好几天才做完这几道题。。效率贼低。。还有一个插头DP和一个大搜索没做。 插头DP学完之后来填,大搜索嘛。。什么时候想练码力了再写吧。 总体感觉不是特别难,没有像今年r2的那种神级毒瘤题。。
乍一看被唬住了。然后就开始寻找问题性质 看数据范围->一定是枚举r,c啊。。 所有数字的总和一定能整除rc 再想了一会儿,发现角上只有一种消法,然后就写了一发纯暴力。
1 | //Code by Lucida |
之前学BSGS的时候做过,本来想迅速写完,结果写完了pow_mod和exgcd之后就卡住了。。无语啊。想了一会儿,模模糊糊记得好像是个算出√P以内的值,hash之后再枚举?总之没回忆起来。
为了防止忘,还是写下来吧。
解yx≡z(mod P) 由费马小定理,yP−1≡1(mod P),所以方程左边的值以[0,P−1]为第一个周期,然后开始循环。(好像是P-2?)但是实现的时候感觉算到P简单一些。 令x=ki−j,则yki−j≡z(mod P)⟺(yi)k≡zyj(mod P) 固定i,这样只需枚举k,看看有没有符合条件的zyj即可。
HashSet里面1 | //Code by Lucida |
太弱了。。这种题想了一中午都没想出来。。 对于每个区间,维护一个左端点颜色,一个右端点颜色,就可以合并了(好像看到了一点捉迷藏括号序列的影子啊。。)
1 | //Code by Lucida |
太弱了,被这题从2016克到了2017。
首先第一问,不用说,是CDQ分治。
然后我就WA了好久。因为之前写CDQ分治,虽说也是统计前面对后面的影响,但是要求的是总和之类的问题。所以我就顺手写了
1
DC(l,mid),DC(mid+1,r);
然后查了很长时间才查出来。 第二问,仍旧CDQ分治,要找出有多少有多少种方案包含某个点,然后除以总方案数就是这个点的概率。 就要记一下一个点前面有多少点可以转移到它,它点可以转移到后面多少个点,相乘就行了。 第一种没问题,第二种就没办法了。最后发现,似乎只能把序列翻转求。 改完之后,还是WA。最后发现,chkmx(pair<int,double>,pair<int,double>)是先按照第一关键字再按第二关键字比较的。所以即使.first相同也有可能不累加。惨痛的教训啊!
1 | //Code by Lucida |
费用流直接上,结果因为没用LL,WA了一次,而且还是全WA。 一定要算好一个变量的取值范围!!! 如果考场上出现这种情况,那就真是>>>>了。
1 | //Code by Lucida |
不会做。。。
用三进制数S表示对陷阱的了解情况。
状态是f[S][x][y][h]表示对陷阱的了解情况为S,在位置(x,y),血量为h,走出的最大概率。
转移的时候,如果碰到了未知的陷阱,可以根据p[]中目前可能存在的状况求出陷阱两种属性分别的概率。
写完之后90。换个枚举顺序就100了??why??
不要被题目吓到。冷静下来好好分析。
1 | //Code by Lucida |
一开始看错题了。看对了之后又理解错了。 对于一次游戏,终止局面一定是相邻的黑子白子靠在一起,这还是能看出来的。 按照标解,黑子右移或者白子左移都没有意义,因为可以模仿。所以只考虑黑子左移或者白子右移的情况。 把空格看成石子的话,问题就变成了:有2k堆石子,一次可以从1∼k堆拿走任意多的石子,不能拿的输,问有多少种先手必胜策略。 这种博弈称为Nim-K,结论和Bash博弈类似:当每堆石子的每个二进制位为1的个数都分别能整除k+1,则先手必败。 然后可以根据这个来DP。定义f[i][j]为处理完二进制前i位,石子总和为j的必败方案数目(当石子堆顺序不同也算不同),然后枚举k+1的倍数q转移。 最终必败的方案总和为∑i=0n−Kf[20][i]×C(n−i−2K,2K) 乘的那个组合数的含义为: 已经确定了每堆石子的个数,只需确定每堆第一个石子的位置就能确定出一个方案。而共有n−i−2K个位置可供选择,要选2K个。
但其实这个转化是有问题的?
1 | //Code by Lucida |
树网的核
1 | //Code by Lucida |
这是个什么梗啊。。然后又不会做 先用分数规划求出到每个出入口的最小危险值。 然后建图,S到奇数点,偶数点到T,流量为最小危险值。 在对应的出入口之间连INF。跑出最小割就是答案。
实数二分最好记答案,实数二分的range要在合法的情况下尽量小,要么就得调小eps。否则误差会大 对于不可达的奇数点或者偶数点,如果和S,T没有连INF就会WA?
1 | //Code by Lucida |
学习了一个虚树。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138//Code by Lucida
#include<bits/stdc++.h>
#define red(x) scanf("%d",&x)
template <class T> inline bool chkmx(T &a,const T &b){return a<b?a=b,1:0;}
template <class T> inline bool chkmn(T &a,const T &b){return a>b?a=b,1:0;}
using std::swap;
using std::min;
using std::sort;
typedef long long LL;
const int MAXN=250000+10,LOG=21;
const LL INF=1e15;
int n,K,h[MAXN];
struct Graph
{
private:
int id[MAXN],ec,us[MAXN],T;
public:
struct Edge{int to,v,pre;}e[MAXN<<1];
void clear(){ec=1,++T;}
void check(int pos){if(us[pos]!=T) us[pos]=T,id[pos]=0;}
void Adde(int f,int t,int v)
{
check(f),check(t);
e[++ec].to=t;e[ec].v=v;e[ec].pre=id[f];id[f]=ec;
e[++ec].to=f;e[ec].v=v;e[ec].pre=id[t];id[t]=ec;
}
void Adde(int f,int t)
{
check(f),check(t);
e[++ec].to=t;e[ec].pre=id[f];id[f]=ec;
}
int operator [](int x){check(x);return id[x];}
Edge &operator ()(int i){return e[i];}
void out()
{
for(int i=0;i<=n;i++)
for(int j=id[i];j;j=e[j].pre)
printf("%d %d\n",i,e[j].to);
}
}real,virt;
LL mine[MAXN]/*minedge*/;
int dfs[MAXN<<1],dc,li[MAXN],ro[MAXN],dep[MAXN];
void DFS(int pos)
{
dfs[++dc]=pos;li[pos]=ro[pos]=dc;
for(int i=real[pos];i;i=real(i).pre)
{
int u=real(i).to;
if(li[u]) continue;
dep[u]=dep[pos]+1;
mine[u]=min(mine[pos],(LL)real(i).v);
DFS(u);ro[pos]=ro[u];dfs[++dc]=pos;
}
}
int f[MAXN<<1][LOG],log_2[MAXN<<1];
void ST()
{
log_2[0]=-1;
for(int i=1;i<=dc;i++) f[i][0]=dfs[i],log_2[i]=log_2[i>>1]+1;
for(int j=1;j<=log_2[dc];j++)
for(int i=1;i<=dc-(1<<j)+1;i++)
{
int pl=f[i][j-1],pr=f[i+(1<<(j-1))][j-1];
f[i][j]=dep[pl]<dep[pr]?pl:pr;
}
}
int LCA(int p1,int p2)
{
if(!p1 || !p2) return 0;
static int i,pl,pr;
p1=li[p1],p2=li[p2];
if(p1>p2) swap(p1,p2);
i=log_2[p2-p1+1],pl=f[p1][i],pr=f[p2-(1<<i)+1][i];
return dep[pl]<dep[pr]?pl:pr;
}
inline bool cmp(const int &a,const int &b){return li[a]<li[b];}
LL DP(int pos)
{
if(!virt[pos]) return mine[pos];
LL res=0;
for(int i=virt[pos];i;i=virt(i).pre)
{
int u=virt(i).to;
res+=DP(u);
}
chkmn(res,mine[pos]);return res;
}
LL solve()
{
static int stack[MAXN],top;
sort(h+1,h+1+K,cmp);
//剪枝
int hc=0;for(int i=1;i<=K;i++)
if(!hc || ro[h[hc]]<li[h[i]]) h[++hc]=h[i];
virt.clear();
stack[top=0]=0;
for(int i=1;i<=hc;i++)
{
for(int lca=LCA(stack[top],h[i]);stack[top]!=lca;)
{
if(dep[lca]>dep[stack[top]])
stack[++top]=lca;
else if(dep[lca]<=dep[stack[top]])
{
virt.Adde(dep[stack[top-1]]>=dep[lca]?stack[top-1]:lca,stack[top]);
if(dep[stack[top]]==dep[lca]) stack[top]=lca;
else top--;
}
}
stack[++top]=h[i];
}
for(;top;top--)
virt.Adde(stack[top-1],stack[top]);
//virt.out();
return DP(0);
}
int main()
{
//freopen("input","r",stdin);
red(n);real.clear();
for(int i=1;i<=n-1;i++)
{
int u,v,w;
red(u),red(v),red(w);
real.Adde(u,v,w);
}
mine[0]=mine[1]=INF,dep[1]=1;
DFS(1);ST();
int m;red(m);
for(int i=1;i<=m;i++)
{
red(K);
for(int j=1;j<=K;j++)
red(h[j]);//h[i]..
printf("%lld\n",solve());
}
return 0;
}
嗯。做法来自Po姐。
紫荆花之恋。我在WC 2014中出的题目,这个题目的核心在于可以意识到替罪羊树这一技巧,能够适用于一切依靠子树大小来维持平衡的结构。那么,它必然也可以被使用于点分治这一结构。那么我们就可以用替罪羊树来维护动态的点分治。当时觉得还挺有意思的,不过现在也已经不怎么研究数据结构了>_>。 --WJMZBMR
支持加点,实时维护dis(i,j)≤ri+rj的点对数目。
考虑不是动态加点的做法。
(口胡begin)
对树点分治,重心为p的一层内,问题变成dis(i,p)+dis(j,p)≤ri+rj,即dis(i,p)−ri≤rj−dis(j,p)
维护一个有序的数组a[]={dis(i,p)−ri},对于一个新分支,求出b[]={rj−dis(j,p)}。用双指针统计答案,然后把b[]取反,将两个表std::merge。
(如果我说的有问题欢迎指教。。)
(口胡end)
加点的话,要想办法统计一个新的点对答案的贡献,即以从它一直到根节点为LCA的所有合法路径。这样,在每个重心处维护一个Treap维护ri−dis(i,p),然后查询值dis(j,p)−rj在其中的排名即可。
然后复杂度会爆棚,于是借助替罪羊思想,如果一个点点分出来的某个分支的size过大,就重新点分治这棵子树。
filltreap()实现。一开始这个函数的Treap::Insert()操作写在了遍历边的循环里,结果最顶上的点的值没有Insert()进去。。TreapNode::cnt++之后,没有立即TreapNode::up()father和点分分支的uplayer混淆。std::queue出了问题,调试的时候在那里挂掉。然后还真的研究了很长时间queue的问题。然而弃疗之后发现某个细节写错了,改了之后立即好了。。由此,崩溃的位置不一定是bug的位置。。Rebuild中,一开始我的filltreap(DyNode::anti)是写在遍历边的循环里的,结果和上面的一样,最顶上的点没有把anti赋值。Rebuild之前可以直接保留这棵子树的anti,因为anti不会随着树的结构改变。于是开心地效仿了,发现立即崩溃。研究了好久才顿悟——在Rebuild之前有垃圾回收,里面的TreapNode等同于已经被析构了,再调用必死啊。还是语言逻辑不好啊。。anti,结果崩。苦想,明白了问题:Rebuild(pos),pos我传的是替罪羊树中的节点编号。它和它的上一层不一定是父子关系。这样filltreap的值就是错的。所以pos应该传这棵子树在原树上的节点编号。%jvcb%跑的那么快简直BUG一样。
upd:发现替罪羊树写错了,改过来之后T飞了,然后有各种卡常数改α,结果最后发现是点分治写错了。太愚蠢了。1 | //Code by Lucida |
半平面交的弱化版。由于不需要去切割队首半平面,可以直接用一个栈。
1 | //Code by Lucida |
套模板
1 | //Code by Lucida |
Simpson积分,拟合二次函数曲线。 公式为6(r−l)(f(l)+f(r)+4f(mid)) 涉及圆的公切线的求法,要用到一点点平面几何。
1 | //Code by Lucida |
如果谁能证明O(N)算法的正确性,请给本蒟蒻指点一下。。
1 | //Code by Lucida |
so that the longest distance between xiaomi's home and the other groundhog's home is minimum.
二分答案?然而二分之后有什么用呢? 注意到,选取一个点,要让平面内所有点到它的距离最近,转化一下,不就是最小覆盖圆吗。。
在计算几何中,当一般算法题的套路用不上(二分答案。。。),就研究一下求解问题的几何性质(圆。)
1 | //Code by Lucida |
要让两个圆覆盖的面积最大,而且不能与边相交。 于是,将凸包上的边内移半径长度,然后求半平面交,得到的区域就是圆心可以存在的区域。 贪心地选择两个最远的点。(在边上肯定不如在点上远?),旋转卡壳。 旋转卡壳比O(n2)枚举慢??常数大? 貌似,求单位法向量, 内移求内核是个常见的套路。
它是平面简单多边形的核是该多边形内部的一个点集,该点集中任意一点与多边形边界上一点的连线都处于这个多边形内部。就是一个在一个房子里面放一个摄像头,能将所有的地方监视到的放摄像头的地点的集合即为多边形的核。
1 | //Code by Lucida |
找一个半径最大的内切圆。 二分r,将凸包上的边向内移动,求半平面交看是否为空。 移动边的时候比较巧妙,用到了单位法向量。
1 | //Code by Lucida |
旋转卡壳
注意判断的条件和卡的时候的逻辑。
按照DavidLee的这种写法必须要正卡再反卡。
1 | //Code by Lucida |