HAOI2018题解

48 篇文章 0 订阅
16 篇文章 1 订阅

还都是比较可做的。


奇怪的背包

对于每一个物品 i i i,他能拼出的物品 d d d满足 d ∣ g c d ( P , V [ i ] ) d|gcd(P,V[i]) dgcd(P,V[i]),所以一个物品只需要对 P P P g c d gcd gcd即可。
根据蜚蜀定理你一堆物品能拼出的物品为这堆物品的 g c d gcd gcd的倍数。
然后你做一个 d p dp dp表示, f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个物品最大公约数为 j j j的方案数。
然后对于每个约数枚举倍数搞一遍就好了。


#include <map>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
int _max(int x, int y) {return x > y ? x : y;}
int _min(int x, int y) {return x < y ? x : y;}
int read() {
	int s = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * f;
}

int plen, p[1600];
LL f[1600][1600], g[1600];
int s[1600];
map<int, int> mp;

int gcd(int a, int b) {return b == 0 ? a : gcd(b, a % b);}

LL pow_mod(LL a, LL k) {
	LL ans = 1;
	while(k) {
		if(k & 1) (ans *= a) %= mod;
		(a *= a) %= mod; k /= 2;
	} return ans;
}

int main() {
	int n = read(), Q = read(), P = read(); int u = P;
	for(int i = 1; i <= sqrt(u); i++) {
		if(u % i == 0) {
			p[++plen] = i;
			p[++plen] = u / i;
		}
	} if(p[plen] * p[plen] == u) --plen;
	sort(p + 1, p + plen + 1);
	for(int i = 1; i <= plen; i++) mp[p[i]] = i;
	for(int i = 1; i <= n; i++) {
		int x = gcd(read(), P);
		++s[mp[x]];
	} f[0][plen] = 1;
	for(int i = 1; i <= plen; i++) {
		for(int j = 1; j <= plen; j++) if(f[i - 1][j]){
			(f[i][j] += f[i - 1][j]) %= mod;
			(f[i][mp[gcd(p[j], p[i])]] += f[i - 1][j] * (pow_mod(2, s[i]) - 1) % mod) %= mod;
		}
	} for(int i = 1; i <= plen; i++) g[i] = (f[plen][i] + mod) % mod;
	for(int i = plen - 1; i >= 1; i--) {
		for(int j = i + 1; j <= plen; j++) if(p[j] % p[i] == 0){
			(g[j] += g[i]) %= mod;
		}
	} for(int i = 1; i <= Q; i++) {
		int x = gcd(P, read());
		printf("%d\n", g[mp[x]]);
	}
	return 0;
}


反色游戏

先考虑无解,对于任意一个连通块,假如 1 1 1的个数为奇数,就无解,否则有解。
因为你想,假如你选了一条边,奇偶性无论如何都是不变的。
否则你的方案数就是 2 m − n + p 2^{m-n+p} 2mn+p p p p为联通块个数, n n n为点数, m m m为边数。
你假设当前的边已经构成了一个森林,且满足与原图连通性相同, m − n + p m-n+p mn+p即未加入的边数,每加入一条边,他都可以选择反转两个端点的颜色或不反转,因为这样奇偶性并不改变。
你考虑用强连通维护每个点的情况,要求出每一个点去掉他会有多少个新的连通块,以及分成的新联通快的奇偶性什么的。。。


#include <ctime>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>

using namespace std;
typedef long long LL;
int _max(int x, int y) {return x > y ? x : y;}
int _min(int x, int y) {return x < y ? x : y;}
const int N = 100001;
const LL mod = 1e9 + 7;
int read() {
	int s = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * f;
}
void put(int x) {
	if(x >= 10) put(x / 10);
	putchar(x % 10 + '0');
}

struct edge {
	int x, y, next;
} e[2 * N]; int len, last[N];
int id, cnt, now, dfn[N], low[N], cut[N], tot[N], hh[N];
int bin[N], du[N], belong[N];
char ss[N];
bool v[N];

void ins(int x, int y) {
	e[++len].x = x, e[len].y = y;
	e[len].next = last[x], last[x] = len;
}

