Loading

随机化的正确打开方式

前言

最近在考场上发现有的题目暴力搜索并不是很好打
而且很耗时间
效率低下
一个10以内的数据甚至要跑一个多小时
既然暴搜是枚举所有可能的答案进行最优性选择
那么可以通过多次随机枚举答案进行选择
保证答案只会更优不会更劣
因题而异不同的写法
一般情况来说
随机化程序能够拿到最劣暴力或者次劣暴力能拿到的分数
因为对应小的数据范围,随机化能涵盖到所有情况的概率也就越大
就等价于打了暴力枚举所有情况
不同的题目有不同的写法
有长有短
下面整理了从九月以来所有模拟赛的随机化代码以及部分题目
留做经验

随机化种子&时间限制

一般的话就用最简单的\(0\)足矣

srand((unsigned long long)time(NULL));

毕竟整个程序是随机的
而且对答案的影响不只是一个数值
所以每次随机出的数对答案影响并不大
只要保证不是都一样即可
如果想要更复杂的随机化方法
参考C++11 随机数生成器

同时因为枚举所有情况
希望能涵盖多种情况,越多越好
那么就让程序在不超出时间限制的情况下一直跑
如果超时就输出答案跑路
答案对不对就交给人品吧
判断时间函数&代码

inline bool time(){
	if((double)clock() / CLOCKS_PER_SEC <= 1.0) return 1;
	return 0;
}

一般判断比时间限制少一丢丢
如果代码将在评测机上运行
按照标准时间除以\(1.5\)倍向下限制比较稳妥

题目&随机化程序

先来镇楼~

联赛模拟测试14-D.笨小猴

大意

Solution

这是唯一一道随机化\(Accepted\)的题目
得益于\(Special \:\ Judge\)和题目本身有好多好多组解的性质
很容易随机到一个合法序列

Code

笨小猴
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return x * w;
}

const int ss = 200010;

struct card{
	int a, b, id;
	inline bool operator < (const card &x) const {
		return x.a < a;
	}
}c[ss], ans[ss];

long long suma, sumb;
int id[ss], n;
inline void RAND(){
	register int tim = 500;
	register bool flag = 0;
	while(tim--){
		random_shuffle(c + 1, c + 1 + n + n + 1);
		register long long tmpa = 0, tmpb = 0;
		for(register int i = 1; i <= n + 1; i++){
			tmpa += c[i].a, tmpb += c[i].b;
			id[i] = c[i].id;
		}
		if(tmpa >= suma - tmpa && tmpb >= sumb - tmpb){
			flag = 1;
			for(register int i = 1; i <= n + 1; i++)
				printf("%d\n", id[i]);
			exit(0);
		}
		if(flag == 1) break;
	}
	if(!flag) puts("-1");
	exit(0);
}

bool vis[ss];
signed main(){
	srand(time(0));
	n = read();
	for(register int i = 1; i <= 2 * n + 1; i++){
		c[i].a = read(), c[i].b = read();
		c[i].id = i;
		suma += c[i].a, sumb += c[i].b;
	}
	RAND();
	return 0;
}

联赛模拟测试21-D.格式化

大意

Solution

对于小于\(10\)的情况枚举全排列
那么剩下的可以按照上述思路随机生成一个序列
更新答案即可
暴力得分\(30pts\)
随机化得分\(45pts\)

Code

格式化
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <ctime>
#define min(a, b) ({int AA = a, BB = b; AA < BB ? AA : BB;})
#define int long long
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return w * x;
}

const int ss = 100005;

struct node{
	int a, b;
	inline bool operator < (const node &x) const {
		if(x.a == a) return x.b < b;
		return x.a > a;
	}
}t[ss];

clock_t st;
inline bool time(){
	if((double)(clock() - st) / CLOCKS_PER_SEC <= 1.8) return 1;
	return 0;
}

