【TopCoder】[TCO2013 2C] Well Timed Search

相关链接

题目传送门:https://arena.topcoder.com/#/u/practiceCode/15657/31723/12515/1/317358
中文题面及题解:http://picks.logdown.com/posts/208980-solutions-to-topcoder-open-2013s-hard-problems

解题报告

这题从头到尾都只会 $O(n^3)$ 的 $DP$
想了四个小时啊 QwQ

考虑搞出一个二叉搜索树
显然我们可以控制一个节点的左右儿子有多少个
因为我们可以询问当前区间的端点,所以我们还可以控制有没有左右儿子
换一句话说,我们可以钦定这棵搜索树的形态

我们继续观察可以发现一下两条:

  1. 我们可以根据一个节点左右儿子子树的大小,求得往左、往右走的概率
  2. 每一个深度在$[A,B]$的节点都是一个合法的结束

于是我们就可以枚举第$A$层有多少结点,然后贪心往下放就可以啦!

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

class WellTimedSearch {
    public:
	double getProbability(int n, int A, int B) {
		int vout = 0; 
		for (int i=1,up,down;i<=n;i++) {
			if (A > 1) up = Get_Top(i+1>>1, A-1);
			else {if (i == 1) up = 0; else up = 1e9;}
			if (n - up - i < 0) break;
			down = Get_Down(B-A, n - up - i, i<<1);
			vout = max(vout, i + down);
		}
		return (double)vout / n;
	}
   	private:
	int Get_Top(int len, int t) {
		if (t > 1) return min((int)1e9, len + (len>1? Get_Top(len+1>>1, t-1): t-1));
		if (t == 1 && len > 1) return 1e9;
		return 1;
	}
	int Get_Down(int t, int num_node, int cur) {
		if (!t) return 0;
		if (cur >= num_node) return num_node;
		return cur + Get_Down(t-1, num_node - cur, cur<<1);
	}
};

【CDOJ 1314】Hash Perfectly

相关链接

题目传送门:http://acm.uestc.edu.cn/#/problem/show/1314
神犇题解:http://www.cnblogs.com/qscqesze/p/5380044.html

解题报告

最开始想用暴力搞一搞
然后搞了两个小时,发现问题越来越多
还是来想正解吧 QwQ

考虑冲突的条件是什么: $a \equiv b({\mathop{\rm Mod}\nolimits} p)$
化简一下就是: $a – b = k \cdot p$
换一句话来说,对于给定模数$p$, 我们只要统计出有多少$a-b$等于$p$的倍数就好
这里暴力的话,用调和级数证一证就是 $O(nlogn)$ 的
于是现在问题变成了求$a-b=x$的有多少对了
我们发现这货搞一发$FFT$就可以啦!

【BZOJ 2001】[HNOI2010] City 城市建设

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2001
神犇题解Ⅰ:http://www.cnblogs.com/BLADEVIL/p/3512644.html
神犇题解Ⅱ:http://blog.miskcoo.com/2015/04/bzoj-2001

解题报告

如果没有边权,只需要维护连通性
那么搞一个可以撤销的并查集就可以啦!

现在考虑带边权的情况,我们仍然对时间进行分治
在每一层,我们进行两个操作:

1. Contraction 缩小点的规模

将所有当前分治区间内会被修改的边的边权置为 $-INF$
然后做一遍MST,将此时通过非 $-INF$ 连接的点用并查集缩起来
因为$-INF$边只会有区间长度个,所以每一次分治后,点的规模不会超过分治区间的长度

2. Reduction 缩小边的规模

将会被修改的边的边权置为 $INF$,然后再做一遍MST
将此时不在MST中的边删去,因为保留的边数不会超过点数,所以边的规模也不会超过分治区间的长度

所以时间复杂度写出来长这样: $T(n) = 2T(\frac{n}{2}) + n\log n$
用主定理化简之后,时间复杂度就是: $O(n{\log ^2}n)$

【BZOJ 4358】permu

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4358
神犇题解Ⅰ:http://www.cnblogs.com/clrs97/p/5049090.html
神犇题解Ⅱ:http://www.cnblogs.com/czllgzmzl/p/5644724.html

解题报告

1. 莫队+并查集

首先我们考虑一个简化版本:搞一个莫队,然后搞一个值域线段树
这样的话,我们的复杂度就是 $O({n^{1.5}}logn)$ 的,虽然可能会T,但很好想
现在考虑怎么把$log$去掉:如果莫队那里只存在插入操作,那么我们就可以使用并查集来维护
于是考虑莫队那里只有左端点的移动那里会有删除操作,于是我们每一次把左端点块内那一部分拿出来暴力重构
这样的话,复杂度就神奇地少了一个$log$!总复杂度: $O(n\sqrt n \alpha (n))$ 感觉可以A地样子!

