2015上海大都会邀请赛 题解

比赛地址

这是一场现场赛


A. Article

题意:

有只猴子在打字,每个时间点有三个顺序发生的事件:1.未保存的内容有p概率丢失;2.瞬间额外按x个键保存全文;3.按1个键输入文章的一个字符。

给定文章的字符个数n,丢失概率p,额外代价x,问猴子在最优策略下最少需要按的键数期望。

n <= 10^5, 0.1 <= p <= 0.9, x <= 100。

题解:

可以先算出成功保存k个字符的期望按键代价f[k],注意到每一次保存的按键代价是一个凸函数,所以最优策略一定会是尽量均匀的分配每次保存的字符数,考虑分配多少段即可,似乎这个也满足凸性。时间复杂度O(n)。

代码:

#include <cstdio>
const int maxn = 100001;
int t, n, x;
double p, f[maxn], ans;
void upd(double &x, const double &y)
{
	if(x >= y)
		x = y;
}
int main()
{
	scanf("%d", &t);
	for(int Case = 1; Case <= t; ++Case)
	{
		scanf("%d%lf%d", &n, &p, &x);
		for(int i = 1; i <= n; ++i)
			f[i] = (f[i - 1] + 1) / (1 - p);
		ans = 2e7;
		for(int i = 1; i <= n; ++i)
		{
			int cnt = n % i;
			upd(ans, f[n / i + 1] * cnt + f[n / i] * (i - cnt) + x * i); 
		}
		printf("Case #%d: %.6f\n", Case, ans);
	}
	return 0;
}

B. Base64

题意:

待补

题解:

队友@Awcrr 写的,据说是大模拟,待补。

代码:

待补。


C. Calculator

题意:

给定n个运算操作,仅限于加一个数、乘一个数、求一个数次幂,有m个操作,要么给定一个x,求一次做完这n个运算后的答案模29393,要么修改第p个运算。

n <= 10^5, m <= 10^5, 0 <= 运算的数字 < 29393。

题解:

现场的时候并没有细看这个题,队友发现映射关系之后说分块,我就去码了T_T,结果是此题自带的常数导致分块一定会tle。

首先可以发现29393=7*13*17*19,是一个square-free number,答案可以分别在四个剩余系内算出后combine出来,每一个操作都可以看成是剩余系内的映射关系,线段树可以维护这些映射关系的combine,修改是单点的所以也很简单,最后CRT一下即可,也可以预处理出0~29392每个数对应四个剩余系的数值,询问时直接查询。时间复杂度O(nlogn)。

代码:

#include <cstdio>
const int maxn = 65536, mod[4] = {7, 13, 17, 19};
int t, n, m, seg[maxn << 1][4][19], mod_pow[4][19][19];
int exgcd(int a, int b, int &x, int &y)
{
	if(!b)
	{
		x = 1;
		y = 0;
		return a;
	}
	int r = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return r;
}
//x = a1 + m1 * t1
//x = a2 + m2 * t2 
//m1 * t1 - m2 * t2 = a2 - a1
//x = a1 + m1 * t1 = a1 + m1 * t10 + m1 * m2 / gcd(m1, m2)
int CRT(int a[], const int m[], int len)
{
	int res = 0, mod = 1;
	for(int i = 0; i < len; ++i)
	{
		int x, y, r;
		r = exgcd(mod, m[i], x, y);
		x = (a[i] - res) / r * x % (m[i] / r);
		if(x < 0)
			x += (m[i] / r);
		res += mod * x;
		mod *= m[i] / r;
	}
	return res;
}
int calc(int x, char op, int y, int id)
{
	if(op == '+')
		return (x + y) % mod[id];
	if(op == '*')
		return x * y % mod[id];
	y %= mod[id] - 1;
	return mod_pow[id][x][y];
}
void build(int o, int l, int r)
{
	if(l == r)
	{
		int x;
		char op, str[20];
		scanf("%s", str);
		sscanf(str, "%c%d", &op, &x);
		for(int i = 0; i < 4; ++i)
			for(int j = 0; j < mod[i]; ++j)
				seg[o][i][j] = calc(j, op, x, i);
		return;
	}
	int m = l + r >> 1;
	build(o + o, l, m);
	build(o + o + 1, m + 1, r);
	for(int i = 0; i < 4; ++i)
		for(int j = 0; j < mod[i]; ++j)
			seg[o][i][j] = seg[o + o + 1][i][seg[o + o][i][j]];
}
void rebuild(int o, int l, int r, int x)
{
	if(l == r)
	{
		int x;
		char op, str[20];
		scanf("%s", str);
		sscanf(str, "%c%d", &op, &x);
		for(int i = 0; i < 4; ++i)
			for(int j = 0; j < mod[i]; ++j)
				seg[o][i][j] = calc(j, op, x, i);
		return;
	}
	int m = l + r >> 1;
	if(x <= m)
		rebuild(o + o, l, m, x);
	else
		rebuild(o + o + 1, m + 1, r, x);
	for(int i = 0; i < 4; ++i)
		for(int j = 0; j < mod[i]; ++j)
			seg[o][i][j] = seg[o + o + 1][i][seg[o + o][i][j]];
}
int main()
{
	for(int i = 0; i < 4; ++i)
		for(int j = 1; j < mod[i]; ++j)
		{
			mod_pow[i][j][0] = 1;
			for(int k = 1; k < mod[i] - 1; ++k)
				mod_pow[i][j][k] = mod_pow[i][j][k - 1] * j % mod[i];
		}
	scanf("%d", &t);
	for(int Case = 1; Case <= t; ++Case)
	{
		scanf("%d%d", &n, &m);
		printf("Case #%d:\n", Case);
		build(1, 1, n);
		for(int i = 1, x, y; i <= m; ++i)
		{
			scanf("%d%d", &x, &y);
			if(x == 1)
			{
				int res[4] = {seg[1][0][y % mod[0]], seg[1][1][y % mod[1]], seg[1][2][y % mod[2]], seg[1][3][y % mod[3]]};
				printf("%d\n", CRT(res, mod, 4));
			}
			else
				rebuild(1, 1, n, y);
		}
	}
	return 0;
}

