ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Petrozavodsk Summer 2022. Day 1. Welcome Contest

2022-09-10 11:30:37  阅读:224  来源: 互联网

标签:Summer cnt cur Contest int rep Welcome num now


Petrozavodsk Summer 2022. Day 1. Welcome Contest

是不是又开新坑了,毛营我来了!

挑几道自己会的 & 有意思 的题写题解 QwQ

D - Double Sort

  • 给定 \(n,m(n\leq m)\),随机一个值域在 \([1,m]\) 且数字不重复的的长度为 \(n\) 的序列 \(a_i\)。
  • 令 \(a_0=0\),将 \(a\) 数组排序,差分,再排序,再求前缀和。
  • 如上操作后对 \(i\in[1,n]\) 每个位置求 \(a'_i\) 的期望值。
  • \(n\leq 50,m\leq 10000\)。

首先最后前缀和的过程可以省略,直接求每个位置差分的期望值,最后加回来即可。

直接设 \(f(i,j,k)\) 表示:序列长度为 \(i\),值域为 \([1,j]\) 时 \(a'_{k+1}-a'_{k}\) 的期望值,那么 \(\text{ans}_i=\sum_{j=0}^{i-1} f(n,m,j)\)。

因为将差分排序了,转移不妨将所有差分 \(-1\),并将所有大小为 \(1\) 的差分都去掉,来归入值域更小的子问题。

考虑枚举大小为 \(1\) 的差分个数 \(l\),不难得到递推式:

\[f(i,j,k)=\frac{1}{\binom{j}{i}}\sum_{l=0}^{i} \binom{i}{l}\binom{j-i}{i-l} ([l<k](f(i-l,j-i,k-l)+1)+[l\geq k]) \]

直接计算就是 \(O(n^3m)\) 的,喜闻乐见的跑不满。

code
#include<bits/stdc++.h>
typedef long long ll;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;

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

const int N = 55, M = 10010;
int n, m; double c[M][N], f[N][M][N];

void prework() {
	c[0][0] = 1.0;
	rep(i, 1, m) {
		c[i][0] = 1.0;
		rep(j, 1, min(i, n)) c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
	}
}

int main() {
	n = read(), m = read();
	prework();
	rep(j, 1, m) rep(i, 1, min(n, j)) rep(x, 0, i) {
		double p = c[i][x] * c[j - i][i - x] / c[j][i];
		rep(k, 1, x)
			f[i][j][k] += p * 1.0;
		rep(k, x + 1, i)
			f[i][j][k] += p * (f[i - x][j - i][k - x] + 1.0);
	}
	double ans = 0.0;
	rep(i, 1, n) ans += f[n][m][i], printf("%.9f\n", ans);
	return 0;
}

F - Leaderboard Effect

  • 有 \(n\) 支队伍参加一场有 \(m\) 道题目的 ACM 比赛,限时 \(t\) 秒。

  • 每道题有参数 \(r_i,c_i,p_i\),分别表示:读题时间,做题时间,做出题目的概率。

  • 每支队伍决策均如下:

    1. 挑选一道没读过的题目 \(i\),如果全都读过了那么剩余时间什么都不做。

      挑选方式:如果所有没做过的题均无人通过,那么随机一道。否则按照通过人数加权随机一道。

    2. 使用 \(r_i\) 时间读题(即使已知时间不够了),读完后能够立刻知道能否做出这题,以 \(p_i\) 的概率能做出。

    3. 如果能做出,那么使用 \(c_i\) 时间 AC 这道题(即使已知时间不够了),榜上通过这题的人数 \(+1\),并回到步骤 1。否则直接回到步骤 1。

  • 对每道题 \(i\),令 \(f(n,i)\) 表示这 \(n\) 支队伍中最终做出这题的人数,求 \(\lim_{n\to \infty} \frac{f(n,i)}{n}\)。

  • \(m\leq 17,t\leq 100\)。

最后一句话实际就是求每道题通过的概率。

一开始拿到这题发现条件繁多,看数据范围知道是状压,但完全不知道加权平均怎么决策。

实际上根据概率的性质直接维护就好,设 \(f(i,j)\) 表示在时间 \(i\) 题目 \(j\) 已经被通过的概率。

\(g(i,s)\) 表示在时间 \(i\) 开始新一轮决策,已经读过的题目集合为 \(s\) 的局面出现的概率。

转移直接转移就好,感觉很厉害。

code
#include<bits/stdc++.h>
typedef long long ll;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;

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

const int N = 17, M = 110;
const double eps = 1e-10;
int n, m, r[N], c[N];
double p[N], f[M][N], g[M][1 << N];

int main() {
	n = read(), m = read();
	rep(i, 0, n - 1) r[i] = read(), c[i] = read(), scanf("%lf", &p[i]);
	g[0][0] = 1.0;
	rep(i, 0, m) {
		if(i) rep(o, 0, n - 1) f[i][o] += f[i - 1][o];
		rep(s, 0, (1 << n) - 1) {
			double sum = 0.0; int cnt = 0;
			rep(o, 0, n - 1) if(! (s >> o & 1)) sum += f[i][o], cnt ++;
			rep(o, 0, n - 1) if(! (s >> o & 1)) {
				double cur = (sum > eps) ? (f[i][o] / sum) : (1.0 / cnt);
				if(i + r[o] <= m)
					g[i + r[o]][s | (1 << o)] += cur * (1 - p[o]) * g[i][s];
				if(i + r[o] + c[o] <= m)
					f[i + r[o] + c[o]][o] += cur * p[o] * g[i][s], 
					g[i + r[o] + c[o]][s | (1 << o)] += cur * p[o] * g[i][s];
			}
		}
	}
	rep(i, 0, n - 1) printf("%.10f\n", f[m][i]);
	return 0;
}

H - Ranked Choice Spoiling

  • 有 \(2\) 或 \(3\) 个候选人参与选票,分别编号 A,B,以及可能有的 C。
  • 已知 \(n\) 个选民心中的候选人排行,选举过程如下:
    • 每个人向还存在的候选人中自己心中排行最高的投票。
    • 如果有人得票超过半数那么直接胜利。
    • 否则排除掉得票最少的候选人,得票一样少优先排除字典序大的。
  • 现在你作为 A 候选人,希望自己赢得选举。你可以新加入一个候选人 Z,并随意控制选美心中他的排名。
  • 询问 A 是否能赢得选举。
  • \(n\leq 1000\)。

首先只有两个人是简单的,只有两种排行:AB 和 BA。

如果 A 本来就能赢那就赢了,否则第一轮就一定是要让 B 出局的,方法是将一些 BA 变为 ZBA。

当然同时不能让 Z 喧宾夺主了,不难计算发现极限情况是 AB : BA = 1 : 3。

对于三个的情况也是类似的。

首先容易证明 Z 只会放在开头或结尾。

然后枚举先排除 B 还是 C,并尽量少的放 Z 来排除掉他,以 B 为例。

一定是优先转变 BCA,因为 BAC 在 B 出局后就能给 A 贡献。之后考虑两种情况:

  • Z 先出局。这种情况很好判定,所有 Z 的得票不减,其它人的不增,所以直接考虑当前局面 Z 能否出局,然后变成两个人的情况。
  • C 先出局。那么类似的考虑不断添加 Z 来干掉 C 即可,注意到 C 出局后没有被添加 Z 的 C 一定是贡献给 A 的。

先排除 C 的决策是类似的,所以这题只是比较简单的贪心。但是这是赛时通过队伍最少的一道题?

code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;

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

const int N = 1010;
int n, m, num[3], cnt[3], sta[6], cur[6];

bool solve0() {
	rep(i, 0, 2) cnt[i] = num[i];
	if(cnt[1] > n / 2 || cnt[2] > n / 2) return false;
	if(cnt[1] > cnt[0] && cnt[2] > cnt[0]) return false;
	if(cnt[2] <= cnt[1]) {
		cnt[0] += sta[4], cnt[1] += sta[5];
		return 3 * cnt[0] >= cnt[1];
	}
	else {
		cnt[0] += sta[2], cnt[2] += sta[3];
		return 3 * cnt[0] >= cnt[2];
	}
}

bool solve1() {
	if(! num[2]) return false;
	
	int now[4];
	rep(i, 0, 3) now[i] = 0;
	rep(i, 0, 5) cur[i] = sta[i];

	if(num[1]) {
		int aim = min(num[0], min(num[1], num[2]));
		if(num[2] == aim) aim --;
		int ned = num[1] - aim;
		
		if(cur[3] >= ned) cur[3] -= ned, now[3] += ned;
		else {
			ned -= cur[3], now[3] += cur[3], cur[3] = 0;
			cur[2] -= ned, now[2] += ned;
		}
	}
	
	now[0] = num[0] + cur[2];
	now[1] = num[2] + cur[3];
	
	int x = now[0], y = now[1], z = now[2] + now[3];
	if(y > x) z += y - x, y = x;
	while(z <= y) z ++, y --;
	if(x + y >= z) return true;
	
	x = now[0], y = now[1], z = now[2] + now[3];
	if(z <= x && z <= y && x + now[2] >= y + now[3]) return true;
	return false;
}

bool solve2() {
	int now[4];
	rep(i, 0, 3) now[i] = 0;
	rep(i, 0, 5) cur[i] = sta[i];
	
	if(num[2]) {
		int aim = min(num[0], min(num[1], num[2]));
		int ned = num[2] - aim;
		if(cur[5] >= ned) cur[5] -= ned, now[3] += ned;
		else {
			ned -= cur[5], now[3] += cur[5], cur[5] = 0;
			cur[4] -= ned, now[2] += ned; 
		}
	}
	
	now[0] = num[0] + cur[4];
	now[1] = num[1] + cur[5];
	
	int x = now[0], y = now[1], z = now[2] + now[3];
	if(y > x) z += y - x, y = x;
	while(z <= y) z ++, y --;
	if(x + y >= z) return true;
	
	x = now[0], y = now[1], z = now[2] + now[3];
	if(z <= x && z <= y && x + now[2] >= y + now[3]) return true;
	return false;
}

int main() {
	n = read(), m = read();

	if(m == 2) {
		rep(i, 1, n) {
			char a[3]; scanf("%s", a);
			char b[3]; scanf("%s", b);
			if(a[0] == 'A') cnt[0] ++; else cnt[1] ++;
		}
		puts(3 * cnt[0] >= cnt[1] ? "1" : "0");
		return 0;
	}

	rep(i, 1, n) {
		char a[3]; scanf("%s", a);
		char b[3]; scanf("%s", b);
		char c[3]; scanf("%s", c);
		num[a[0] - 'A'] ++;
		if(a[0] == 'A') sta[b[0] == 'B' ? 0 : 1] ++;
		else if(a[0] == 'B') sta[b[0] == 'A' ? 2 : 3] ++;
		else sta[b[0] == 'A' ? 4 : 5] ++;
	}
	
	if(solve0() || solve1() || solve2()) puts("1"); else puts("0");
	return 0;
}

K - Transparency

  • 给定一个 \(n\) 个节点的自动机,\(1\) 为初始节点,有 \(p\) 个终止节点。
  • 转移只有大小写字母,求构造两个自动机可接受的串 \(a,b\),使得 \(a,b\) 在去掉小写字母后完全相同,但是 \(a,b\) 本身不同。
  • 输出最小的 \(|a|+|b|\)。
  • \(n\leq 50\)。

思考时间最长的一道题。猛然发现直接优化建图就好……

相当于两个点在自动机上走,首先将所有终止节点都连向一个虚点 \(z\)。

\(\text{same}(i)\) 表示到 \(i\) 都一模一样。

\(\text{diff}(i,j)\) 表示一个到了节点 \(i\) 一个到了节点 \(j\) 且一定不完全一样。

发现这样的转移是乏力的,因为缺少了从 \(\text{same}\) 开始一直总小写字母的转移。

这种转移时不能归入 \(\text{diff}\) 的,因为 \(\text{diff}\) 两个节点都能任意走小写字母,如果从 \(\text{same}\) 出发很可能错误的构造了一模一样的串。

解决方法是再设 \(\text{walk}(i,j)\) 表示一个到了节点 \(i\) 一个到了节点 \(j\),且节点 \(i\) 比节点 \(j\) 多走了若干小写字母的情况。

转移按照定义就好,时刻保证大写字母的子序列完全一致。之后最短路的起点就是 \(\text{same}(1)\),终点就是 \(\text{diff}(z,z)\)。

点数是 \(O(n^2)\) 的,边数再乘字符集大小,还是很宽松的。

code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;

#define eb emplace_back
typedef pair<int, int> pii;
#define mp make_pair
#define fi first
#define se second

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

const int N = 55;
const int M = N * N * 3;
const int inf = 1e9;
int n, p, m, tr[N][60];
int tot, same[N], walk[N][N], diff[N][N];
int dis[M]; bool vis[M];
vector<pii> g[M];

void add(int u, int v, int w) {g[u].eb(mp(v, w));}

int main() {
	n = read() + 1, p = read(), m = read();
	rep(i, 1, p) {int x = read(); tr[x][0] = n;}
	rep(i, 1, m) {
		int x = read();
		char c[3]; scanf("%s", c);
		int y = read();
		int cur = (c[0] <= 'Z') ? (c[0] - 'A' + 1) : (c[0] - 'a' + 27);
		tr[x][cur] = y;
	}
	
	rep(i, 1, n) {
		same[i] = ++ tot;
		rep(j, 1, n) walk[i][j] = ++ tot, diff[i][j] = ++ tot;
	}
	
	rep(i, 1, n) {
		rep(c, 0, 52) if(tr[i][c]) add(same[i], same[tr[i][c]], 2);
		rep(c, 27, 52) if(tr[i][c]) add(same[i], walk[tr[i][c]][i], 1);
		rep(c1, 27, 52) rep(c2, 27, 52) if(tr[i][c1] && tr[i][c2] && c1 != c2)
			add(same[i], diff[tr[i][c1]][tr[i][c2]], 2);
	}
	
	rep(i, 1, n) rep(j, 1, n) {
		rep(c, 27, 52) if(tr[i][c])
			add(diff[i][j], diff[tr[i][c]][j], 1), 
			add(walk[i][j], walk[tr[i][c]][j], 1);
		rep(c, 27, 52) if(tr[j][c])
			add(diff[i][j], diff[i][tr[j][c]], 1);
		rep(c, 0, 26) if(tr[i][c] && tr[j][c])
			add(diff[i][j], diff[tr[i][c]][tr[j][c]], 2), 
			add(walk[i][j], diff[tr[i][c]][tr[j][c]], 2);
	}
	
	rep(i, 1, tot) dis[i] = inf;
	priority_queue<pii> q;
	int s = same[1]; q.push(mp(dis[s] = 0, s));
	while(! q.empty()) {
		int u = q.top().se; q.pop();
		if(vis[u]) continue; else vis[u] = true;
		for(pii e : g[u]) {
			int v = e.fi, w = e.se;
			if(dis[v] > dis[u] + w) dis[v] = dis[u] + w, q.push(mp(- dis[v], v));
		}
	}
	
	int ans = dis[diff[n][n]];
	printf("%d\n", ans == inf ? -1 : ans - 2); 
	
	return 0;
}

M - Word Ladder

  • 构造长度为 \(5000\) 的单次表,使得每个单词长度均相等且 \(\leq 10\)。
  • 且相邻单词的汉明距离为 \(1\),且不相邻单词的汉明距离不为 \(1\)。
  • 单词只能由小写字母构成。

WA 了 7 发终于写对各种细节的构造。

实际也比较简单,从 aaaaaaaaaa 出发,每次挑相邻的两个一直执行如下过程:

 aa -> ba -> bb -> cb -> cc -> ... -> zy -> zz

这样大概的总单词数就是 \(5\times 26\times 50\) 的,但是不同列的转换,以及不同底字母的转换有些细节,可能会牺牲一些单词。

最终造出的单词表大小为 \(6461\) QwQ

code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;

#define eb emplace_back

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

vector<vector<char>> ans;

vector<char> solve(vector<char> s, int c, int p) {
	int cur = (c == 0 ? 1 : (c == 1 ? 2 : 0));
	rep(i, 1, 24 + (c == 0)) {
		s[p] = cur + 'a'; 	  if(! p || i > 1) ans.eb(s);
		s[p ^ 1] = cur + 'a'; if(! p || i > 1) ans.eb(s);
		cur ++;
		if(cur == c - 1) cur ++;
		if(cur == c) cur ++;
	}
	return s;
}

int main() {
	rep(c, 0, 24) {
		vector<char> sta(10, c + 'a'), las;
		if(c == 0) ans.eb(sta);
		rep(i, 0, 4) {
			las = solve(sta, c, i << 1);
			if(i < 4) {
				int nxt = (c == 0 ? 1 : (c == 1 ? 2 : 0));
				las[(i << 1) + 2] = nxt + 'a', ans.eb(las);
				las[(i << 1) + 3] = nxt + 'a', ans.eb(las);
				las[i << 1] = c + 'a', ans.eb(las);
				las[i << 1 | 1] = c + 'a', ans.eb(las);
			}
		}
		if(c < 25) {
			rep(j, 0, 2) las[j] = c + 1 + 'a', ans.eb(las);
			per(j, 9, 3) las[j] = c + 1 + 'a', ans.eb(las);
		}
	}
	int n = read();
	rep(i, 0, n - 1) {rep(j, 0, 9) putchar(ans[i][j]); puts("");}
	return 0;
}

标签:Summer,cnt,cur,Contest,int,rep,Welcome,num,now
来源: https://www.cnblogs.com/lpf-666/p/16676222.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有