2. KD-Tree

这里就是本题的高能部分!简直是太神啦!_(:з」∠)_
考虑将询问化为二维上的点,然后我们按照点值的大小,将序列的位置一个一个插入
每一次把所有包含他的询问的计数器加一,其他的询问的计数器置零
于是我们的每个询问对应的点上的计数器的历史最大值就是这个询问的答案啦!
时间复杂度:$O(n \sqrt n)$

【BZOJ 4537】[HNOI2016] 最小公倍数

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4537
神犇题解Ⅰ:http://www.cnblogs.com/clrs97/p/5406018.html
神犇题解Ⅱ:http://blog.csdn.net/heheda_is_an_oier/article/details/51197705

解题报告

考虑只有 ${{\rm{2}}^a}$ 的限制的话
只需要将边和询问排序后依次执行就可以了
具体的话,就是判一判在不在同一个连通块和连通块内的最大值是否符合条件

现在有两个限制,且独立不了的话
那么考虑将 $a$ 分块
边和询问按照 $a$ 所在的块为第一关键字,$b$ 为第二关键字
于是单次询问的加边数量就在 $\sqrt m $ 级别了
于是再搞一个持久化并查集什么的,遇到操作就暴力插入、还原
于是复杂度就是 $O({m^{1.5}}\log n)$ 辣!

【BZOJ 4456】[ZJOI2016] 旅行者

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4456
神犇题解:http://blog.csdn.net/neither_nor/article/details/51733997

解题报告

将询问离线后考虑分治
不停地将区间尽量划分成正方形
考虑每一个块内的询问:

1. 最优路径经过中轴线

那么我们枚举中轴线上的每一个点
跑一遍到块内所有点的最短路
然后用这个东西来更新答案

2. 最优路径不经过中轴线

那么我们递归处理

于是我们就在 $O(n\sqrt n {\log ^2}n)$ 的时间复杂度内解决这个问题了
然而复杂度可以被证明到少个 $log$ 参见:http://blog.csdn.net/neither_nor/article/details/51733997

【BZOJ 4377】[POI2015] Kurs szybkiego czytania

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4377
神犇题解:http://blog.csdn.net/commonc/article/details/51282041

解题报告

考虑一个匹配的第一个位置是x的话
第i个位置实际的值就是 $ (x + (i – 1)a)\% n$
然后这个位置显然会受到限制,形如:$ {p_1} < x < {p_2}$的限制条件
于是对于限制条件的反区间取并,然后从总区间中减去,剩下的就是符合条件的开头啦!

【BZOJ 4059】[Cerc2012] Non-boring sequences

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4059
神犇题解Ⅰ:http://blog.csdn.net/popoqqq/article/details/46380617
神犇题解Ⅱ:http://krydom.com/bzoj4059/

解题报告

考虑直接找出所有不无聊的区间
直接 $ O(n^2)$ 枚举的话,会T掉,于是考虑分治
假如当前考虑到的区间为(l,r),且这个区间中一个只出现过一次的数为x,下标为p
那么显然,横跨x的区间合法,两边的区间就递归下去做就好啦!

但如果每一次我们从左到右扫描x的话,时间复杂度长这样:$ T(n) = T(n – 1) + n$
这样的话,上限是 $ O(n^2)$ 显然不清真
考虑同时从两边开始扫描的话,复杂度就变为了: $ T(n) = T(p) + T(n – p) + \min (n – p,p)$
于是这个是:$ O(n\log n)$ 的,于是就可以愉快的暴力啦!
至于复杂度的证明的话,我们发现这货的形式和线段树的启发式合并的式子长得一毛一样
因为线段树的启发式合并是 $ O(n\log n)$ 的,那么这货的复杂度也就是对的啦!

当然你也可以使用归纳法什么的来证明复杂度
甚至你可以根本不用这个暴力的算法,而使用优雅的矩形面积并来做:
http://www.cnblogs.com/DaD3zZ-Beyonder/p/5803964.html

【BZOJ 4644】经典傻逼题

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4644
神犇题解Ⅰ:http://www.cnblogs.com/clrs97/p/5734792.html
神犇题解Ⅱ:http://blog.csdn.net/neither_nor/article/details/53997223

解题报告

首先,如果我们把边权异或到点上
原问题就可以转化为:选一些点,使NIM和最大
于是就是线性基的锅了,时间复杂度:$ \frac{{nm{L^2}}}{{{\rm{64}}}}$

然后我们发现这么做会T
于是对时间进行分治
这样的话,复杂度优化到:$ \frac{{m\log m{L^2}}}{{{\rm{64}}}}$

然后Claris还说可以用Method of Four Russians
这样话,复杂度就变成了:$ \frac{{m\log m{L^2}}}{{{\rm{64logL}}}}$