D. Doom

题意:

给定一个长度为n的序列,有一个s初始化为0,有m个操作,每次将[l, r]的数字加到s里,然后每个数字平方,答案模2^63-2^31。

n, m <= 10^5, 0 <= 序列元素 < 2^63 - 2^31。

题解:

这题是我看的T_T,由于buaa校赛的时候犯sb了所以看的额外紧张,校赛时先写的并查集+bit,然后才发现是线段树维护一个长度为6的序列,在上海就是sb地以为要维护一个长度为61的序列了,wa来wa去。

首先可以看出phi(p)=2^61,再根据a^(x+phi(p))≡a^(x mod phi(p) + phi(p)) (mod p)可以知道,每个数平方61次以后数值就不会发生变化了,于是可以暴力地单点更新,区间查询,时间复杂度O(nlogn)。

这里涉及到的爆long long的加减乘法,有兴趣的话可以看一下我的程序里是怎么O(1)实现的。

代码:

#include <cstdio>
#include <cstring>
typedef long long LL;
const int maxn = 100001;
const LL mod = 9223372034707292160;
int t, n, m, cnt[maxn], fa[maxn];
LL a[maxn], bit[maxn], ans;
LL inc(LL x, LL y)
{
	LL ret = x + (y - mod);
	if(ret < 0)
		ret += mod; 
	return ret;
}
LL dec(LL x, LL y)
{
	LL ret = x - y;
	if(ret < 0)
		ret += mod;
	return ret;
}
LL mul(LL x, LL y)
{
	LL ret = x * y - (long long)((long double)x * y / mod + 0.001) * mod;
	if(ret < 0)
		ret += mod;
	return ret;
	/*LL ret = 0;
	while(y)
	{
		if(y & 1)
			ret = inc(ret, x);
		x = inc(x, x);
		y >>= 1;
	}
	return ret;*/
}
void add(int x, LL v)
{
	for( ; x <= n; x += x & -x)
		bit[x] = inc(bit[x], v);
}
LL sum(int x)
{
	LL ret = 0;
	for( ; x > 0; x -= x & -x)
		ret = inc(ret, bit[x]);
	return ret;
}
int find(int x)
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int main()
{
	scanf("%d", &t);
	for(int Case = 1; Case <= t; ++Case)
	{
		ans = 0;
		memset(cnt, 0, sizeof cnt);
		memset(bit, 0, sizeof bit);
		scanf("%d%d", &n, &m);
		for(int i = 1; i <= n; ++i)
		{
			scanf("%lld", a + i);
			fa[i] = i;
			add(i, a[i]);
		}
		printf("Case #%d:\n", Case);
		for(int i = 1, l, r; i <= m; ++i)
		{
			scanf("%d%d", &l, &r);
			ans = inc(ans, dec(sum(r), sum(l - 1)));
			for(int j = find(r); j >= l; j = find(j - 1))
			{
				LL tmp = mul(a[j], a[j]);
				add(j, dec(tmp, a[j]));
				a[j] = tmp;
				++cnt[j];
				if(cnt[j] >= 31)
					fa[j] = find(j - 1);
			}
			printf("%lld\n", ans);
		}
	}
	return 0;
}

E. Exam

题意:

没看,队长@constroy 看的,我也想看水题嘛T_T。。

题解:

应该是贪心吧,待补。