void tarjan(int x, int fa) {
	low[x] = dfn[x] = ++id; belong[x] = now;
	v[x] = 1, tot[x] = ss[x] == '1';
	for(int k = last[x]; k; k = e[k].next) {
		int y = e[k].y;
		if(!dfn[y]) {
			tarjan(y, x);
			tot[x] += tot[y];
			if(low[y] >= dfn[x]) {
				cut[x]++; v[x] &= (tot[y] & 1) == 0;
				hh[x] += tot[y];
			} else low[x] = _min(low[x], low[y]);
		} else if(y != fa) low[x] = _min(low[x], dfn[y]);
	} if(!fa) cut[x]--;
}

int main() {
	bin[0] = 1; for(int i = 1; i < N; i++) bin[i] = (bin[i - 1] * 2) % mod;
	int tt = read();
	while(tt--) {
		int n = read(), m = read();
		int ans = 0;
		memset(du, 0, sizeof(du));
		len = 0; memset(last, 0, sizeof(last));
		memset(dfn, 0, sizeof(dfn)), memset(low, 0, sizeof(low));
		memset(belong, 0, sizeof(belong)), memset(cut, 0, sizeof(cut));
		memset(v, 0, sizeof(v)), memset(hh, 0, sizeof(hh)), memset(tot, 0, sizeof(tot));
		memset(belong, 0, sizeof(belong));
		for(int i = 1; i <= m; i++) {
			int x = read(), y = read();
			ins(x, y), ins(y, x), du[x]++, du[y]++;
		} scanf("%s", ss + 1);
		cnt = id = 0;
		int bk = 0;
		for(int i = 1; i <= n; i++) if(!dfn[i]) {
			++cnt, now = i, tarjan(i, 0);
			bk += tot[i] & 1;
		} int sum = m - n + cnt;
		if(bk) putchar('0'), putchar(' ');
		else put(bin[sum]), putchar(' ');
		for(int i = 1; i <= n; i++) {
			if(!du[i]) {
				if(bk - (ss[i] == '1')) putchar('0'), putchar(' ');
				else put(bin[sum]), putchar(' ');
			} else {
				if(v[i] && !(bk - (tot[belong[i]] & 1)) && !((tot[belong[i]] - (ss[i] == '1') - hh[i]) & 1)) put(bin[sum - du[i] + cut[i] + 1]), putchar(' ');
				else putchar('0'), putchar(' ');
			}
		} puts("");
	}
	return 0;
}


字串覆盖

考虑 r − l + 1 &gt; = 50 r-l+1&gt;=50 rl+1>=50的部分,
先求一边 S A SA SA,建一个主席树。
你可以对于一个串二分出他在 S A SA SA数组中的范围,然后你相当于在主席树中寻找下一个这个串出现的位置。
发现对于 r − l + 1 &gt; = 2000 r-l+1&gt;=2000 rl+1>=2000,需要这样暴力求 n x t nxt nxt的次数很少其实很少直接暴力做。
然后 50 &lt; = r − l + 1 &lt; = 2000 50&lt;=r-l+1&lt;=2000 50<=rl+1<=2000,因为随机什么的,次数又有限制,所以很快。
对于 r − l + 1 &lt; = 50 r-l+1&lt;=50 rl+1<=50,我们对于每一个点每一个长度求一个 n x t nxt nxt,然后用倍增优化暴力找 n x t nxt nxt的过程。
这样预处理的时间复杂度是 O ( 50 ∗ l o g n ∗ n ) O(50*logn*n) O(50lognn)但实际上是可以做到不带那个 l o g log log C l a r i s Claris Claris使用了一种基数排序的方式将复杂度做到 O ( 50 ∗ n ) O(50*n) O(50n)预处理。


#include <ctime>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <algorithm>

using namespace std;
typedef long long LL;
int _max(int x, int y) {return x > y ? x : y;}
int _min(int x, int y) {return x < y ? x : y;}
const int N = 100001;
int read() {
	int s = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * f;
}
void put(LL x) {
	if(x >= 10) put(x / 10);
	putchar(x % 10 + '0');
}

struct tnode {
	int lc, rc, c;
} t[N * 20]; int cnt, rt[N * 2];
int z, Rsort[N * 2], Rank[N * 2], sa[N * 2], tt[N * 4], height[N * 2];
int a[N * 2], bin[20], Log[N * 2];
char s1[N], s2[N];
struct st_table {
	int n, f[18][N * 2];
	
