2017 Multi-University Training Contest - Team 1

2017多校联合训练1
菜鸡只能补6题。

Add More Zero

题目:求2^m - 1 > 10 ^ k,求k的最大值
水题,直接算k / lg ⁡ 2 \lg 2 lg2 即可,比赛时还傻傻的打了个表二分。。

#include <bits/stdc++.h>

#define ll long long
#define MAXN 100005

using namespace std;

ll m;
double t;

bool check(ll n) {
	return m > n / t;
}

ll binary_search(ll l, ll r) {
	ll mid = l + r >> 1;
	ll ans = mid;
	while (l <= r) {
		mid = l + r >> 1;
		if (check(mid)) {
			ans = mid;
			l = mid + 1;
		} else {
			r = mid - 1;
		}
	}
	return ans;
}

ll ans[MAXN];

int main() {
	int cas = 1;
	t = log10(2);
	for (ll i = 0; i <= 100000; i++) {
		m = i; 
		ans[i] = binary_search(0, 100000);
	}
	while (~scanf("%lld", &m)) {
		printf("Case #%d: %lld\n", cas++, ans[m]);
	}
}

##Balala Power!##

个人感觉挺恶心的一道模拟贪心题。

题目意思是。给n条只由小写字母组成的字符串,你可以将a-z逐一表示成0-25,现在问将每一串表示成26进制数后,相加求10进制的最大值。

思路:将所有串按照右部为尾端来构成一个序列,每一个串为一行,对每一列从右边开始加就有26^0xxx + 26^1yyy…
这里只需要将整个字符串倒置即可。。
按照每一列字符出现的次数排序,如果相同则比较下一列。
然后贪心选取最先出现的小写字符,将他们赋值。
最后要注意到不能有前导0,则读入的时候标记一下不能为前导0的字符。
所有数赋值后,若最后一个数为0且为某一串开头,则逐步往前移动至可以为0开头的字符。

这里要注意。统计每一列的字符的个数的时候,要记得进位!!!!
跟队友wa死了一下午这里。。

#include <bits/stdc++.h>

#define MAXN 100025
#define ll long long
#define MOD 1000000007

using namespace std;

struct node {
	ll idx, val, let; 
	node(){}
	node(ll _idx, ll _val, ll _let) {
		idx = _idx;
		val = _val;
		let = _let;
	}
} lets[30];

ll tot[MAXN][30];
ll rec[MAXN];
ll val[30];
bool head[30];
ll lmax;

int cmp(node a, node b) {
	if (a.val != b.val) {
		return a.val > b.val;
	}
	for (ll i = a.idx - 1; i >= 0; i--) {
		if (tot[i][a.let] != tot[i][b.let]) {
			return tot[i][a.let] > tot[i][b.let];
		}
	}
	return 0;
}

int cmp2(node a, node b) {
	return a.val < b.val;
}

void init() {
	memset(tot, 0, sizeof(tot));
	memset(head, false, sizeof(head));
	memset(val, -1, sizeof(val));
	rec[0] = 1;
	for (ll i = 1; i < MAXN; i++) {
		rec[i] = (rec[i - 1] * 26) % MOD;
	}
}

ll calc() {
	ll ans = 0;
	for (ll i = 0; i < lmax; i++) {
		for (ll j = 0; j < 26; j++) {
			if (val[j] != -1 && tot[i][j]) {
				ans = (ans + (((val[j] * rec[i]) % MOD) * tot[i][j]) % MOD) % MOD;
			}
		}
	}
	return ans;
}