【BZOJ 4722】由乃

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4722
神犇题解Ⅰ:http://blog.csdn.net/werkeytom_ftd/article/details/54429577
神犇题解Ⅱ:http://www.cnblogs.com/wxxlouisa/p/6139165.html

解题报告

这题看起来一眼不可做啊!
仔细想一想,发现确实不会做

看完题解后:果然是乱搞题!
先不管修改操作的话,如果询问的区间长度超过13的话,一定有解
相关证明可以参见:http://blog.csdn.net/werkeytom_ftd/article/details/54429577

现在考虑修改操作
因为单次询问最多涉及13个数
所以只需要支持单点查询+区间修改就可以了
于是用线段树打标记什么的就可以啦!

然而我们惊讶的发现,我们打的Tag在最后计算的时候会很大
More Formally:次数会爆 $ long long$
于是用不了快速幂了 QwQ
然后又不会了

于是接着去膜拜题解
发现我们可以用类似快速幂一样的东西搞出来
我们最终要求的是:$ {a_i}^{{3^x}}$
于是我们预处理出 $ f(i,j)$ 表示 $ {a_i}^{{3^{{2^j}}}}$
于是像快速幂一样搞一搞就可以啦!

最后我们求出了这13个数是什么,怎么判断有没有解呢?
我们可以 Meet in middle !

【Tsinsen 1342】能量棒

相关链接

题目传送门:http://www.tsinsen.com/A1342
参考代码:http://paste.ubuntu.com/23803611/

解题报告

这题乍看需要 $ O({n^2})$ 的DP的样子
然而可以用 Tree 这道题一样,给每一次转移附加一个值 $ k$
然后直接转移,记录一下转移了多少次,根据次数的多少来调整 $ k$
然后再用上决策单调性来优化DP的话
这样似乎就可以在 $ O(n{\log ^{\rm{2}}}n)$ 的时间复杂度内解决这个问题了!

值得一提的是,毛爷爷讲了一下这个方法的适用范围:
DP的那个函数必须是一个凸包
不过毛爷爷也说了:
只要拍一拍,拍不出错就行啦!

【BZOJ 4538】[HNOI2016] 网络

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4538
神犇题解:http://krydom.com/bzoj4538/

解题报告

这题我先说一种怪怪的做法 QwQ
考虑使用主席树,第一维是权值(用BIT搞成后缀和),第二维是DFS序
这样的话,对于询问我们可以在第一维的权值上进行二分
对于每一次二分 $ a$ ,我们可以通过判断覆盖该点的区间个数是否等于所有不小于 $ a$ 的区间个数是否相等

这样话,原问题转化为:在主席树上的区间加减问题
这一块的时空复杂度是 $ O(n{\log ^3}n)$ 的
然后考虑上二分的 $ log$ 这样的话,复杂度就是 $ O(n{\log ^4}n)$ 的
这种做法是自己YY的,感觉很有道理的样子
然而懒得写代码了,也不知道对不对

另外的话,再说一说正解吧!
考虑一条路径,说白了就是查询路径上的点的时候不查到该路径
查询非该路径上的点的时候查询到该路径
这时考虑树链剖分的话,该路径对应的 $ {log(n)}$ 个区间应该忽略,其他部分添加上这个路径的信息
因为是一整块区间抠掉 $ {log(n)}$ 个区间,所以剩余部分也只会有 $ {log(n)}$ 个区间
于是树链剖分暴力搞一搞就可以辣!
时空复杂度比之前的做法应该要稍微优越一点

