题目描述:给定一个长度为\(n\)的序列\(a\), 有\(m\)次操做,每次操做选择一个区间\([L, R]\), 将区间内的数从小到大排序,或从大到小排序,问最终序列中间的数是什么。c++
solution
二分答案\(mid\),将小于\(mid\)的数变成\(-1\),大于\(mid\)的数变成\(1\),等于\(mid\)的数为0,每次操做至关于询问某个区间内\(-1\)或\(1\)的个数,而后将区间从新染色,这个能够用线段树维护。最终若是中间的数是\(0\),则二分的值就是答案,若是是\(-1\)说明答案偏大,不然答案偏小。ui
时间复杂度: \(O(nlog^2n)\)spa
题目描述:有\(n\)我的,他们的高度\(h_i\)两两不一样,如今他们要排队,有四个要求:rest
给定两两之间右前方的关系,问第一行人数的最小值,或者无解。code
solution
设\(f[i]\)表示\(i\)这我的至少排在从右数起第\(f[i]\)个位置,根据题目给的条件能够列出一些不等式,而后用差分约束求解。
但不太清楚为何这样能够保证条件3,4.htm
时间复杂度:\(O(n^2)\)blog
题目描述:有一个\(2000 \times 2000\)的网格图,上面有一些线段,问这些线段穿过了多少个格子。排序
solution
枚举\(x\)坐标,根据线段定位\(y\)坐标,染色,最后统计答案。队列
时间复杂度:\(O(2000n)\)it
题目描述:给定一棵树大小为\(n\),你如今要将树上的点所有删掉,每次随机选择一个点,得分为这个点所在的树的大小,而后把这个点以及它所连的边删掉,求总得分的指望值乘\(n!\)。
solution
考虑每一个点对总得分的贡献,考虑两个点\(u, v\),当选择\(u\)删掉时,\(v\)能对\(u\)的得分做出贡献,当且仅当\(u\)到\(v\)的路径上,\(u\)是第一个被删掉的点,这个几率为(路径长度+1)的倒数,这个几率等于\(v\)对\(u\)的指望贡献,所以问题转化为求每种路径长度有多少条,总得分指望值就等于路径条数/(路径长度+1)的和。这个问题能够用点分治+\(FFT\)解决。
时间复杂度:\(O(nlog^2n)\)
code
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int mod=int(1e9)+7; const int maxn=int(1e6)+100; int n; int deep[maxn], size[maxn]; int fa[maxn], maxs[maxn]; bool ban[maxn]; vector<int> pp, p; vector<int> out[maxn]; int cnt[maxn]; LL ans[maxn], tmpans[maxn]; LL inv[maxn]; namespace FFT { typedef complex<double> cplxd; const double PI=acos(-1); int n; int rev[maxn]; cplxd ca[maxn], cb[maxn]; void FFT_init() { for (int i=0; i<n; ++i) rev[i]=0; for (int i=0; i<n; ++i) for (int j=0; 1<<j<n; ++j) rev[i]=(rev[i]<<1) | (i>>j & 1); } void FFT(cplxd *a, int type) { for (int i=0; i<n; ++i) if (i<rev[i]) swap(a[i], a[rev[i]]); for (int i=2; i<=n; i<<=1) { cplxd wn(cos(2*PI/i), sin(type*2*PI/i)); for (int j=0; j<n; j+=i) { cplxd w(1,0); for (int k=0; k<i>>1; ++k, w*=wn) { cplxd tmp=a[j+k]; a[j+k]=a[j+k]+w*a[j+k+(i>>1)]; a[j+k+(i>>1)]=tmp-w*a[j+k+(i>>1)]; } } } } void solve(int *a, int *b, LL *c, int nn) { nn<<=1; n=1; while (n<nn) n<<=1; FFT_init(); for (int i=0; i<nn; ++i) ca[i]=cplxd(a[i], 0), cb[i]=cplxd(b[i], 0); for (int i=nn; i<n; ++i) ca[i]=cplxd(0, 0), cb[i]=cplxd(0, 0); FFT(ca, 1); FFT(cb, 1); for (int i=0; i<n; ++i) ca[i]=ca[i]*cb[i]; FFT(ca, -1); for (int i=0; i<n; ++i) c[i]=LL(ca[i].real()/n+0.5)%mod; } } void read() { scanf("%d", &n); for (int i=1; i<n; ++i) { int x, y; scanf("%d%d", &x, &y); out[x].push_back(y); out[y].push_back(x); } } void dfs2(int cur, int _fa, int deep, int &maxdeep) { maxdeep=max(maxdeep, deep); ++cnt[deep]; for (auto &to:out[cur]) if (to!=_fa && !ban[to]) dfs2(to, cur, deep+1, maxdeep); } int findroot(int cur, int _fa, int &total) { int root=0; size[cur]=1; maxs[cur]=0; for (auto &to:out[cur]) if (to!=_fa && !ban[to]) { int nid=findroot(to, cur, total); if (root==0 || maxs[root]>maxs[nid]) root=nid; size[cur]+=size[to]; maxs[cur]=max(maxs[cur], size[to]); } maxs[cur]=max(maxs[cur], total-size[cur]); if (root==0 || maxs[root]>maxs[cur]) root=cur; return root; } void dfs1(int root) { int maxdeep=0; dfs2(root, 0, 0, maxdeep); FFT::solve(cnt, cnt, tmpans, maxdeep+1); for (int i=0; i<=maxdeep*2; ++i) ans[i]=(ans[i]+tmpans[i])%mod; for (int i=0; i<=maxdeep; ++i) cnt[i]=0; ban[root]=true; for (auto &to:out[root]) if (!ban[to]) { maxdeep=0; dfs2(to, 0, 1, maxdeep); FFT::solve(cnt, cnt, tmpans, maxdeep+1); for (int i=0; i<=maxdeep*2; ++i) ans[i]=(ans[i]-tmpans[i]+mod)%mod; for (int i=0; i<=maxdeep; ++i) cnt[i]=0; } for (auto &to:out[root]) if (!ban[to]) { size[root]=size[to]; dfs1(findroot(to, 0, size[root])); } } void solve() { dfs1(findroot(1, 0, n)); LL answer=0; inv[1]=1; for (int i=2; i<=n; ++i) inv[i]=inv[mod%i]*(mod-mod/i)%mod; for (int i=0; i<n; ++i) answer=(answer+ans[i]*inv[i+1]%mod)%mod; for (int i=1; i<=n; ++i) answer=answer*i%mod; printf("%lld\n", answer); } int main() { read(); solve(); return 0; }
题目描述:给定一个序列\(p_i\),在二维平面上将\((0, i)\)与\((1, p_i)\)相连,在给定一个费用\(c_i\),表示将\((0, i), (1, p_i)\)这条线段删掉的费用,在删掉一条线段的时候,与这条线段相交的线段都会被删掉,但不须要费用,问最少须要多少花费才能把全部线段删掉。
solution
将一条线段用一个点\((i, p_i)\)表示,则最终的方案必定是上升的,而且以相邻两个点做为对角的矩形内部没有点。设\(f[i]\)表示\(i\)必定要选,且\(i\)左下的点已经被删掉的费用。cdq分治,左右两边按纵坐标从小到大排序,分别维护两个按横坐标排的单调栈,左边的栈是用来维护能做为决策的点,右边是维护对应横坐标能在左边选择的高度范围,而后用线段树维护dp值。
时间复杂度:\(O(nlog^2n)\)
code
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int maxn=int(1e5)+100; const int inf=0x7fffffff; struct mes { int x, y, v; }; struct data { int value, cost, num; }; int n; data a[maxn]; int f[maxn], q[maxn], qq[maxn]; int g[maxn]; int tree[maxn*4]; mes dat; void read() { scanf("%d", &n); for (int i=1; i<=n; ++i) scanf("%d", &a[i].value); for (int i=1; i<=n; ++i) scanf("%d", &a[i].cost); for (int i=1; i<=n; ++i) a[i].num=i; } bool cmp0(data b, data c) { return b.value<c.value; } bool cmp1(data b, data c) { return b.num<c.num; } void build(int cur, int L, int R) { tree[cur]=inf; if (L==R) return; int mid=(L+R)>>1; build(cur<<1, L, mid); build(cur<<1 | 1, mid+1, R); } void updata(int cur, int L, int R) { if (dat.x>R || dat.y<L) return; if (dat.x<=L && R<=dat.y) { tree[cur]=dat.v; return; } int mid=(L+R)>>1; updata(cur<<1, L, mid); updata(cur<<1 | 1, mid+1, R); tree[cur]=min(tree[cur<<1], tree[cur<<1 | 1]); } int ask(int cur, int L, int R) { if (dat.x>R || dat.y<L) return inf; if (dat.x<=L && R<=dat.y) return tree[cur]; int mid=(L+R)>>1; return min(ask(cur<<1, L, mid), ask(cur<<1 | 1, mid+1, R)); } void cdq(int L, int R) { if (L>=R) { if (f[L]<inf) f[L]+=a[L].cost; return; } int mid=(L+R)>>1; cdq(L, mid); sort(a+L, a+mid+1, cmp0); sort(a+mid+1, a+R+1, cmp0); int head=1, tail=0; int hh=1, tt=0; for (int i=mid+1, j=L; i<=R; ++i) { while (j<=mid && a[j].value<a[i].value) { dat.v=inf; while (head<=tail && a[q[tail]].num<a[j].num) { dat.x=dat.y=a[q[tail]].value; updata(1, 0, n); --tail; } q[++tail]=j; dat.x=dat.y=a[j].value; dat.v=f[a[j].num]; updata(1, 0, n); ++j; } while (hh<=tt && a[qq[tt]].num>a[i].num) --tt; qq[++tt]=i; dat.x=a[qq[tt-1]].value; dat.y=a[i].value; if (dat.x<=dat.y) f[a[i].num]=min(f[a[i].num], ask(1, 0, n)); } dat.v=inf; while (tail) { dat.x=dat.y=a[q[tail]].value; updata(1, 0, n); --tail; } sort(a+mid+1, a+R+1, cmp1); cdq(mid+1, R); } void solve() { f[0]=0; ++n; a[n].value=a[n].num=n; for (int i=1; i<=n; ++i) f[i]=inf; build(1, 0, n); cdq(0, n); printf("%d\n", f[n]); } int main() { read(); solve(); return 0; }
题目描述:给定\(n\)个数,将这\(n\)个数从新排列,使得相邻两个数的差的绝对值的最小值最大。输出方案。
solution
从小到大排序,结论是放在奇数位的数是连续的一段,所以只要找出连续的一段数,头和尾的值相差最小,按顺序放入奇数位,剩下的数从段尾+1开始,循环插入偶数位。
时间复杂度:\(O(n)\)
题目描述:有\(n\)个数,找出全部子集中,和为第\(k\)小的子集的和。
solution
从小到大排序,二分答案,用一个队列记住当前的有效状态,一个状态包括当前的集合的和以及当前集合选择的数的最大编号。每次从队列里面取出一个状态,而后添加一个数,这个数的编号要大于最大编号,并且加上那个数的和要小于等于二分的答案,看总共有多少个集合的和小于等于二分的答案。
时间复杂度:\(O(nlogn)\)
题目描述:给定一个在第一象限的简单多边形,用一条穿过原点的直线切割多边形,问最多能切成多少个区域。
solution
将点按极角排序。假设如今直线通过点\(i\), 考虑八种状况。
对于状况2,4,6,8,区域没有变化,
对于状况1,当直线再偏一点时区域加一,对于状况4,区域加一。
对于状况3,区域减一,对于状况7,当直线再偏一点时,区域减一。
根据不一样状况区域的数量变化,求出最大值。
时间复杂度:\(O(nlogn)\)
题目描述:给定一棵树,开始时全部边都是白色,每次操做选择两个叶子节点,这两个叶子节点之间的路径要全是白色,而后将路径全涂成黑色,重复操做,直到没法选择为止,问最少要多少次操做。
solution
贪心,每棵子树只会向它的父亲传递两个叶子节点,对于一棵子树,
若是有多于两个儿子子树为\(1\),则多出来的子树要两两匹配,
若是有多于一个儿子子树为\(2\),则多出来的子树要两两匹配,
若是进行了前两个操做以后,还有两个子树为\(1\)的儿子,一个子树为\(2\)的儿子,则用一个\(1\)匹配一个\(2\)。
最后想父亲传递的叶子节点数位剩下的叶子节点数与\(2\)取最小值。
时间复杂度:\(O(n)\)
题目描述:给定一个\(01\)串,有一种操做:将某个位置的数移到某个位置。有若干个询问,每次询问为一个数,问用这么屡次操做获得的串的最长\(0\)子串为多长。
solution
若是移动的数字为\(1\),则至关于将\(1\)直接移除,若是是\(0\),则至关于从某个地方移一个\(0\)过来。
先将连续的相同数字合并。设\(s1[i]\)为\(1\)的个数前缀和,\(s0[i]\)为\(0\)的个数前缀和。枚举右端\(i\),要求出\(j\)使得\((s0[i]-s0[j])-(s1[i]-s1[j])\)最大,由于这至关于用\((s1[i]-s1[j])\)次操做得到了\((s0[i]-s0[j])\)个零,剩下的操做数直接从另外的地方移\(0\)过来。这里能够用单调队列维护。
时间复杂度:\(O(Qn)\)