SDOI 2017 Round1 解题报告

Day 1

T1 数字表格


题目大意

· 求\(\prod\limits_{i=1}^n\prod\limits_{j=1}^mFibonacci(\gcd(i,j))\)\(T\leq1000\)\(n,m\leq10^6\)

思路

· 一言不合化式子(不失一般性地假设\(n<m\))$$\begin{aligned}
ans&=\prod_{i=1}n\prod_{j=1}mf((i,j))\
&=\prod_{d=1}nf(d)n\sum\limits_{j=1}m[(i,j)=d]}\
&=\prod_{d=1}nf(d)^{\frac nd}\mu(k)\left\lfloor\frac n{dk}\right\rfloor\left\lfloor\frac m{dk}\right\rfloor}
\end{aligned}$$
· 到这里直接\(O\left(Tn^{\frac 34}\right)\)暴力计算答案,常数压的好就可以通过此题。
· 更神奇的:$$\prod_{d=1}nf(d)^{\frac nd}\mu(k)\left\lfloor\frac n{dk}\right\rfloor\left\lfloor\frac m{dk}\right\rfloor}=\prod_{T=1}n\left(\prod_{d|T}f(d)\right)^{\left\lfloor\frac nT\right\rfloor\left\lfloor\frac mT\right\rfloor}$$
· 我们设\(g(n)=\prod\limits_{d|n}f(d)^{\mu\left(\frac nd\right)}\)
· 很类似莫比乌斯反演,\(f(n)=\prod\limits_{d|n}g(n)\),这个结论没什么意义,不过可以从另一个角度来做这道题。
· 如何预处理\(g(i)\)呢?\(g(i)\)并不是积性函数,但我们可以枚举所有斐波那契数,然后再枚举它们的倍数来计算对\(g(i)\)的贡献,时间复杂度\(O(n\ln n)\)
· 预处理完成后\(O\left(\left(\sqrt n+\sqrt m\right)\log n\right)\)回答单次询问。
· 时间复杂度\(O\left(n\ln n+T\left(\sqrt n+\sqrt m\right)\log n\right)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

const int N = 1000003;
const int p = 1000000007;

bool notp[N];
int mu[N], prime[N], num = 0, g[N], nig[N];

int ipow(int a, int b) {
	int r = 1, w = a;
	while (b) {
		if (b & 1) r = 1ll * r * w % p;
		w = 1ll * w * w % p;
		b >>= 1;
	}
	return r;
}

void Euler_shai() {
	mu[1] = 1;
	for (int i = 2; i <= 1000000; ++i) {
		if (!notp[i]) {
			prime[++num] = i;
			mu[i] = -1;
		}
		
		for (int j = 1, cj; j <= num && (cj = prime[j] * i) <= 1000000; ++j) {
			notp[cj] = true;
			if (i % prime[j] == 0) {
				mu[cj] = 0;
				break;
			}
			mu[cj] = -mu[i];
		}
	}
	
	for (int i = 0; i <= 1000000; ++i) g[i] = nig[i] = 1;
}

int Fib[N], niFib[N];

int solve(int n, int m) {
	int ret = 1;
	for (int ri, T = 1; T <= n; ++T) {
		ri = min(n / (n / T), m / (m / T));
		ret = 1ll * ret * ipow(1ll * g[ri] * nig[T - 1] % p, 1ll * (n / T) * (m / T) % (p - 1)) % p;
		T = ri;
	}
	return ret;
}

int main() {
	freopen("product.in", "r", stdin);
	freopen("product.out", "w", stdout);
	
	Euler_shai();
	
	Fib[0] = 0; Fib[1] = 1; niFib[1] = niFib[0] = 1;
	for (int i = 2; i <= 1000000; ++i) {
		Fib[i] = (Fib[i - 1] + Fib[i - 2]) % p;
		niFib[i] = ipow(Fib[i], p - 2);
	}
	
	for (int i = 1; i <= 1000000; ++i)
		for (int j = i; j <= 1000000; j += i)
			if (mu[j / i] == 1) {
				g[j] = 1ll * g[j] * Fib[i] % p;
				nig[j] = 1ll * nig[j] * niFib[i] % p;
			} else if (mu[j / i] == -1) {
				g[j] = 1ll * g[j] * niFib[i] % p;
				nig[j] = 1ll * nig[j] * Fib[i] % p;
			}
	
	for (int i = 2; i <= 1000000; ++i) {
		g[i] = 1ll * g[i] * g[i - 1] % p;
		nig[i] = 1ll * nig[i] * nig[i - 1] % p;
	}
	
	int T, n, m; scanf("%d", &T);
	while (T--) {
		scanf("%d%d", &n, &m);
		if (n > m) n ^= m ^= n ^= m;
		printf("%d\n", solve(n, m));
	}
	
	return 0;
}

T2 树点涂色


题目大意

· 根为1的有根树上初始每个节点都有不同颜色,一条路径的权值为这条路径上点的不同颜色数。
· 修改:把x到根这条链上所有点都涂上一种之前从没出现过的颜色。
· 查询:链的权值和子树内一点到根的路径的权值的最大值。
· \(n\leq10^5,m\leq10^5\)

思路

· 把具有相同颜色的链看成重链,那么重链上相邻的点只可能是父子关系,不可能是兄弟关系。
· 线段树维护dfs序,每个节点维护这个区间内所有点到根路径的权值的最大值,链的权值可以两个端点和lca差分一下求出,子树内一点到根的权值的最大值直接在线段树上查询子树的dfs序区间内的最大值。
· 修改操作类似lct的access操作,每次修改x点,相当于把x到根的这条路径变成重链,同时要相应的断掉以前的一些重链。在重链和轻链变换的时候顺便在线段树上修改即可。
· 时间复杂度\(O(m\log^2n)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 100003;

int n, deep[N], pos[N];

void check_max(int &a, int b) {if (b > a) a = b;}

namespace SegmentTree {
	int ma[N << 2], lazy[N << 2];
	void BuildTree(int rt, int l, int r) {
		if (l == r) {ma[rt] = deep[pos[l]]; return;}
		int mid = (l + r) >> 1;
		BuildTree(rt << 1, l, mid);
		BuildTree(rt << 1 | 1, mid + 1, r);
		ma[rt] = max(ma[rt << 1], ma[rt << 1 | 1]);
	}
	
	void pushdown(int rt) {
		if (lazy[rt]) {
			int num = lazy[rt];
			lazy[rt << 1] += num;
			ma[rt << 1] += num;
			lazy[rt << 1 | 1] += num;
			ma[rt << 1 | 1] += num;
			lazy[rt] = 0;
		}
	}
	
	void add(int rt, int l, int r, int L, int R, int num) {
		if (L <= l && r <= R) {lazy[rt] += num; ma[rt] += num; return;}
		int mid = (l + r) >> 1;
		pushdown(rt);
		if (L <= mid) add(rt << 1, l, mid, L, R, num);
		if (R > mid) add(rt << 1 | 1, mid + 1, r, L, R, num);
		ma[rt] = max(ma[rt << 1], ma[rt << 1 | 1]);
	}
	
	int query(int rt, int l, int r, int L, int R) {
		if (L <= l && r <= R) return ma[rt];
		int mid = (l + r) >> 1, s = 0;
		pushdown(rt);
		if (L <= mid) check_max(s, query(rt << 1, l, mid, L, R));
		if (R > mid) check_max(s, query(rt << 1 | 1, mid + 1, r, L, R));
		return s;
	}
}

struct Enode {int nxt, to;} E[N << 1];
int cnt = 0, point[N], st[N], top;

void ins(int u, int v) {E[++cnt] = (Enode) {point[u], v}; point[u] = cnt;}

int m, L[N], R[N], dfsID = 0, sz[N], fath[N];
bool used[N];

void dfs() {
	st[top = 1] = 1; deep[1] = 1;
	while (top) {
		int u = st[top];
		if (!used[u]) {
			L[u] = ++dfsID; pos[dfsID] = u;
			for (int i = point[u]; i; i = E[i].nxt)
				if (E[i].to != fath[u]) {
					st[++top] = E[i].to;
					fath[E[i].to] = u;
					deep[E[i].to] = deep[u] + 1;
				}
			used[u] = true;
		} else {
			for (int i = point[u]; i; i = E[i].nxt)
				if (E[i].to != fath[u])
					sz[u] += sz[E[i].to];
			R[u] = L[u] + sz[u];
			++sz[u];
			--top;
		}
	}
}

namespace LCT {
	struct node *null;
	struct node {
		node *ch[2], *fa; int id, mn;
		void setc(node *r, int c) {ch[c] = r; if (r != null) r->fa = this;}
		bool pl() {return fa->ch[1] == this;}
		bool check() {return fa == null || (fa->ch[0] != this && fa->ch[1] != this);}
		void count() {
			mn = id;
			if (ch[0] != null) if (L[ch[0]->mn] < L[mn]) mn = ch[0]->mn;
			if (ch[1] != null) if (L[ch[1]->mn] < L[mn]) mn = ch[1]->mn;
		}
	} pool[N];
	
	void init() {
		null = &pool[0]; null->ch[0] = null->ch[1] = null->fa = null;
		node *t;
		for (int i = 1; i <= n; ++i) {
			t = pool + i; t->id = t->mn = i;
			t->ch[0] = t->ch[1] = null;
			t->fa = &pool[fath[i]];
		}
	}
	
	void rotate(node *r) {
		node *f = r->fa;
		int c = r->pl();
		if (f->check()) r->fa = f->fa;
		else f->fa->setc(r, f->pl());
		f->setc(r->ch[c ^ 1], c);
		r->setc(f, c ^ 1);
		f->count();
	}
	
	void splay(node *r) {
		for (; !r->check(); rotate(r))
			if (!r->fa->check()) rotate(r->pl() == r->fa->pl() ? r->fa : r);
		r->count();
	}
	
	void access(node *r) {
		node *y = null;
		while (r != null) {
			splay(r);
			if (r->ch[1] != null) SegmentTree::add(1, 1, n, L[r->ch[1]->mn], R[r->ch[1]->mn], 1);
			r->ch[1] = y; r->count();
			if (y != null) SegmentTree::add(1, 1, n, L[y->mn], R[y->mn], -1);
			y = r; r = r->fa;
		}
	}
}

int F[N][18];

int work(int x, int y) {
	if (deep[x] < deep[y]) x ^= y ^= x ^= y;
	int xx = x, yy = y;
	for (int i = 17; i >= 0; --i)
		if (deep[F[x][i]] >= deep[y])
			x = F[x][i];
	
	if (x == y)	return SegmentTree::query(1, 1, n, L[xx], L[xx]) - SegmentTree::query(1, 1, n, L[x], L[x]) + 1;
	
	for (int i = 17; i >= 0; --i)
		if (F[x][i] != F[y][i])
			x = F[x][i], y = F[y][i];
	
	int numx, numy, numz;
	int ret = SegmentTree::query(1, 1, n, L[xx], L[xx]) - (numx = SegmentTree::query(1, 1, n, L[x], L[x])) + 1
	+ SegmentTree::query(1, 1, n, L[yy], L[yy]) - (numy = SegmentTree::query(1, 1, n, L[y], L[y])) + 1;
	
	numz = SegmentTree::query(1, 1, n, L[fath[x]], L[fath[x]]);
	if (numz != numx && numz != numy) ++ret;
	return ret;
}

int main() {
	freopen("paint.in", "r", stdin);
	freopen("paint.out", "w", stdout);
	scanf("%d%d", &n, &m);
	int u, v;
	for (int i = 1; i < n; ++i) {
		scanf("%d%d", &u, &v);
		ins(u, v); ins(v, u);
	}
	
	dfs();
	SegmentTree::BuildTree(1, 1, n);
	LCT::init();
	
	for (int i = 1; i <= n; ++i) F[i][0] = fath[i];
	for (int j = 1; j <= 17; ++j)
		for (int i = 1; i <= n; ++i)
			F[i][j] = F[F[i][j - 1]][j - 1];
	
	int x, y, op;
	while (m--) {
		scanf("%d%d", &op, &x);
		if (op == 2) scanf("%d", &y);
		switch (op) {
			case 1:
				LCT::access(&LCT::pool[x]);
			break;
			case 2:
				printf("%d\n", work(x, y));
			break;
			case 3:
				printf("%d\n", SegmentTree::query(1, 1, n, L[x], R[x]));
			break;
		}
		
//		for (int i = 1; i <= n; ++i) printf("%d ", SegmentTree::query(1, 1, n, L[i], L[i])); puts("");
	}
	
	return 0;
}

T3 序列计数


题目大意

· 长度为\(n\)的序列,满足序列中的数都是不大于\(m\)的正整数,\(n\)个数的和是\(p\)的倍数,而且这\(n\)个数中至少有一个是质数。
· 求满足条件的序列的个数。
· \(1\leq n\leq 10^9,1\leq m \leq 2\times 10^7,1\leq p \leq 100\)

思路

· 容斥一下,在满足前两个条件的情况下,统计\(n\)个数无质数限制的序列个数再减去\(n\)个数都不是质数的序列个数。
· 设\(f(i,j)\)表示长度为\(i\),和模\(p\)\(j\)的序列个数。
· 提前预处理模\(p\)\(j\)有多少个数,即\(f(1,j)\)。欧拉筛求出\(m\)以内的质数。
· \(f(i,j)=\sum\limits_{k,l}[(k+l)\mod p=j]f(i-1,k)\times f(1,l)\)
· dp可以用倍增优化,时间复杂度\(O\left(m+p^2\log n\right)\)
· 当然矩乘也是可以的。

#include<cstdio>
#include<bitset>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

const int N = 20000003;
const int mo = 20170408;

int f[103], g[103], F[103], n, m, p;
int nump = 0, prime[1270608];
bitset <N> notp;

void Euler_shai() {
	notp[1] = 1;
	for (int i = 2; i <= m; ++i) {
		if (!notp[i]) prime[++nump] = i;
		for (int j = 1; j <= nump && prime[j] * i <= m; ++j) {
			notp[prime[j] * i] = 1;
			if (i % prime[j] == 0) break;
		}
	}
}

int main() {
	freopen("count.in", "r", stdin);
	freopen("count.out", "w", stdout);
	
	scanf("%d%d%d", &n, &m, &p);
	
	int num = n, tot = 0;
	while (num) {num >>= 1; ++tot;}
	for (int i = 1; i <= m; ++i) ++f[i % p];
	for (int i = 0; i < p; ++i) F[i] = (f[i] %= mo);
	for (int i = tot - 2; i >= 0; --i) {
		memset(g, 0, p << 2);
		for (int j = 0; j < p; ++j)
			for (int k = 0; k < p; ++k)
				(g[(j + k) % p] += 1ll * f[j] * f[k] % mo) %= mo;
		memcpy(f, g, p << 2);
		
		if ((n >> i) & 1) {
			memset(g, 0, p << 2);
			for (int j = 0; j < p; ++j)
				for (int k = 0; k < p; ++k)
					(g[(j + k) % p] += 1ll * f[j] * F[k] % mo) %= mo;
			memcpy(f, g, p << 2);
		}
	}
	
	int ans = f[0];
	
	Euler_shai();
	memset(f, 0, p << 2); memset(F, 0, p << 2);
	for (int i = 1; i <= m; ++i) if (notp[i]) ++f[i % p];
	for (int i = 0; i < p; ++i) F[i] = (f[i] %= mo);
	for (int i = tot - 2; i >= 0; --i) {
		memset(g, 0, p << 2);
		for (int j = 0; j < p; ++j)
			for (int k = 0; k < p; ++k)
				(g[(j + k) % p] += 1ll * f[j] * f[k] % mo) %= mo;
		memcpy(f, g, p << 2);
		
		if ((n >> i) & 1) {
			memset(g, 0, p << 2);
			for (int j = 0; j < p; ++j)
				for (int k = 0; k < p; ++k)
					(g[(j + k) % p] += 1ll * f[j] * F[k] % mo) %= mo;
			memcpy(f, g, p << 2);
		}
	}
	
	printf("%d\n", (ans - f[0] + mo) % mo);
	return 0;
}

Day 2

T1 新生舞会


题目大意

· \(n\)个男生和\(n\)个女生组成舞伴,第\(i\)个男生和第\(j\)个女生组成舞伴会产生\(a_{ij}\)的喜悦程度和\(b_{ij}\)的不协调程度。
· 有\(n\)个喜悦程度\(a_1',a_2',\dots a_n'\)和不协调程度\(b_1',b_2',\dots b_n'\),最大化\(C=\frac{a_1'+a_2'+\dots+a_n'}{b_1'+b_2'+\dots+b_n'}\)
· \(1\leq n\leq 100,1\leq a_{ij},b_{ij}\leq 10^4\),保留6位小数。

思路

· 分数规划,把分母乘到左边, 左边再移向到右边,二分\(C\)的值,通过移项后的式子的最大值来判断\(C\)偏大还是偏小。
· 移项后的式子的最大值可以用最大费用最大流来计算。
· 用数组存边比结构体快一倍!!!
· 把实数乘\(10^7\)变成整数可以减小常数。
· 时间复杂度\(O\left(n^2\sqrt n\log 10^{12}\right)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

int in() {
	int k = 0; char c = getchar();
	for (; c < '0' || c > '9'; c = getchar());
	for (; c >= '0' && c <= '9'; c = getchar())
		k = k * 10 + (c ^ 48);
	return k;
}

const int N = 103;

int b[N][N], n, S, T;
ll a[N][N], w[N * N * 3];
int nxt[N * N * 3], to[N * N * 3], c[N * N * 3], cnt, point[N << 1];

void ins(int u, int v, int C, ll W) {
	++cnt; nxt[cnt] = point[u]; to[cnt] = v; c[cnt] = C; w[cnt] = W; point[u] = cnt;
	++cnt; nxt[cnt] = point[v]; to[cnt] = u; c[cnt] = 0; w[cnt] = -W; point[v] = cnt;
}

bool inq[N << 1];
ll dis[N << 1];
int qu[100000], pre[N << 1];

bool spfa() {
	memset(dis, 0xc0, (T + 1) << 3);
	int p = 0, q = 1, v;
	ll t;
	qu[1] = S; dis[S] = 0; inq[S] = true;
	while (p != q) {
		++p; if (p == 100000) p = 0;
		int u = qu[p]; inq[u] = false;
		for (int i = point[u]; i; i = nxt[i])
			if (c[i] && (t = dis[u] + w[i]) > dis[v = to[i]]) {
				pre[v] = i;
				dis[v] = t;
				if (!inq[v]) {
					++q; if (q == 100000) q = 0;
					qu[q] = v;
					inq[v] = true;
				}
			}
	}
	return dis[T] != dis[0];
}

ll mcmf(ll C) {
	cnt = 1; memset(point, 0, (T + 1) << 2);
	for (int i = 1; i <= n; ++i) {
		ins(S, i, 1, 0), ins(i + n, T, 1, 0);
		ll *ta = a[i]; int *tb = b[i];
		for (int j = 1; j <= n; ++j) {
			++ta; ++tb;
			ins(i, j + n, 1, *ta - C * *tb);
		}
	}
	
	ll f = 0;
	while (spfa()) {
		int u = T, e;
		for (u = T, e = pre[u]; u != S; e = pre[u = to[e ^ 1]]) --c[e], ++c[e ^ 1];
		f += dis[T];
	}
	return f;
}

int main() {
	freopen("ball.in", "r", stdin);
	freopen("ball.out", "w", stdout);
	n = in();
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j)
			a[i][j] = in(), a[i][j] *= 10000000ll;
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j)
			b[i][j] = in();
	
	S = (n << 1) + 1; T = S + 1;
	ll left, right, s1 = 0, s2 = 0;
	for (int i = 1; i <= n; ++i)
		s1 += a[i][i], s2 += b[i][i];
	left = s1 / s2 + 1;
	s1 = 0; s2 = 0;
	ll mx;
	for (int i = 1; i <= n; ++i) {
		mx = 0;
		for (int j = 1; j <= n; ++j)
			if (a[i][j] > mx)
				mx = a[i][j];
		s1 += mx;
		mx = 0x7fffffff;
		for (int j = 1; j <= n; ++j)
			if (b[i][j] < mx)
				mx = b[i][j];
		s2 += mx;
	}
	right = s1 / s2;
	
	ll mid;
	while (left < right) {
		mid = (left + right + 1) >> 1;
		if (mcmf(mid) >= 0) left = mid;
		else right = mid - 1;
	}
	if (left % 10 >= 5) left += 10; left /= 10;
	printf("%I64d.%06I64d\n", left / 1000000, left % 1000000);
	return 0;
}

T2 硬币游戏


题目大意

· \(n\)个同学每个同学猜一个长度为\(m\)的HT序列(H表示正面朝上,T表示反面朝上)
· 不断地扔硬币,组成一个HT序列。当某个同学的猜的序列突然出现在扔出来的硬币序列中,则停止扔硬币并且这个同学获胜。
· 问每个同学获胜的概率是多少。
· \(1\leq n,m\leq 300\)

思路

· 这道题我想了一整天,最后问了zyf2000才知道怎么做。
· 某个同学猜的序列在硬币序列中出现的位置肯定是最后。
· 首先设\(f(i,j)\)表示长度为\(i\)的硬币序列,只有第\(i\)个位置和\(m\)个序列中某一个序列匹配,并且这个匹配的序列是第\(j\)个序列的概率。
· 那么第\(i\)个同学获胜的概率就是\(\sum\limits_{j\geq 1}f(j,i)\)
· 设\(N\)表示没有扔出来和同学猜的序列匹配的硬币序列的概率,这个\(N\)有点抽象,更具体的:\(N=\sum\limits_{j\geq 1}\left(1-\sum\limits_{k=1}^nf(j,k)\right)\)
· 然后假设A同学猜的序列是TTH,B同学猜的序列是HTT,那么可以构造一个以TTH结尾的硬币序列,并且1到序列长度-3的所有位置都不是能匹配A或B的位置,所以匹配位置只可能是序列长度-2
,序列长度-1或序列长度(即最后一个位置)
· 这样就有一个等式了:P(NTTH)=P(A)+P(BTH)+P(BH)
· 很明显吧,如果一个序列的前缀是另一个序列的后缀,那么就会产生贡献。
· 所有同学获胜的概率和为1,\(n+1\)个变元,\(n+1\)个等式,高斯消元即可。
· 时间复杂度\(O(n^3)\)

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 703;

int cnt;
double a[N][N], f[N];

void gauss() {
	for (int i = 1; i <= cnt; ++i) {
		int tmp = i;
		for (int j = i + 1; j <= cnt; ++j)
			if (fabs(a[j][i]) > fabs(a[tmp][i]))
				tmp = j;
		if (tmp != i)
			for (int j = i; j <= cnt + 1; ++j)
				swap(a[i][j], a[tmp][j]);
		
		for (int j = i + 1; j <= cnt; ++j)
			if (a[j][i] != 0) {
				double k = a[j][i] / a[i][i];
				for (int t = i; t <= cnt + 1; ++t)
					a[j][t] -= a[i][t] * k;
			}
	}
	
	for (int i = cnt; i >= 1; --i) {
		for (int j = i + 1; j <= cnt; ++j)
			a[i][cnt + 1] -= a[i][j] * f[j];
		f[i] = a[i][cnt + 1] / a[i][i];
	}
}

int end[N];
double fac[N];
int n, m, Acnt = 1, ch[N * N][2], fail[N * N], qu[N * N], deep[N * N], to[N * N];
char s[N];

void insert(int id) {
	int tmp = 1;
	for (int i = 1; i <= m; ++i) {
		int v = s[i] == 'H' ? 0 : 1;
		if (ch[tmp][v] == 0) ch[tmp][v] = ++Acnt;
		tmp = ch[tmp][v];
	}
	end[id] = tmp; to[tmp] = id;
}

int dfsID = 0, L[N * N], R[N * N], dfs_a[N * N];
void dfs(int x) {
	L[x] = ++dfsID;
	if (to[x])
		dfs_a[dfsID] = to[x];
	else {
		if (ch[x][0]) dfs(ch[x][0]);
		if (ch[x][1]) dfs(ch[x][1]);
	}
	R[x] = dfsID;
}

void BuildFail() {
	int p = 0, q = 1; qu[1] = 1;
	while (p != q) {
		int u = qu[++p];
		for (int i = 0; i <= 1; ++i)
			if (ch[u][i]) {
				int p = fail[u];
				while (p && ch[p][i] == 0) p = fail[p];
				fail[ch[u][i]] = p ? ch[p][i] : 1;
				deep[ch[u][i]] = deep[u] + 1;
				qu[++q] = ch[u][i];
			}
	}
	
	dfs(1);
}

void Jump(int id) {
	int p = fail[end[id]];
	while (p != 1) {
		for (int i = L[p]; i <= R[p]; ++i)
			if (dfs_a[i])
				a[dfs_a[i]][id] += fac[m - deep[p]];
		p = fail[p];
	}
}

int main() {
	freopen("game.in", "r", stdin);
	freopen("game.out", "w", stdout);
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i) {
		scanf("%s", s + 1);
		insert(i);
	}
	
	fac[0] = 1;
	for (int i = 1; i <= m; ++i) fac[i] = fac[i - 1] / 2;
	
	BuildFail();
	
	cnt = n + 1;
	for (int i = 1; i <= n; ++i) {
		a[i][i] = 1; a[i][cnt] = -1;
		Jump(i);
	}
	for (int i = 1; i <= n; ++i) a[cnt][i] = 1;
	a[cnt][cnt + 1] = 1;
	
	gauss();
	for (int i = 1; i <= n; ++i) printf("%.10lf\n", f[i]);
	
	return 0;
}