int main() {
	ll n, cas = 1;
	char ch[MAXN];
	while (~scanf("%lld", &n)) {
		init();
		lmax = 0;
		ll maxt = 25;
		for (ll i = 0; i < n; i++) {
			scanf("%s", ch);
			ll len = strlen(ch);
			if (len > 1) {
				head[ch[0] - 'a'] = true;
			}
			lmax = max(lmax, len);
			for (ll j = 0; j < len; j++) {
				tot[j][ch[len - 1 - j] - 'a']++; 
				if (tot[j][ch[len - 1 - j] - 'a'] == 26) {
					tot[j][ch[len - 1 - j] - 'a'] = 0;
					tot[j + 1][ch[len - 1 - j] - 'a']++;
					if (j == len - 1) {
						lmax = max(lmax, len + 1);
					}
				}
			}
		}
		for (ll i = lmax - 1; i >= 0; i--) {
			for (ll j = 0; j < 26; j++) {
				lets[j] = node(i, tot[i][j], j);
			}
			sort(lets, lets + 26, cmp);
			for (ll j = 0; j < 26; j++) {
				if (val[lets[j].let] == -1 && tot[i][lets[j].let]) {
					val[lets[j].let] = maxt--;
				}
			}
		}
		if (maxt == -1) {
			for (ll i = 0; i < 26; i++) {
				lets[i] = node(0, val[i], i);
			}
			sort(lets, lets + 26, cmp2);
			if (head[lets[0].let]) {
				ll tmp = 1;
				while (head[lets[tmp].let] && tmp < 26) {
					tmp++;
				}
				if (tmp != 26) {
					for (ll i = 0; i < tmp; i++) {
						val[lets[i].let] = val[lets[i + 1].let];
					}
					val[lets[tmp].let] = 0;
				}
			}
		}
		printf("Case #%lld: %lld\n", cas++, calc());
	}
}

##Colorful Tree##

题意:给一棵树,每两点之间的距离为:他们路径上所有点不同颜色的个数。现在求对于整个图的n * (n - 1) / 2条路径,他们总距离为多少。

思路:
该题可以将模型进行转换,求总图每两点之间的距离之和,可以看成,对于每一个颜色,经过它的不同路径有多少条,求所有颜色分别经过他们的不同路径之和。
还可以转换成,所有路径*总颜色个数,除去不经过某种颜色的路径之和,即为答案。
orz摩拜tls大佬想出这种题。

类似树形dp,对于每一个颜色的节点,维护该点颜色时,他的子节点除去了相同颜色子树的个数。
最后要注意到,最顶部的那个节点的其他颜色是没有dp到的,最后再组合维护一下即可。

#include <bits/stdc++.h>

#define MAXN 200005
#define ll long long

using namespace std;

ll col[MAXN];
ll size[MAXN]; //记录子节点数
ll sum[MAXN]; //记录截断值,即为该点到所有不同颜色的节点,到相同颜色的节点时结束。 
vector<ll> g[MAXN];
bool vis[MAXN];
ll mark[MAXN];
ll ans;

void addEdge(ll u, ll v) {
	g[v].push_back(u);
}

void init() {
	ans = 0;
	memset(mark, 0, sizeof(mark));
	memset(vis, false, sizeof(vis));
	memset(size, 0, sizeof(size));
	memset(sum, 0, sizeof(sum));
	for (ll i = 0; i < MAXN; i++) {
		g[i].clear();
	}
}

void dfs(ll u) {
	size[u] = 1;
	vis[u] = true;
	ll len = g[u].size();
	ll all = 0;
	for (ll i = 0; i < len; i++) {
		ll to = g[u][i];
		ll s = sum[col[u]];
		if (vis[to]) {
			continue;
		}
		dfs(to);
		size[u] += size[to];
		ll step = sum[col[u]] - s;  //截断后的数量 
		all += step;
		ans += (size[to] - step) * (size[to] - step - 1) / 2;
	}
	sum[col[u]] += size[u] - all;
}

int main() {
	ll n, u, v, cas = 1;
	while (~scanf("%lld", &n)) {
		init();
		ll tt = 0;
		for (ll i = 1; i <= n; i++) {
			scanf("%lld", &col[i]);
			tt += mark[col[i]] ^ 1;
			mark[col[i]] = 1;
		}
		for (ll i = 0; i < n - 1; i++) {
			scanf("%lld %lld", &u, &v);
			addEdge(u, v);
			addEdge(v, u);
		}
		dfs(1);
		ll lstans = n * (n - 1) * tt / 2;
		for (ll i = 1; i <= n; i++) {
			if (tt != col[1] && mark[i]) {
				ans += (n - sum[i]) * (n - sum[i] - 1) / 2;
			}
		} 
		printf("Case #%lld: %lld\n", cas++, lstans - ans);
	}
}

##Function##

题意:对于f(i)=bf(ai)一个函数,有a和b序列,现在问你可以构成f(x)的情况有多少种
不知从何找到的规律,将a序列和b序列根据下标和值打环,若a的某个环上的点可以整除b的某个环上的点。则肯定满足。
打环统计一下数就好了。。暴力最大情况O(n * m) 卡过了。。