int a[ss];
signed main(){
	freopen("reformat.in", "r", stdin);
	freopen("reformat.out", "w", stdout);
	st;
	register int n = read();
	for(register int i = 1; i <= n; i++)
		t[i].a = read(), t[i].b = read(), a[i] = i;
	register int ans = 0x7fffffff;
	
	if(n <= 10){
		do{
			register long long sum = 0;
			register int tmp = 0;
			for(register int i = 1; i <= n; i++){
				sum += t[a[i]].a;
				tmp = max(tmp, sum);
				sum -= t[a[i]].b;
			}
			ans = min(ans, tmp);
		}while(next_permutation(a + 1, a + 1 + n));
		printf("%lld\n", ans);
		return 0;
	}
	
	while(time()){
		random_shuffle(t + 1, t + 1 + n);
		register long long sum = 0;
		register int tmp = 0;
		for(register int i = 1; i <= n; i++){
			sum += t[a[i]].a;
			tmp = max(tmp, sum);
			sum -= t[a[i]].b;
		}
		ans = min(ans, tmp);
	}
	printf("%lld\n", ans);
	return 0;
}

联赛模拟测试25-B. Confess

大意

Solution

有一说一这个题目当时考场没看懂
就按照自己理解的转换再判断了
和上面一样\(SPJ\)是很容易\(Rand\)到合法解的
甚至这个题不用判断直接随机两个数字
再加上上等人品是可以拿到\(70pts\)
理论上多交几次可以直接过
但是没有什么意义
就不放代码了

联赛模拟测试26-A.字符交换

大意

Solution

直接随机生成\(k\)次交换的坐标并直接交换
用当前的答案更新总答案
暴力得分\(40pts\)
随机化得分\(60~70pts\)

Code

字符交换
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define max(a, b) ({register int AA = a, BB = b; AA > BB ? AA : BB;})
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return x * w;
}

const int ss = 4005;

int pos[30][ss], cnt[30];
int dis[30], tot;

struct node{
	int id, dis;
	inline bool operator < (const node &x) const{
		return x.dis > dis;
	}
}t[30];

clock_t st;
inline bool time(){
	if((double)(clock() - st) / CLOCKS_PER_SEC <= 0.95) return 1;
	return 0;
}

char s[ss], tmp[ss];
int ans, ans1, n, k;
inline void RAND(){
	memcpy(s, tmp, sizeof s);
	register int cnt = 1;
	register int tot = k;
	while(tot--){
		int pos = rand() % (n - 1) + 1;
		swap(s[pos], s[pos + 1]);
		char tmp = s[1];
		register int len = 1;
		for(register int i = 2; i <= n; i++){
			if(s[i] == tmp) len++, cnt = max(cnt, len);
			else tmp = s[i], len = 1;
		}
	}
	ans = max(ans, cnt);
}

signed main(){
	freopen("swap.in", "r", stdin);
	freopen("swap.out", "w", stdout);
	st;
	srand(time(0));
	n = read(), k = read();
	scanf("%s", s + 1);
	
	for(register int i = 1; i <= n; i++){
		pos[s[i] - 'a' + 1][++cnt[s[i] - 'a' + 1]] = i;
	}
	memset(dis, 0x3f, sizeof dis);
	//pos[a][id]表示字母a出现的第id个位置为i
	for(register int i = 1; i <= 26; i++){//计算每个字母要走的最小距离
		for(register int j = 1; j <= cnt[i]; j++){//枚举每个出现的位置
			register int id = pos[i][j];
			register int tmp = 0;
			for(register int k = 1; k <= cnt[i]; k++){//求其他点到当前点的距离和
				if(pos[i][k] < id) tmp += id - pos[i][k] - 1;
				if(pos[i][k] > id) tmp += pos[i][k] - id - 1;
			}
			dis[i] = min(dis[i], tmp);
		}
	}
	for(register int i = 1; i <= 26; i++){
		if(dis[i] == 0x3f3f3f3f) continue;
		t[++tot].id = i, t[tot].dis = dis[i];
	}
	sort(t + 1, t + 1 + tot);
	for(register int i = tot; i >= 1; i--){
		if(k >= t[i].dis){
			ans1 = cnt[t[i].id];
			break;
		}
	}
	
	memcpy(tmp, s, sizeof s);
	register int len = 1, cnt = 1;
	register int tmp = s[1];
	for(register int i = 2; i <= n; i++){
		if(s[i] == tmp) len++, cnt = max(cnt, len);
		else tmp = s[i], len = 1;
	}
	ans = max(ans, len);
	while(time()) RAND();
	printf("%d\n", max(ans, ans1));
	return 0;
}

