比赛地址
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)被至少染色一次的概率,可以算反面,算反面时又可以分成两个维度分别算,直接求出概率即可。
代码:
待补。
小记
补完再写。