	void bt() {
		for(int i = 1; i <= z; i++) f[0][i] = height[i];
		for(int i = 1; bin[i] <= z; i++) {
			for(int j = 1; j <= z - bin[i] + 1; j++) {
				f[i][j] = _min(f[i - 1][j], f[i - 1][j + bin[i - 1]]);
			}
		}
	}
	
	int query(int l, int r) {
		l++;
		int hh = Log[r - l + 1];
		return _min(f[hh][l], f[hh][r - bin[hh] + 1]);
	}
} st;
struct node {int s, t, l, r;} q[N];
struct beizeng {
	vector<int> q[N];
	int n, f[17][N], a[N];
	LL s[N];
	
	void dfs(int x) {
		s[x] += x;
		for(int k = 0; k < q[x].size(); k++) {
			int y = q[x][k];
			s[y] = s[x], dfs(y);
		}
	}
	
	void bt() {
		for(int i = 1; i <= n; i++) f[0][i] = a[i], q[i].clear();
		for(int i = 1; bin[i] <= n; i++) {
			for(int j = 1; j <= n - bin[i] + 1; j++) {
				f[i][j] = f[i - 1][f[i - 1][j]];
			}
		} for(int i = 1; i <= n; i++) q[a[i]].push_back(i);
		dfs(0);
	}
} g;
vector<int> o[N];
LL ans[N];

void Link(int &u, int l, int r, int p) {
	if(!u) u = ++cnt;
	t[u].c++;
	if(l == r) return ;
	int mid = (l + r) / 2;
	if(p <= mid) Link(t[u].lc, l, mid, p);
	else Link(t[u].rc, mid + 1, r, p);
}

void Merge(int &u1, int u2) {
	if(!u1 || !u2) {u1 += u2; return ;}
	t[u1].c += t[u2].c;
	Merge(t[u1].lc, t[u2].lc);
	Merge(t[u1].rc, t[u2].rc);
}

int fl(int u1,int u2, int l, int r, int ll, int rr) {
	if(!(t[u1].c - t[u2].c)) return 0;
	if(l == r) return l;
	int mid = (l + r) / 2;
	if(rr <= mid) return fl(t[u1].lc, t[u2].lc, l, mid, ll, rr);
	else if(ll > mid) return fl(t[u1].rc, t[u2].rc, mid + 1, r, ll, rr);
	else {
		int s = fl(t[u1].lc, t[u2].lc, l, mid, ll, mid);
		if(s == 0) return fl(t[u1].rc, t[u2].rc, mid + 1, r, mid + 1, rr);
		return s;
	}
}

void get_sa() {
	memcpy(Rank, a, sizeof(Rank));
	int m = 30;
	memset(Rsort, 0, sizeof(Rsort));
	for(int i = 1; i <= z; i++) ++Rsort[Rank[i]];
	for(int i = 1; i <= m; i++) Rsort[i] += Rsort[i - 1];
	for(int i = z; i >= 1; i--) sa[Rsort[Rank[i]]--] = i;
	int ln = 1, p = 0;
	while(p < z) {
		int tp = 0;
		for(int i = z - ln + 1; i <= z; i++) tt[++tp] = i;
		for(int i = 1; i <= z; i++) if(sa[i] - ln > 0) tt[++tp] = sa[i] - ln;
		
		memset(Rsort, 0, sizeof(Rsort));
		for(int i = 1; i <= z; i++) ++Rsort[Rank[tt[i]]];
		for(int i = 1; i <= m; i++) Rsort[i] += Rsort[i - 1];
		for(int i = z; i >= 1; i--) sa[Rsort[Rank[tt[i]]]--] = tt[i];
		
		for(int i = 1; i <= z; i++) tt[i] = Rank[i];
		p = 1; Rank[sa[1]] = 1;
		for(int i = 2; i <= z; i++) {
			if(tt[sa[i]] != tt[sa[i - 1]] || tt[sa[i] + ln] != tt[sa[i - 1] + ln]) ++p;
			Rank[sa[i]] = p;
		} ln *= 2, m = p;
	}
}

void get_height() {
	int p = 0;
	for(int i = 1; i <= z; i++) {
		int j = sa[Rank[i] - 1];
		if(p) p--;
		while(a[i + p] == a[j + p]) p++;
		height[Rank[i]] = p;
	}
}