代码:

待补。


F. Friends

题意:

给定一些集合的包含关系,每个集合最多有n种固定的元素,问有多少种满足包含关系的局面。

n <= 3000。

题解:

这个题也是队长看的,然而并没有看出什么,我强行上去算了一发,发现每种元素是互相独立的,再验证了一下n=1的结果,答案即32^n,然后就高精度水过啦。

代码:

待补。


G. Game

题意:

给定一颗树,树上每个点有点权,求k条从树根到叶子节点的路径,使得在至少一条路径上的点的点权和最大,求最大和。

k <= n <= 10^5。

题解:

我看的,然后说在bzoj见过然后给了一个错误的思想T_T,不过还好神队友@Awcrr 秒杀此题,赛后发现确实是bzoj3252攻略(题面都差不多),好像这题又被藏起来了。

可以发现每个点最多被删一次,求出dfs序后线段树维护当前的最大和路径即可,然后递归进去删贡献即可。时间复杂度O(nlogn)。

代码:

待补。


H. Homework

题意:

给定二维平面上的n个定点,存在一些点使得任意一条通过该点的直线在不通过那n个定点的情况下能把定点分成两个部分,每个部分的定点数不少于[n/3],问这些满足条件的点组成的集合的面积。

n <= 1000。

题解:

还是我看的= =,读了三遍没看懂题,弃疗,后来看懂题意之后很快想出了算法,大概复杂度是O(n^2logn),待补。

代码:

待补。


I. Inverse

题意:

给定一种序列生成规则b_i = \sum{j=0}^{n-1}{f((i or j) xor j)*a_j},其中f(x)为:如果x的二进制位上1的个数为偶数,则f(x)=1,否则f(x)=0。

现在给出长度n和序列b,反求原序列a。保证n是2的倍数,n=2^k,保证序列a是整数。

0 <= k <= 20, b_i <= 10^9。

题解:

没错又是我看的,开场我就在做这个,然而手工打表并没有带来什么好的结果,最后一直是玩泥巴阶段,最后几小时就是队友打表找规律,我去搞CD,如果换一下分工说不定会有奇效。。。

可以发现f((i or j) xor j)等价于f(~i and j),然后将b和a写成向量,f组成一个系数矩阵A,A是具有分形性质的,按照中位线分块是类似于[B, ~B; B, B]的矩阵,这提示我们A的逆也是一个有分形规律的矩阵,事实上现在就可以尝试构造B^-1来快速求逆了。

我的方法是将比较小的A高斯消元求逆之后打表,发现除了右上角的一个元素之外A^-1_ij = (-1)^cnt(i and ~j) * 2 / n,右上角的那格只是多减了1。

于是可以分治算a了,每次只需要算左上的内积,右上的内积(因为和b的积的位置不一样),然后就能推出左下的内积,右下的内积,时间复杂度O(nlogn)。

补充一个详细一点的解答

代码:

#include <cstdio>
const int maxn = 1 << 21;
int t, n;
long long a[maxn], b[maxn], c[maxn];
void solve(int x1, int x2, int y1, int y2, long long* c)
{
	if(x1 == x2)
	{
		c[x1] = a[y1];
		return;
	}
	int d = x2 - x1 + 1 >> 1;
	int xm = x1 + d, ym = y1 + d;
	solve(x1, xm - 1, y1, ym - 1, c);
	solve(x1, xm - 1, ym, y2, c + d);
	for(int i = 0; i < d; ++i)
	{
		b[x1 + i] = c[i] + c[d + i];
		b[xm + i] = -c[i] + c[d + i];
	}
	for(int i = 0; i < d << 1; ++i)
		c[i] = b[x1 + i];
}
int main()
{
	scanf("%d", &t);
	for(int Case = 1; Case <= t; ++Case)
	{
		scanf("%d", &n);
		n = 1 << n;
		for(int i = 0; i < n; ++i)
			scanf("%lld", a + i);
		printf("Case #%d:", Case);
		if(n == 1)
		{
			printf(" %d\n", a[0]);
			continue;
		}
		solve(0, n - 1, 0, n - 1, c);
		c[0] -= (n >> 1) * a[n - 1];
		for(int i = 0; i < n; ++i)
			printf(" %lld", c[i] / (n >> 1));
		putchar('\n');
	}
	return 0;
}

J. Joyful

题意:
给定一个m*n的网格,每次随机选择两个点,将这两个点所构成的矩形染色,做k次染色,问期望染了多少个格子。

m, n <= 500, k <= 20。

题解:

我看的,队长算的,我写的。

期望可以分摊到每个小格计算,考虑(i, j)被至少染色一次的概率,可以算反面,算反面时又可以分成两个维度分别算,直接求出概率即可。

代码:

待补。


小记

补完再写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值