上午小测2-B.P&Q

大意

给定\(n\)个数每次可以选择两个,分别减去\(p,q\)
问最多可以减去几次

Solution

这和下面有一道题目很像
随机出两个下标进行操作
每次次数取最大
暴力得分\(20pts\)
随机化得分\(55pts\)

Code

P&Q
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define max(a, b) ({register int AA = a, BB = b; AA > BB ? AA : BB;})
#define int long long
using namespace std;

inline int read(){
	register int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return x * w;
}

const int ss = 52;

clock_t st;
inline bool time(){
	if((double)(clock() - st) / CLOCKS_PER_SEC <= 0.9) return 1;
	return 0;
}

int a[ss], tmp[ss], cnt, n, p, q;
inline void RAND(){
	memcpy(a, tmp, sizeof tmp);
	register int num = 0;
	register int tot = 1000;
	while(tot--){
		register int x = rand() % n + 1, y = rand() % n + 1;
		while(x == y){x = rand() % n + 1;}
		if(a[x] >= p && a[y] >= q) a[x] -= p, a[y] -= q, num++;
	}
	cnt = max(cnt, num);
}

signed main(){
	srand((unsigned long long)time(NULL));
	n = read();
	for(register int i = 1; i <= n; i++) tmp[i] = a[i] = read();
	p = read(), q = read();
	register int tmp = max(p, q);
	p = p + q - tmp, q = tmp;
	while(time()) RAND();
	printf("%d\n", cnt * (p + q));
	return 0;
}

上午小测5-A. 砍树

大意

Solution

随机化一个答案判断是否合法
即能否砍掉所有的树
取最大即可
暴力得分\(20pts\)
随机化得分\(20pts\)

Code

砍树
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define max(a, b) ({register int AA = a, BB = b; AA > BB ? AA : BB;})
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return x * w;
}

const int ss = 105;

int a[ss], n, maxx;
long long kk;

inline int gcd(register int a, register int b){
	return !b ? a : gcd(b, a % b);
}

inline bool check(register int d){}

inline bool tim(){
	if((double)clock() / CLOCKS_PER_SEC <= 1.35) return 1;
	return 0;
}

int ans;
inline void RAND(){
	register int tot = 600000;
	while(tot--){
		register int d = rand() % 500000 + 1;
		register long long tot = 0, k = 1;
		for(register int i = 1; i <= n; i++){
			while(a[i] > k * d) k++;
			tot += k * d - a[i];
		}
		if(tot <= kk) ans = max(ans, d);
	}
}

signed main(){
	srand(time(NULL));
	n = read(), kk = read();
	for(register int i = 1; i <= n; i++) a[i] = read(), maxx = max(maxx, a[i]);
	stable_sort(a + 1, a + 1 + n);
	if(kk == 0){
		register int g = a[1];
		for(register int i = 2; i <= n; i++)
			g = gcd(a[i] - a[i - 1], gcd(a[i - 1] - a[i - 2], g));
		if(g == 1) ans = 1;
		else ans = g;
		printf("%d\n", ans);
		return 0;
	}
	while(tim()) RAND();
	printf("%d\n", ans);
	return 0;
}

</details>