int findl(int p, int len) {
	int l = 1, r = p - 1, hh = 0;
	while(l <= r) {
		int mid = (l + r) / 2;
		if(st.query(p - mid, p) >= len) l = mid + 1, hh = mid;
		else r = mid - 1;
	} return p - hh;
}

int findr(int p, int len) {
	int l = 1, r = z - p, hh = 0;
	while(l <= r) {
		int mid = (l + r) / 2;
		if(st.query(p, p + mid) >= len) l = mid + 1, hh = mid;
		else r = mid - 1;
	} return p + hh;
}

int main() {
	int n = read(), K = read();
	scanf("%s%s", s1 + 1, s2 + 1);
	for(int i = 1; i <= n; i++) a[i] = s1[i] - 'a' + 2;
	a[n + 1] = 1;
	for(int i = 1; i <= n; i++) a[i + n + 1] = s2[i] - 'a' + 2;
	z = 2 * n + 1, get_sa(), get_height();
	bin[0] = 1;for(int i = 1; i <= 19; i++) bin[i] = bin[i - 1] << 1;
	Log[1] = 0; for(int i = 2; i <= z; i++) Log[i] = Log[i >> 1] + 1;
	st.n = z, st.bt();
	for(int i = 1; i <= z; i++) {
		if(sa[i] <= n) Link(rt[i], 1, n, sa[i]);
		Merge(rt[i], rt[i - 1]);
	} int m = read();
	for(int i = 1; i <= m; i++) {
		q[i].s = read(), q[i].t = read(), q[i].l = read(), q[i].r = read();
		int len = q[i].r - q[i].l + 1;
		if(len <= q[i].t - q[i].s + 1) o[len].push_back(i);
	}
	for(int len = 1; len <= 50; len++) {
		int pp = o[len].size();
		if(!pp) continue;
		g.n = n;
		for(int j = 1; j <= n; j++) {
			int ll = findl(Rank[j], len), rr = findr(Rank[j], len);
			if(j + len > n - len + 1) break;
			int gg = fl(rt[rr], rt[ll - 1], 1, n, j + len, n - len + 1);
			g.a[j] = gg;
		} g.bt();
		for(int z = 0; z < pp; z++) {
			int i = o[len][z];
			int s = q[i].s, t = q[i].t, l = q[i].l, r = q[i].r;
			int ll = findl(Rank[n + l + 1], r - l + 1), rr = findr(Rank[n + l + 1], r - l + 1);
			int gg = fl(rt[rr], rt[ll - 1], 1, n, s, t - len + 1);
			if(gg == 0) ans[i] = 0;
			else {
				LL s1 = 1, s2; int cc = gg;
				for(int j = 16; j >= 0; j--) if(g.f[j][gg] && g.f[j][gg] + len - 1 <= t){
					s1 += bin[j];
					gg = g.f[j][gg];
				} s2 = g.s[cc] - g.s[g.a[gg]];
				ans[i] = s1 * K - s2;
			}
		}
	} for(int len = 50 + 1; len <= m; len++) {
		for(int z = 0; z < o[len].size(); z++) {
			int i = o[len][z];
			int s = q[i].s, t = q[i].t, l = q[i].l, r = q[i].r;
			int ll = findl(Rank[n + l + 1], r - l + 1), rr = findr(Rank[n + l + 1], r - l + 1);
			int gg = fl(rt[rr], rt[ll - 1], 1, n, s, t - len + 1);
			if(gg == 0) ans[i] = 0;
			else {
				LL s1 = 1, s2 = gg;
				while(1) {
					if(gg + len > t - len + 1) break;
					gg = fl(rt[rr], rt[ll - 1], 1, n, gg + len, t - len + 1);
					if(gg == 0) break;
					s1++, s2 += gg;
				} ans[i] = s1 * K - s2;
			}
		}
	} for(int i = 1; i <= m; i++) put(ans[i]), puts("");
	return 0;
}


苹果树

以前写过了


#include <cstdio>
#include <cstring>

using namespace std;
typedef long long LL;

LL mod;
LL C[2100][2100], jc[2100];