【UOJ 246】[UER #7] 套路

相关链接

题目传送门:http://uoj.ac/contest/35/problem/246
官方题解:http://matthew99.blog.uoj.ac/blog/2085

解题报告

这题真™是套路深啊!
一言不合就根号大军了

具体来说的话,我们考虑答案的区间长度为 $ len$
并且钦定一个阀值 $ S$

  1. 考虑 $ len<S$ 的情况
    我们枚举右端点,暴力向左扫 $ S$ 个就可以了

  2. 考虑 $ len \ge S$ 的情况
    我们发现相似度不会超过 $ \frac{m}{{len – 1}}$
    于是考虑在权值上暴力向两边扫 $ S$ 个

这样的话,我们就可以在 $ O(nS + n\frac{m}{s})$ 的复杂度内解决这个问题了
不难发现,当 $ S = \sqrt m$ 时复杂度最优

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 200000+9;

int n,m,K,S,a[N],gap[N],lst[N];
LL vout;

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

int main() {
	n = read(); m = read(); 
	K = read(); S = sqrt(m) + 1;
	for (int i=1;i<=n;i++) a[i] = read();
	
	memset(gap,60,sizeof(gap));
	for (int i=1;i<=n;i++) {
		for (int j=i-1;j>=i-S&&j;--j) {
			gap[j] = min(gap[j], gap[j+1]);
			gap[j] = min(gap[j], abs(a[i] - a[j]));
			if (i-j+1 >= K) vout = max(vout, (LL)(i - j) * gap[j]);
		}
	}
	memset(gap,0,sizeof(gap));
	for (int i=1;i<=n;i++) {
		for (int j=0;j<=S;j++) {
			if (j) gap[j] = max(gap[j], gap[j-1]);
			if (a[i]-j >= 1) gap[j] = max(gap[j], lst[a[i]-j]);
			if (a[i]+j <= m) gap[j] = max(gap[j], lst[a[i]+j]);
			if (i-gap[j] >= K) vout = max(vout, (LL)(i-gap[j]-1) * (j+1)); 
		}
		lst[a[i]] = i;
	}
	printf("%lld\n",vout);
	return 0;
}

【UOJ 278】[UTR #2] 题目排列顺序

相关链接

题目传送门:http://uoj.ac/contest/36/problem/278
解题报告:http://jiry-2.blog.uoj.ac/blog/2242

解题报告

这个题目看一眼就觉得搞一个差分约束系统
然后用函数式线段树优化建图
时间复杂度 $ O(nlo{g^2}n)$
感觉可以过的样子!

然后去看题解:

直接排一个序就可以了

不要问我为什么跪在地上

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 100000+9;

int n,vout[N];
pair<int,int> arr[N];

inline int read(){
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

int main(){
	n = read();
	for (int i=1;i<=n;i++) {
		arr[i].first = read();
		arr[i].second = -i;
	}
	sort(arr+1, arr+1+n);
	for (int i=1;i<=n;i++)
		vout[-arr[i].second] = i;
	for (int i=1;i<=n;i++)
		printf("%d ",vout[i]);
	return 0;
}

【BZOJ 2456】mode

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2456
神犇题解:https://oi.men.ci/bzoj-2456/

解题报告

考虑将不同的数抵消掉
因为题目所求的数多于一半
所以剩下的就一定是答案啦!

Code

#include<cstdio>
#define LL long long

int main() {
	int n,tot=0; LL num,cur;
	for (scanf("%d",&n);n;n--) {
		scanf("%lld",&cur);
		if (num != cur) {
			if (tot) tot--;
			else tot = 1, num = cur;
		} else tot++;
	} printf("%lld\n",num);
	return 0;
}

—– UPD 2016.12.29 —–
这题似乎还有一个做法:

考虑将原数分解成二进制
统计每一位二进制上0多还是1多

酱紫的话,最后在在二进制下逐位确定答案就好啦!

【HackerRank】Unique Colors

相关链接

题目传送门:https://www.hackerrank.com/challenges/unique-colors
题目大意:http://paste.ubuntu.com/23686184/
官方题解:https://www.hackerrank.com/challenges/unique-colors/editorial

解题报告

Ⅰ.点分治的做法 \(O(n\log (n))\)

先考虑一种较为简单的情况:只有一种颜色
询问有多少对节点间至少存在一个点被染色

直接考虑点分治的做法的话
设当前的分治点为1
定义\({T_i}\)为点i的子树中到1号点的路径上有点被染色的点的数量
定义\({S_i}\)为点i的子树中点的个数

考虑此时对 点3 的贡献:

  1. 点3点1 的简单路径上没有染色点
    那么贡献为\({T_1} – {T_3}\)
  2. 点3点1 的简单路径上有染色点
    那么贡献为\({S_1} – {S_2}\)

这样的话,只有一种颜色的情况就解决了
现在考虑有多种颜色:

不难发现对于一个固定点i,颜色只需要分两类:
1. 到分支点的路径上出现过该颜色
2. 到分治点的路径上没有出现过该颜色

这样的话,我们发现只需要一次DFS就可以解决这个问题
于是就可以使用点分治解决这个问题啦!

Ⅱ. DFS序的做法 \(O(n)\)

先来考虑一个简单一点的版本:假如这货是在序列上
考虑每一种颜色单独处理的话,就是将序列分成几块,每一块的贡献不同
现在重新考虑树上的版本,其实也是把整个树分成好几块
酱紫的话,我们唯一需要解决的就是如何给树上一个连通块统一加上一个数

我们发现我们可以先搞出一个最小的包含“需要更改的连通块”的子树
然后去掉一些子树来删掉多余的部分
子树操作的话,在DFS序上是连续的,于是打差分就好
因为题目的特殊性,不难发现需要删掉的子树总共只会有\(O(n)\)个
于是此题就 \(O(n)\) 辣!

Code

好像两种写法都不是特别好写的样子
于是就不写代码辣!