### 联赛模拟测试30-B.bird
#### 大意
![](https://img2020.cnblogs.com/blog/1718530/202011/1718530-20201123194126161-146464925.png)

#### Solution
对答案影响比较大的开枪的时间
那么就来随机它
每次随机100次开枪的时间看能打下来多少鸟
取最大
暴力得分$30pts$
随机化得分$20pts$

#### Code

<details>
<summary> Bird </summary>

```cpp
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define max(a, b) ({register int AA = a, BB = b; AA > BB ? AA : BB;})
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return w * x;
}

const int ss = 1000100;

int n, k, cnt, maxx;
int tmpl[ss], tmpr[ss];

struct bird{
	int l, r;
	inline bool operator < (const bird &x) const {
		if(x.l == l) return x.r > r;
		return x.l > l;
	}
}a[ss], b[ss];

inline bool tim(){
	if((double) clock() / CLOCKS_PER_SEC <= 0.85) return 1;
	return 0;
}

int ans;
int vis[ss];
bool bird[ss];
inline void RAND(){
	register int tot = 100;
	while(tot--){
		memset(bird, 0, sizeof bird);
		memcpy(a, b, sizeof b);
		register int num = 0;
		register int d = rand() % k + 1;
		register int last = 0;
		register int tmp = d;
		while(d <= maxx - tmp){
			d += tmp;
			if(!bird[vis[d]]) num++;
			bird[vis[d]] = 1;
		}
		ans = max(ans, num);
	}
}

signed main(){
	srand(time(0));
	n = read(), k = read();
	for(register int i = 1; i <= n; i++){
		register int head = read(), tail = read();
		if(tail < 0) continue;
		a[++cnt].l = head, a[cnt].r = tail;
		maxx = max(maxx, tail);
		for(register int j = head; j <= tail; j++)
			vis[j] = cnt;
	}
	sort(a + 1, a + 1 + cnt);
	memcpy(b, a, sizeof b);
	while(tim()) RAND();
	cout << ans << endl;
	return 0;
}

联赛模拟测试33-A. 合并集合

大意

Solution

随机对序列上的集合进行合并
这个代码实现难点在于
合并之后要将所有后面的集合提前更新下标
而且整个序列长度减一
每次用结果更新答案取最大即可
暴力得分\(30pts\)
随机化得分\(30pts\)

Code

合并集合


#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define max(a, b) ({register int AA = a, BB = b; AA > BB ? AA : BB;})
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return x * w;
}

const int ss = 1005;

int a[ss], b[ss];
int belong[ss][ss], bt[ss][ss], size[ss], st[ss];

inline bool tim(){
	if((double)(clock()) / CLOCKS_PER_SEC <= 1.95) return 1;
	return 0;
}

int n, maxx, ans;
inline void RAND(){
	memcpy(a, b, sizeof b);
	memcpy(belong, bt, sizeof bt);
	memcpy(size, st, sizeof st);
	register int tot = n - 1;
	register int tmp = n, cnt = 0;
	while(tot--){
		register int pos = rand() % tmp + 1;
		register int nxt = pos + 1;
		if(nxt == tmp + 1) nxt = 1;
//		cout << "pos = " << pos << " nxt = " << nxt << endl;
		cnt += size[pos] * size[nxt];
		size[pos] += size[nxt];
		for(register int i = 1; i <= maxx; i++){
			if(belong[pos][i] == belong[nxt][i] && belong[pos][i] == 1)
				size[pos]--;
			else if(belong[pos][i] == 0 && belong[nxt][i] == 1)
				belong[pos][i] = 1;
		}
		/*
		for(register int i = 1; i <= tmp; i++)
			printf("size[%d] = %d\n", i, size[i]);
		*/
		for(register int i = nxt; i < tmp; i++){
			size[i] = size[i + 1];
			for(register int j = 1; j <= maxx; j++)
				belong[i][j] = belong[i + 1][j];
		}
		/*
		for(register int i = 1; i <= tmp; i++, puts(""))
			for(register int j = 1; j <= maxx; j++)
				printf("belong[%d][%d] = %d\n", i, j, belong[i][j]);
		*/
		tmp--;
	}
	ans = max(ans, cnt);
}

signed main(){
	freopen("merge.in", "r", stdin);
	freopen("merge.out", "w", stdout);
	srand((unsigned long long)time(NULL));
	n = read();
	for(register int i = 1; i <= n; i++){
		a[i] = read();
		b[i] = a[i];
		maxx = max(maxx, a[i]);
		belong[i][a[i]] = 1;
		size[i] = 1;
	}
	memcpy(bt, belong, sizeof belong);
	memcpy(st, size, sizeof size);
	while(tim()) RAND();
	printf("%d\n", ans);
	return 0;
}

联赛模拟测试34-D.赤壁情

大意

给定\(n,m,k\)
随机一个长度为\(n\)的全排列
问相邻两数绝对值之差不小于\(m\)的概率为多大
保留\(k\)位小数

Solution

因题而宜
由于要求概率那么答案=合法情况/所有情况
所以对于小于\(10\)的部分依然全排列保证答案的正确性
但是大于\(10\)的部分因为随机化生成的序列不能保证全部覆盖或者没有重复
所以精度会有一定的损失
但是两种情况不能全部覆盖会导致答案变小
有重复会导致答案偏大
近似认为两者误差在方案很多的时候能够互补
所以此处不做判断
直接生成序列求合法方案数量
如果对于不可重复计数的题目
看最下面的小技巧
暴力得分\(30pts\)
随机化得分\(30~60pts\)

Code

赤壁情
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return x * w;
}

const int ss = 105;

inline bool tim(){
	if((double) clock() / CLOCKS_PER_SEC <= 3.0) return 1;
	return 0;
}

int a[ss];
signed main(){
	srand((unsigned long long)time(NULL));
	register int n = read(), m = read(), k = read();
	if(m <= (n - 1)){
		if(k == 3) printf("%.3lf\n", 1.0);
		if(k == 5) printf("%.5lf\n", 1.0);
		if(k == 8) printf("%.8lf\n", 1.0);
		return 0;
	}
	for(register int i = 1; i <= n; i++) a[i] = i;
	
	if(n <= 10){
		register int cnt = 0, num = 0;
		do{
			num++;
			register int tmp = 0;
			for(register int i = 1; i < n; i++)
				tmp += abs(a[i] - a[i + 1]);
			if(tmp >= m) cnt++;
		}while(next_permutation(a + 1, a + 1 + n));
		if(k == 1) printf("%.1lf\n", (double)cnt / num);
		if(k == 2) printf("%.2lf\n", (double)cnt / num);
		if(k == 3) printf("%.3lf\n", (double)cnt / num);
		if(k == 4) printf("%.4lf\n", (double)cnt / num);
		if(k == 5) printf("%.5lf\n", (double)cnt / num);
		if(k == 6) printf("%.6lf\n", (double)cnt / num);
		if(k == 7) printf("%.7lf\n", (double)cnt / num);
		if(k == 8) printf("%.8lf\n", (double)cnt / num);
		return 0;
	}
	
	register int cnt = 0, num = 0;
	while(tim()){
		num++;
		random_shuffle(a + 1, a + 1 + n);
		register int tmp = 0;
		for(int j = 1; j < n; j++)
			tmp += abs(a[j] - a[j + 1]);
		if(tmp >= m) cnt++;
	}
	if(k == 3) printf("%.3lf\n", (double)cnt / num);
	if(k == 5) printf("%.5lf\n", (double)cnt / num);
	if(k == 8) printf("%.8lf\n", (double)cnt / num);
	return 0;
}

联赛模拟测试35-A. 组合

大意

Solution

这一看就很可以随机
对于不可交换
将多元组随机排列(多元组排列见小技巧)
像上面那个合并集合一样
暴力合并判断合法直接输出
跑到最后都没有合法解就输出\(No\)
对于可交换的情况
随机一个标号的卡片交换前后顺序再按照上面的情况随机排列判断答案即可
因为第二种可交换的方案数过多
所以这样随机出来的情况是远远不够的
导致分数偏少
暴力得分\(40pts\)
随机化得分\(25pts\)

Code

组合
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return x * w;
}

const int ss = 100010;

inline bool tim(){
	if((double) clock() / CLOCKS_PER_SEC <= 1.33) return 1;
	return 0;
}

int a[ss][2];
int t[ss];
int op, n, m;
int cnt[ss];
bool vis[ss];
inline void RAND_1(){
	register int pos = rand() % m + 1;
	if(!vis[pos]) vis[pos] = 1;
	else vis[pos] = 0;
	swap(a[pos][1], a[pos][2]);
	register bool flag = 1;
	do{
		flag = 1;
		for(register int i = 1; i < m; i++){
			if(a[t[i]][2] != a[t[i + 1]][1]){
				flag = 0;
				break;
			}
		}
		if(flag){
			puts("YES");
			for(register int i = 1; i <= m; i++){
				if(vis[t[i]]) printf("%d ", -t[i]);
				else printf("%d ", t[i]);
			}
			puts("");
			exit(0);
		}
	}while(next_permutation(t + 1, t + 1 + m));
}

inline int RAND_2(){
	register int pos = rand() % m + 1;
	if(!vis[pos]) vis[pos] = 1;
	else vis[pos] = 0;
	swap(a[pos][1], a[pos][2]);
	register bool flag = 1;
	while(tim()){
		flag = 1;
		random_shuffle(t + 1, t + 1 + m);
		for(register int i = 1; i < m; i++)
			if(a[t[i]][2] != a[t[i + 1]][1]){
				flag = 0;
				break;
			}
		if(flag){
			puts("YES");
			for(register int i = 1; i <= m; i++){
				if(vis[t[i]]) printf("%d ", -t[i]);
				else printf("%d ", t[i]);
			}
			puts("");
			exit(0);
		}
	}
	puts("NO");
	exit(0);
}

signed main(){
	srand((unsigned long long)time(NULL));
	op = read(), n = read(), m = read();
	for(register int i = 1; i <= m; i++) t[i] = i;
	for(register int i = 1; i <= m; i++){
		a[i][1] = read(), a[i][2] = read();
	}
	if(n <= 6 && m <= 15){
		if(op == 2){
			register bool flag = 1;
			do{
				flag = 1;
				for(register int i = 1; i < m; i++){
					if(a[t[i]][2] != a[t[i + 1]][1]){
						flag = 0;
						break;
					}
				}
				if(flag){
					puts("YES");
					for(register int i = 1; i <= m; i++)
						printf("%d ", t[i]);
					puts("");
					return 0;
				}
			}while(next_permutation(t + 1, t + 1 + m));
			if(!flag) return puts("NO"), 0;
			return 0;
		}
		while(tim()) RAND_1();
		puts("NO");
		return 0;
	}
	if(op == 2){
		register bool flag = 1;
		while(tim()){
			flag = 1;
			random_shuffle(t + 1, t + 1 + m);
			for(register int i = 1; i < m; i++)
				if(a[t[i]][2] != a[t[i + 1]][1]){
					flag = 0;
					break;
				}
			if(flag){
				puts("YES");
				for(register int i = 1; i <= m; i++)
					printf("%d ", t[i]);
				puts("");
				return 0;
			}
		}
		puts("NO");
		return 0;
	}
	RAND_2();
	return 0;
}
/*
2 7 7 
1 2
2 3
3 4
4 5
5 6
6 7
7 1
*/

联赛模拟测试37-B.斗地主

大意

Solution

随机+贪心
从前往后能选则选
记录每个数字能向后链接到最大的链
查询的时候直接判断即可
那么如果选择从前往后贪心
最好的情况就是牌都排好了
一溜扫过去就可
但是显然不可能
那么就随机这个牌的序列
每次更新每个数字的最远扩展
查询即可
暴力得分\(40pts\)
随机化得分\(40pts\)

Code

斗地主
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <map>
#define max(a, b) ({register int AA = a, BB = b; AA > BB ? AA : BB;})
using namespace std;

inline int read(){
	int x = 0, w = 1;
	char ch = getchar();
	for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	return x * w;
}

const int ss = 200010;

struct node{
	int a, b;
	inline bool operator < (const node &x) const {
		if(a == x.a) return x.b < b;
		return x.a > a;
	}
}t[ss];
map<int, int> vis[ss];
int cnt[ss];
int sum[ss], ans[ss];
queue<int> q;
bool chos[ss];

int n, k;
inline void wdnmd(){
	memset(cnt, 0, sizeof cnt);
	for(register int i = 1; i <= n; i++)
		vis[i].clear();
	for(register int i = 1; i <= k; i++){
		cnt[t[i].a]++;
		vis[t[i].a][cnt[t[i].a]] = i;
		cnt[t[i].b]++;
		vis[t[i].b][cnt[t[i].b]] = i;
	}
	/*
	for(register int i = 1; i <= n; i++){
		for(register int j = 1; j <= cnt[i]; j++){
			printf("%d --> %d is %d\n", i, j, vis[i][j]);
		}
	}
	*/
	for(register int i = 1; i <= n; i++){
//		cout << "\ni = " << i << endl;
		memset(chos, 0, sizeof chos);
		if(!cnt[i]){
			ans[i] = 0;
			continue;
		}
		ans[i] = max(i, ans[i]);
		chos[vis[i][1]] = 1;
		q.push(i);
		while(!q.empty()){
			/*
			for(register int j = 1; j <= k; j++)
				cout << chos[j] << " ";
			puts("");
			*/
			register int u = q.front();
			q.pop();
//			cout << "u = " << u << endl;
			register int tmp = u + 1;
//			printf("cnt[%d] = %d\n", tmp, cnt[tmp]);
			for(register int j = 1; j <= cnt[tmp]; j++){
				register int pos = vis[tmp][j];
//				printf("%d全部的%d\n", tmp, pos);
				if(chos[pos]) continue;
//				printf("%d能选的%d\n", tmp, pos);
				chos[pos] = 1;
				ans[i] = max(ans[i], tmp);
				q.push(tmp);
				break;
			}
		}
//		cout << ans[i] << endl;
	}
	/*
	puts("");
	for(register int i = 1; i <= n; i++)
		cout << ans[i] << "\n";
	*/
}

inline bool tim(){
	if((double) clock() / CLOCKS_PER_SEC <= 0.95) return 1;
	return 0;
}

signed main(){
	srand(time(NULL));
	n = read(), k = read();
	register bool flag = 1;
	for(register int i = 1; i <= k; i++){
		t[i].a = read(), t[i].b = read();
		if(t[i].a > t[i].b) swap(t[i].a, t[i].b);
		sum[t[i].a] = 1;
		if(t[i].a != t[i].b) flag = 0;
	}
	if(flag){
		for(register int i = 1; i <= n; i++)
			sum[i] += sum[i - 1];
		register int q = read();
		while(q--){
			register int l = read(), r = read();
			if(sum[r] - sum[l - 1] == r - l + 1) puts("Yes");
			else puts("No");
		}
		return 0;
	}
	
	sort(t + 1, t + 1 + k);
	wdnmd();
	while(tim()){
		random_shuffle(t + 1, t + 1 + k);
		wdnmd();
	}
	/*
	for(register int i = 1; i <= 8000; i++){
		random_shuffle(t + 1, t + 1 + k);
		wdnmd();
	}
	*/
	register int q = read();
	while(q--){
		register int l = read(), r = read();
		if(r < l) puts("No");
		else if(r > ans[l]) puts("No");
		else puts("Yes");
	}
}

注意事项&小技巧

  • \(SPJ\)的题目一定要\(Rand\)合法解,给过的可能行很大
  • 如果每次生成答案的程序本身就要随机很多次并且更改原数组,需要备份一个原数组在下次随机的时候还原
  • 如果随机一次就需要很长时间,需要调整时间限制大小
  • 有误差可以互补的概率题可以直接忽略,如果不能忽略,可以采用哈希的思想记录每种情况,重复的就不再计入答案
  • 对于多元组排列,\(STL\)并不支持,可能也是博主不知道,可以另外开一个数组记录下标,对下标进行全排列,调用的时候调用随机后的下标可以达到一样的效果

小结

随机化也是有很多小技巧的
考场上遇到实在不会的题目当然可以打随机化骗分
赛后一定要及时学习练习正解的打法或者正常一点的暴力
码题愉快~
\(RP++\)

posted @ 2020-11-23 20:38  Gary_818  阅读(398)  评论(3编辑  收藏  举报