文章目录
- [Poi2014]Couriers
- problem
- solution
- code
- CodeChef - TKCONVEX
- problem
- solution
- code
随机算法的典型套路:枚举太花时,转化为随机一个数。然后通过对正确率的分析,选择一个随机的次数来卡。前提是要保证每一次检验随机是否为答案的时间复杂度要能够接受。一般不超过 nlognn\log nnlogn。
[Poi2014]Couriers
problem
BZOJ 3524
solution
如果有解,也就是说区间内的随机选择一个数是答案的概率不大于 12\frac 1 221。否则怎么随机都无解。
可以预处理相同值的数出现位置,二分查找求出某个值在 [l,r][l,r][l,r] 区间的出现次数。
所以一次判断是 O(logn)O(\log n)O(logn) 的。
随机 202020 次,正确率就在 1−10−51-10^{-5}1−10−5 左右了。
code
#include <bits/stdc++.h>
using namespace std;
#define maxn 500005
int n, m;
int a[maxn];
vector < int > pos[maxn];int find_l( int x, int p ) { int l = 0, r = pos[x].size() - 1, ans;while( l <= r ) {int mid = l + r >> 1;if( pos[x][mid] >= p ) ans = mid, r = mid - 1;else l = mid + 1;}return ans;
}int find_r( int x, int p ) {int l = 0, r = pos[x].size() - 1, ans;while( l <= r ) {int mid = l + r >> 1;if( pos[x][mid] <= p ) ans = mid, l = mid + 1;else r = mid - 1;}return ans;
}int main() {scanf( "%d %d", &n, &m );for( int i = 1;i <= n;i ++ ) {scanf( "%d", &a[i] );pos[a[i]].push_back( i );}mt19937 wwl(time(0));for( int i = 1, l, r;i <= m;i ++ ) {scanf( "%d %d", &l, &r );uniform_int_distribution < int > range( l, r );for( int k = 1;k <= 20;k ++ ) {int x = range( wwl ); x = a[x];int L = find_l( x, l );int R = find_r( x, r );if( R - L + 1 << 1 > r - l + 1 ) { printf( "%d\n", x ); goto next_turn; }}printf( "0\n" );next_turn:;}return 0;
}
CodeChef - TKCONVEX
problem
vjudge链接
solution
定理1
能构成多边形的线段,必须满足任意一条边长度都小于剩余所有边长度的总和。
这是显然的,因为要形成封闭的图形,可以看作是这条边是最后一条连接两个端点的直线段,其余线段绕成了一个弧形。
事实上只需要长度最长的线段满足这个性质即可。
此定理只能保证构成多边形,但不保证是凸多边形。所以提出下一个定理。
定理2
任意非凸多边形都可以通过对边的旋转和平移构成一个新凸多边形。
在本题中,可以选择的集合为 (n2k)(2kk)\binom{n}{2k}\binom{2k}{k}(2kn)(k2k),已经是非常大了,不可能一个一个检验。
定理3
假设有解,那么一定存在一个解(大小为 kkk 的子集)满足:对所有线段长度排序后,这个子集的元素对应排序后序列的一段连续区间。
证明:显然。假设子集为 SSS,排序后的序列为 PPP。
如果其对应的元素构成不连续的区间。即 ∃iPi∈S∧Pi+1∉S\exist_iP_i\in S\wedge P_{i+1}\notin S∃iPi∈S∧Pi+1∈/S。
找到满足条件的 iii,则 iii 肯定是一段连续区间的右端点,保证 iii 不是若干个不连续区间的最右端点。
重复操作移除 PiP_iPi,加入 Pi+1P_{i+1}Pi+1。直到将左边的若干个不连续区间和最右边的一个连续区间拼接成一整个大连续区间。
这样操作后,可以发现长度最大的依然是最右边的端点,没有移动过,但是其余线段的长度都有所增长。
既然原来都是符合条件的子集,那么现在肯定也是一个符合条件的子集。
题目要求两个大小为 kkk 的凸多边形。
不妨设第一个凸多边形从边集合为 SSS 中,选择另一个凸多边形从边集合 Sˉ\bar{S}Sˉ 中选择。
满足 S⋂Sˉ=∅∧S⋃Sˉ=ΩS\bigcap\bar{S}=\empty\wedge S\bigcup \bar{S}=\OmegaS⋂Sˉ=∅∧S⋃Sˉ=Ω。
那么对于每一条线段,有 12\frac 1 221 的概率属于 SSS,12\frac 1 221 的概率属于 Sˉ\bar{S}Sˉ。换言之,选出任意一个子集的概率是相同的。
那么就对每条边随机其属于哪个集合。
随机划分集合后,O(n)O(n)O(n) 的可以做到快速判断这个集合能否选出一个子集构成凸多边形。
最后来分析一下这个随机的正确率。
划分的总方案数为 2n2^n2n。考虑极端最坏情况,有且仅有两个互不相交的集合 X,YX,YX,Y 可以构成凸多边形。
那么一个随机划分方案有解,当且仅当 S⋂X=X∧S⋂Y=∅S\bigcap X=X\wedge S\bigcap Y=\emptyS⋂X=X∧S⋂Y=∅ 或者 S⋂Y=Y∧S⋂X=∅S\bigcap Y=Y\wedge S\bigcap X=\emptyS⋂Y=Y∧S⋂X=∅。
这样的方案数为 2n−2k×22^{n-2k}\times 22n−2k×2。
因此在最坏情况下选出一个合法划分的概率为 122k−1\frac{1}{2^{2k-1}}22k−11。
设随机运行以上算法 ttt 次,那么 ttt 最多取到 200002000020000 左右,时间复杂度为 O(nlogn+tn)O(n\log n+tn)O(nlogn+tn)。
在 k=6k=6k=6 时,正确率为 1−(1−1211)t≈1−10−51-(1-\frac{1}{2^{11}})^t≈1-10^{-5}1−(1−2111)t≈1−10−5。
注意:原题目 k∈[3,10]k\in[3,10]k∈[3,10]。这个随机算法是不能通过的。所以下面的代码不保证正确性。
code
#include <bits/stdc++.h>
using namespace std;
#define maxn 1005
#define int long long
struct node { int val, id; }a[maxn];
int n, k;
int s[2][maxn];
pair < int, int > ans[2];signed main() {scanf( "%lld %lld", &n, &k );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i].val ), a[i].id = i;sort( a + 1, a + n + 1, []( node x, node y ) { return x.val < y.val; } );mt19937 wwl;uniform_int_distribution < int > range( 0, 1 );for( int t = 1;t <= 20000;t ++ ) {s[0][0] = s[1][0] = 0;for( int i = 1;i <= n;i ++ ) {int x = range( wwl );s[x][++ s[x][0]] = i;}int cnt = 0;for( int j = 0;j <= 1;j ++ ) {for( int i = 1, len = 0, sum = 0;i <= s[j][0];i ++ ) {len ++, sum += a[s[j][i]].val;if( len > k ) sum -= a[s[j][i - k]].val;if( len == k and sum > (a[s[j][i]].val << 1) ) {ans[j] = make_pair( i - k + 1, i );cnt ++;break;}}}if( cnt == 2 ) goto yes;}no : return ! printf( "No\n" );yes :printf( "Yes\n" );for( int i = 0;i <= 1;i ++ )for( int j = ans[i].first;j <= ans[i].second;j ++ )printf( "%lld ", a[s[i][j]].id );return 0;
}