T3 相关分析


题目大意

· \(n\)个二元组\((x,y)\)构成一个序列。
· 修改\([L,R],S,T\):区间内所有二元组的\(x\)都加上\(S\)\(y\)都加上\(T\)
· 修改\([L,R],S,T\):区间内所有二元组的\(x\)都变为\(S+i\)\(y\)都变为\(T+i\)\(i\)为二元组的下标。
· 查询\([L,R]\):计算\(\frac{\sum\limits_{i=L}^R(x_i-\bar x)(y_i-\bar y)}{\sum\limits_{i=L}^R(x_i-\bar x)^2}\)
· \(1\leq n,m\leq10^5,1\leq L\leq R\leq n,0\leq|S|,|T|\leq10^5,0\leq|x_i|,|y_i|\leq10^5\)

思路

· 线段树维护4个信息:\(\sum x_i,\sum y_i,\sum x_i^2,\sum x_iy_i\)
· 把要计算的式子全拆开,所需要的信息就是上面的4个信息。
· 区间加:\(\sum (x_i+S)^2=\sum(x_i^2+2Sx_i+S^2),\sum (x_i+S)(y_i+T)=\sum(x_iy_i+Tx_i+Sy_i+ST)\)
· 区间覆盖:\(\sum x_i^2\)可以用平方和公式\(\frac {n(n+1)(2n+1)}6\)计算(直接预处理平方和可以减小常数),\(\sum x_iy_i\)同理。
· 维护一个区间加标记和区间覆盖标记就可以了。
· 时间复杂度\(O(m\log n)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

int in() {
	int k = 0, fh = 1; char c = getchar();
	for (; c < '0' || c > '9'; c = getchar())
		if (c == '-') fh = -1;
	for (; c >= '0' && c <= '9'; c = getchar())
		k = k * 10 + (c ^ 48);
	return k * fh;
}

const int N = 100003;

int n, m, len;
double x[N], y[N], xl, yl;

struct PP {
	double sumx, sumy, sumxx, sumxy;
	PP (double _sumx = 0, double _sumy = 0, double _sumxx = 0, double _sumxy = 0)
		: sumx(_sumx), sumy(_sumy), sumxx(_sumxx), sumxy(_sumxy) {}
	PP operator + (const PP &A) const {
		return PP(sumx + A.sumx, sumy + A.sumy, sumxx + A.sumxx, sumxy + A.sumxy);
	}
};

namespace ST {
	double sum_x[N << 2], sum_y[N << 2], sum_xy[N << 2], sum_xx[N << 2], lazy_x[N << 2], lazy_y[N << 2];
	double cover_x[N << 2], cover_y[N << 2];
	bool cover_mark[N << 2];
	
	ll getsum(int l, int r) {
		return (1ll * r * (r + 1) * ((r << 1) + 1) - 1ll * (l - 1) * l * (((l - 1) << 1) + 1)) / 6;
	}
	
	void coverit(int rt, int l, int r, const double &S, const double &T) {
		lazy_x[rt] = lazy_y[rt] = 0;
		cover_x[rt] = S; cover_y[rt] = T; cover_mark[rt] = true;
		len = r - l + 1;
		sum_x[rt] = (S + l + S + r) * len / 2;
		sum_y[rt] = (T + l + T + r) * len / 2;
		sum_xx[rt] = getsum(S + l, S + r);
		sum_xy[rt] = S * T * len + (S + T) * (1ll * (l + r) * len / 2) + getsum(l, r);
	}
	
	double Slen, Tlen;
	void update(int rt, int l, int r, const double &S, const double &T) {
		if (cover_mark[rt]) {
			if (l != r) {
				int mid = (l + r) >> 1;
				coverit(rt << 1, l, mid, cover_x[rt], cover_y[rt]);
				coverit(rt << 1 | 1, mid + 1, r, cover_x[rt], cover_y[rt]);
			}
			cover_mark[rt] = false;
		}
		lazy_x[rt] += S; lazy_y[rt] += T;
		len = r - l + 1; Slen = S * len; Tlen = T * len;
		sum_xx[rt] += S * 2 * sum_x[rt] + S * Slen;
		sum_xy[rt] += sum_x[rt] * T + sum_y[rt] * S + S * Tlen;
		sum_x[rt] += Slen;
		sum_y[rt] += Tlen;
	}
	
	void pushdown(int rt, int l, int r) {
		if (lazy_x[rt] || lazy_y[rt]) {
			int mid = (l + r) >> 1;
			update(rt << 1, l, mid, lazy_x[rt], lazy_y[rt]);
			update(rt << 1 | 1, mid + 1, r, lazy_x[rt], lazy_y[rt]);
			lazy_x[rt] = lazy_y[rt] = 0;
		}
		if (cover_mark[rt]) {
			int mid = (l + r) >> 1;
			coverit(rt << 1, l, mid, cover_x[rt], cover_y[rt]);
			coverit(rt << 1 | 1, mid + 1,r, cover_x[rt], cover_y[rt]);
			cover_mark[rt] = false;
		}
	}
	
	void pushup(int rt) {
		sum_x[rt] = sum_x[rt << 1] + sum_x[rt << 1 | 1];
		sum_y[rt] = sum_y[rt << 1] + sum_y[rt << 1 | 1];
		sum_xx[rt] = sum_xx[rt << 1] + sum_xx[rt << 1 | 1];
		sum_xy[rt] = sum_xy[rt << 1] + sum_xy[rt << 1 | 1];
	}
	
	void build(int rt, int l, int r) {
		if (l == r) {
			xl = x[l]; yl = y[l];
			sum_x[rt] = xl; sum_y[rt] = yl;
			sum_xx[rt] = xl * xl;
			sum_xy[rt] = xl * yl;
			cover_mark[rt] = false;
			return;
		}
		int mid = (l + r) >> 1;
		build(rt << 1, l, mid);
		build(rt << 1 | 1, mid + 1, r);
		pushup(rt);
	}
	
	void add(int rt, int l, int r, int L, int R, const double &S, const double &T) {
		if (L <= l && r <= R) {
			update(rt, l, r, S, T);
			return;
		}
		int mid = (l + r) >> 1;
		pushdown(rt, l, r);
		if (L <= mid) add(rt << 1, l, mid, L, R, S, T);
		if (R > mid) add(rt << 1 | 1, mid + 1, r, L, R, S, T);
		pushup(rt);
	}
	
	void cover(int rt, int l, int r, int L, int R, const double &S, const double &T) {
		if (L <= l && r <= R) {
			coverit(rt, l, r, S, T);
			return;
		}
		int mid = (l + r) >> 1;
		pushdown(rt, l, r);
		if (L <= mid) cover(rt << 1, l, mid, L, R, S, T);
		if (R > mid) cover(rt << 1 | 1, mid + 1, r, L, R, S, T);
		pushup(rt);
	}
	
	PP getans(int rt, int l, int r, int L, int R) {
		if (L <= l && r <= R) return PP(sum_x[rt], sum_y[rt], sum_xx[rt], sum_xy[rt]);
		PP tl, tr;
		int mid = (l + r) >> 1;
		pushdown(rt, l, r);
		if (L <= mid) tl = getans(rt << 1, l, mid, L, R);
		if (R > mid) tr = getans(rt << 1 | 1, mid + 1, r, L, R);
		return tl + tr;
	}
}

int main() {
	freopen("relative.in", "r", stdin);
	freopen("relative.out", "w", stdout);
	
	n = in(); m = in(); //scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i) scanf("%lf", x + i);
	for (int i = 1; i <= n; ++i) scanf("%lf", y + i);
	ST::build(1, 1, n);
	
	int L, R, op; double S, T, xb, yb, fz, fm;
	PP r;
	while (m--) {
		op = in(); //scanf("%d", &op);
		if (op == 1) {
			L = in(); R = in();//scanf("%d%d", &L, &R);
			r = ST::getans(1, 1, n, L, R);
			xb = r.sumx / (R - L + 1);
			yb = r.sumy / (R - L + 1);
			fz = r.sumxy - r.sumy * xb - r.sumx * yb + xb * yb * (R - L + 1);
			fm = r.sumxx - r.sumx * 2 * xb + xb * xb * (R - L + 1);
			printf("%.10lf\n", fz / fm);
		} else if (op == 2) {
			L = in(); R = in();
			scanf("%lf%lf", &S, &T);
			ST::add(1, 1, n, L, R, S, T);
		} else {
			L = in(); R = in();
			scanf("%lf%lf", &S, &T);
			ST::cover(1, 1, n, L, R, S, T);
		}
		/*
		for (int i = 1; i <= n; ++i) {
			PP r = ST::getans(1, 1, n, i, i);
			printf("(%.3lf, %.3lf) ", r.sumx, r.sumy);
		}
		puts("");*/
	}
	return 0;
}
posted @ 2017-04-13 10:00  abclzr  阅读(640)  评论(0编辑  收藏  举报