int main() {
	int n; scanf("%d%lld", &n, &mod);
	for(int i = 0; i <= n; i++) C[i][0] = 1;
	for(int i = 1; i <= n; i++) for(int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
	jc[0] = 1; for(int i = 1; i <= n; i++) jc[i] = (LL)jc[i - 1] * i % mod;
	LL ans = 0;
	for(int i = 2; i <= n; i++) {
		LL hh = jc[i];
		for(int j = n - i + 1; j >= 1; j--) {
			LL u = jc[j] * C[n - i][j - 1] % mod * hh % mod;
			(ans += (LL)j * (n - j) % mod * u % mod) %= mod;
			(hh *= (LL)n - j) %= mod;
		}
	} printf("%lld\n", ans);
	return 0;
}

染色

g [ i ] g[i] g[i]为至少有 i i i种颜色的方案数,这个东西很好求。
然后直接二项式反演即可。


#include <ctime>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>

using namespace std;
typedef long long LL;
int _max(int x, int y) {return x > y ? x : y;}
int _min(int x, int y) {return x < y ? x : y;}
const int M = 100002, N = 10000001;
const LL mod = 1004535809;
int read() {
	int s = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * f;
}
void put(int x) {
	if(x >= 10) put(x / 10);
	putchar(x % 10 + '0');
}

int w[M], R[M * 4];
int jc[N], inv[N], f[M * 4], g[M * 4];
int w1[M * 4], w2[M * 4];

int pow_mod(int a, int k) {
	int ans = 1;
	while(k) {
		if(k & 1) ans = (LL)ans * a % mod;
		a = (LL)a * a % mod; k /= 2;
	} return ans;
}

int C(int n, int m) {
	if(n < m) return 0;
	return (LL)jc[n] * inv[m] % mod * inv[n - m] % mod;
}

void pre(int len) {
	for(int i = 1; i < len; i <<= 1) {
		w1[i] = pow_mod(3, (mod - 1) / (i << 1));
		w2[i] = pow_mod(w1[i], mod - 2);
	}
}

int add(int x, int y) {
	x += y;
	while(x >= mod) x -= mod;
	return x;
}

void NTT(int y[], int len, int on) {
	for(int i = 0; i < len; i++) if(i < R[i]) swap(y[i], y[R[i]]);
	for(int i = 1; i < len; i <<= 1) {
		int wn = on == -1 ? w2[i] : w1[i];
		for(int j = 0; j < len; j += i << 1) {
			int w = 1;
			for(int k = 0; k < i; k++, w = (LL)w * wn % mod) {
				LL u = y[j + k], v = (LL)y[j + k + i] * w % mod;
				y[j + k] = add(u, v), y[j + k + i] = add(u, mod - v);
			}
		}
	} if(on == -1) {
		int cc = pow_mod(len, mod - 2);
		for(int i = 0; i < len; i++) y[i] = (LL)y[i] * cc % mod;
	}
}

int main() {
	int n = read(), m = read(), S = read();
	jc[0] = inv[0] = 1; for(int i = 1; i <= _max(n, m); i++) jc[i] = (LL)jc[i - 1] * i % mod;
	inv[_max(n, m)] = pow_mod(jc[_max(n, m)], mod - 2); for(int i = _max(n, m) - 1; i >= 1; i--) inv[i] = (LL)inv[i + 1] * (i + 1) % mod;
	for(int i = 0; i <= m; i++) w[i] = read();
	int p = 1; f[0] = pow_mod(m, n);
	for(int i = 1; i <= m; i++) {
		p = (LL)p * C(n - (i - 1) * S, S) % mod;
		f[i] = (LL)C(m, i) * p % mod * pow_mod(m - i, n - i * S) % mod;
		f[i] = (LL)f[i] * jc[i] % mod;
	} for(int i = 0; i <= m; i++) g[m - i] = i & 1 ? mod - inv[i] : inv[i];
	int len = 1, L = 0;
	for(; len <= m + m; len <<= 1);
	for(int i = 0; i < len; i++) R[i] = (R[i >> 1] >> 1) | (i & 1) * (len >> 1);
	pre(len);
	NTT(f, len, 1), NTT(g, len, 1);
	for(int i = 0; i < len; i++) f[i] = (LL)f[i] * g[i] % mod;
	NTT(f, len, -1);
	int ans = 0;
	for(int i = m; i <= m + m; i++) ans = add(ans, (LL)f[i] * w[i - m] % mod * inv[i - m] % mod);
	printf("%d\n", ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值