#include <bits/stdc++.h>

#define MAXN 100005
#define ll long long
#define MOD 1000000007

using namespace std;

bool mark[MAXN];
ll a[MAXN], b[MAXN];
ll alen, blen, t;
vector<ll> res[2];

void init() {
	alen = blen = 0;
	for (ll i = 0; i < 2; i++) {
		res[i].clear();
	}
}

void dfs(ll u, ll *num) {
	if (mark[u]) {
		return ;
	}
	mark[u] = true;
	t++;
	dfs(num[u], num);
}

int main() {
	ll n, m, cas = 1;
	while (~scanf("%lld %lld", &n, &m)) {
		init();
		for (ll i = 0; i < n; i++) {
			scanf("%lld", &a[i]);
		}
		for (ll i = 0; i < m; i++) {
			scanf("%lld", &b[i]);
		}
		memset(mark, false, sizeof(mark));
		for (ll i = 0; i < n; i++) {
			if (!mark[i]) {
				t = 0;
				dfs(i, a);
				res[0].push_back(t);
			}
		}
		memset(mark, false, sizeof(mark));
		for (ll i = 0; i < m; i++) {
			if (!mark[i]) {
				t = 0;
				dfs(i, b);
				res[1].push_back(t);
			}
		}
		alen = res[0].size(), blen = res[1].size();
		ll ans = 1;
		for (ll i = 0; i < alen; i++) {
			ll tt = 0;
			for (ll j = 0; j < blen; j++) {
				if (res[0][i] % res[1][j] == 0) {
					tt += res[1][j];
					tt %= MOD;
				}
			}
			ans = (ans * tt) % MOD;
		}
		printf("Case #%lld: %lld\n", cas++, ans);
	}
}

##Hints of sd0061##

题目:给一个递推函数构成的ai序列,m次询问,问第bi小的数的值是多少。(从0开始)
思路:直接排序是不行的,将询问的方式排个序,用nth_element每次缩小区间找第n大的数即可。

#include <bits/stdc++.h>

#define ull unsigned int
#define MAXN 10000005

using namespace std;

ull x, y, z;
ull a[MAXN];
ull ans[105];
int b[105], pos[105];

ull rng61() {
	ull t;
	x ^= x << 16;
	x ^= x >> 5;
	x ^= x << 1;
	t = x;
	x = y;
	y = z;
	z = t ^ x ^ y;
	return z;
}

int cmp(int aa, int bb) {
	return b[aa] < b[bb];
}

int main() {
	ull A, B, C;
	int n, m;
	int cas = 1;
	while (~scanf("%d %d %u %u %u", &n, &m, &A, &B, &C)) {
		x = A, y = B, z = C;
		for (int i = 0; i < m; i++) {
			scanf("%d", &b[i]);
			pos[i] = i;
		}
		sort(pos, pos + m, cmp);
		for (int i = 0; i < n; i++) {
			a[i] = rng61();
		}
		pos[m] = m;
		b[pos[m]] = n;
		for (int i = m - 1; i >= 0; i--) {
			nth_element(a, a + b[pos[i]], a + b[pos[i + 1]]);
			ans[pos[i]] = a[b[pos[i]]];
		}
		printf("Case #%d:", cas++);
		for (int i = 0; i < m; i++) {
			printf(" %u", ans[i]);
		}
		puts("");
	}
} 

##KazaQ’s Socks##

题目:有一个人穿袜子,每次选最小号码的袜子,每n-1天会把袜子拿去洗一次,问第k天穿了哪个号吗的袜子。
水题,找到循环节,前n个数为1-n,后面每2 * (n - 1)一个循环节。

#include <bits/stdc++.h>

#define ll long long

using namespace std;

int main() {
	ll n, k, cas = 1;
	while (~scanf("%lld %lld", &n, &k)) {
		printf("Case #%lld: ", cas++);
		if (k <= n) {
			printf("%lld\n", k);
		} else {
			ll p = (k - n) % (2 * (n - 1));
			if (p == 0) {
				p = 2 * (n - 1);
			} 
			if (p <= n - 1) {
				printf("%lld\n", p);
			} else {
				p -= n - 1;
				if (p == n - 1) {
					printf("%lld\n", n);
				} else {
					printf("%lld\n", p);
				}
			}
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值