20190907 模拟赛题解

T1

题意

给出 N N N,求形如 ( a , b , c , d ) (a,b,c,d) (a,b,c,d)的有序数对使得 a + b + c + d = N a+b+c+d=N a+b+c+d=N a , b , c , d a,b,c,d a,b,c,d均为质数。
N ≤ 100000 N\le 100000 N100000

题解

考场上我就写了 F F T FFT FFT,然后过掉了。

然后发现 100000 100000 100000内的质数大概只有 9500 9500 9500个,然后加起来的和 ≤ 100000 \le100000 100000的质数对是可以枚举的。所以直接先预处理每个数等于两个质数加起来的方案,然后枚举前两个数的和 x x x,乘上 N − x N-x Nx等于后两个数的和的方案,最后全部加起来就是了。

放上 F F T FFT FFT的代码

CODE

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long double ld;
typedef double db;
const int MAXN = 100005;
const db Pi = acos(-1.0);
int p[MAXN/10], cnt;
bool vis[MAXN];
void pre(int N) {
	for(int i = 2; i <= N; ++i) {
		if(!vis[i]) p[++cnt] = i;
		for(int j = 1; j <= cnt && i*p[j] <= N; ++j) {
			vis[i*p[j]] = 1;
			if(i % p[j] == 0) break;
		}
	}
}
const int MAXL = (1<<18)+5;
struct cp {
	db x, y;
	cp(){}
	cp(db x, db y):x(x), y(y){}
	inline cp operator +(const cp &o)const { return cp(x+o.x, y+o.y); }
	inline cp operator -(const cp &o)const { return cp(x-o.x, y-o.y); }
	inline cp operator *(const cp &o)const { return cp(x*o.x - y*o.y, x*o.y + o.x*y); }
}f[MAXL], tmp[2][19][MAXL/2];
int rv[MAXL];

inline void FFT(cp* arr, const int &n, const int &flg) {
	for(register int i = 0; i < n; ++i) if(i < rv[i]) swap(arr[i], arr[rv[i]]);
	static cp t;
	for(register int i = 2, p = 1; i <= n; i<<=1, ++p) {
		for(register int j = 0; j < n; j+=i) {
			for(register int k = j, q = 0; k < j+i/2; ++k, ++q) {
				t = arr[k + i/2] * tmp[flg][p][q];
				arr[k + i/2] = arr[k] - t;
				arr[k] = arr[k] + t;
			}
		}
	}
	if(!flg) for(register int i = 0; i < n; ++i) arr[i].x /= n;
}
int nn[15];
int main () {
	freopen("plus.in", "r", stdin);
	freopen("plus.out", "w", stdout);
	int n = 0, T;
	scanf("%d", &T);
	for(int i = 1; i <= T; ++i)
		scanf("%d", &nn[i]), n = max(n, nn[i]);
	pre(n);
	for(int i = 1; i <= cnt; ++i) f[p[i]] = cp(1, 0);
	int len = 1; while(len <= (n<<1)) len<<=1;
	for(int i = 1; i < len; ++i) 
		rv[i] = (rv[i>>1]>>1)|((i&1)*(len>>1));
	for(int i = 1; i <= 18; ++i) {
		cp w1 = cp(cos(2*Pi/(1<<i)), sin(2*Pi/(1<<i)));
		cp w0 = cp(cos(-2*Pi/(1<<i)), sin(-2*Pi/(1<<i)));
		tmp[1][i][0] = tmp[0][i][0] = cp(1, 0);
		for(int j = 1; j <= (1<<17); ++j)
			tmp[1][i][j] = tmp[1][i][j-1] * w1,
			tmp[0][i][j] = tmp[0][i][j-1] * w0;
	}
	FFT(f, len, 1);
	for(int i = 0; i < len; ++i) f[i] = f[i] * f[i];
	FFT(f, len, 0);
	for(int i = 1; i <= n; ++i) f[i] = cp(round(f[i].x), 0);
	for(int i = n+1; i < len; ++i) f[i] = cp(0, 0);
	
	FFT(f, len, 1);
	for(int i = 0; i < len; ++i) f[i] = f[i] * f[i];
	FFT(f, len, 0);
	
	for(int i = 1; i <= T; ++i)
		printf("%lld\n", (long long)(round(f[nn[i]].x)));
}

T2

题意

假设给定了两个整数 m , n m,n m,n。有 n n n 个互不相同的整数 x 1 , x 2 , . . . , x n ( 0 ≤ x i ≤ 2 m − 1 ) x1,x2,...,xn(0≤xi≤2^m-1) x1,x2,...,xn(0xi2m1)。对于每一个属于 0 0 0 2 m − 1 2^m-1 2m1 的 y,我们找到 p y p_y py使得 x p y x_{p_y} xpy异或 y y y 有最大值。即对于任意的 i = ̸ p y i=\not p_y i≠py,有 y ⊕ x p y &gt; y ⊕ x i y⊕x_{p_y} &gt;y⊕ x_i yxpy>yxi。(其中 ⊕ ⊕ 表示二进制异或)。

现在我们把这个问题反过来。给定 m m m n n n,以及序列 p 0 , p 1 , . . . , p 2 m − 1 p_0,p_1,...,p_{2^m-1} p0,p1,...,p2m1,计算有多少个不同序列 x 1 , x 2 , . . . , x n x_1,x_2,...,x_n x1,x2,...,xn 可以通过上文描述的问题生成出序列 p p p。两个序列是不同的当且仅当存在一个 i 使得两个序列中 x i x_i xi是不同的。

答案对 1000000007 ( 1 0 9 + 7 ) 1000000007(10^9+7) 1000000007(109+7)取模。
m ≤ 16 , n ≤ 2 m m\le16,n\le 2^m m16,n2m

题解

考虑分治。对于区间 ( l , r ) (l,r) (l,r)的所有 p p p考虑,这里的 p l . . . r p_{l...r} pl...r都要在 ( l , r ) (l,r) (l,r)范围内。

假设 x x x序列中既有最高位为 0 0 0的数,又有最高位为 1 1 1的数。那么 ( l , m i d ) (l,mid) (l,mid) p p p ( m i d + 1 , r ) (mid+1,r) (mid+1,r) p p p一定没有交集。因为他们的 p p p都是在对方的区间取得。那么就可以分治下去。方案就是 ( l , m i d ) (l,mid) (l,mid) ( m i d + 1 , r ) (mid+1,r) (mid+1,r)的答案乘起来。

另一种情况是 x x x序列中只有最高位为 0 0 0的或者只有最高位为 1 1 1的数。那么两边的 p p p一定是一一对应的。因为所有的 x i x_i xi最高位是一样的,不用考虑。所以由于一一对应,答案就是 ( l , m i d ) ∗ 2 (l,mid)*2 (l,mid)2,因为最高位既可以是 0 0 0又可以是 1 1 1。这种情况的条件是 p ( l , m i d ) = p ( m i d + 1 , r ) p(l,mid)=p(mid+1,r) p(l,mid)=p(mid+1,r),顺次一一相等。

边界就是 l = r l=r l=r时返回 1 1 1。因为相当于 m = 0 m=0 m=0,下面的数只有一个选择。

考试时差点想到…没有发现第二种情况两边的 p p p一一对应。

CODE

#include <cstdio>
const int mod = 1e9 + 7;
const int MAXN = (1<<16)+5;
int n, m, s, p[MAXN]; bool vis[MAXN];
int solve(int l, int r) {
	if(l == r) return 1;
	int mid = (l + r) >> 1, re = 0;
	bool flg = 1;
	for(int i = l; i <= mid; ++i)
		if(p[i] != p[mid+i-l+1]) { flg = 0; break; }
	if(flg) re = (re + 2ll * solve(l, mid) % mod) % mod;
	
	flg = 1;
	for(int i = l; i <= mid; ++i) vis[p[i]] = 1;
	for(int i = mid+1; i <= r; ++i) if(vis[p[i]]) { flg = 0; break; }
	for(int i = l; i <= mid; ++i) vis[p[i]] = 0;
	if(flg) re = (re + 1ll * solve(l, mid) * solve(mid+1, r) % mod) % mod;
	
	return re;
}
int main () {
	freopen("match.in", "r", stdin);
	freopen("match.out", "w", stdout);
	scanf("%d%d", &m, &n);
	s = 1<<m;
	for(int i = 1; i <= s; ++i) scanf("%d", &p[i]);
	printf("%d\n", solve(1, s));
}

T3

题意

一棵树,初始时所有边都是黑色。有两种操作:

  • 0 0 0:求 a a a b b b路径上的黑边数量。
  • 1 1 1:将 a a a b b b路径上所有边设为白边,然后把只有一个点在这条路径上的边设为黑边。
题解

以为写了 80 80 80分暴力,发现有 30 30 30的数据不符合题目描述(可能是题目描述不符合数据? ),然后就 50 50 50了…

正解比较巧妙,我们把 1 1 1操作看做把 a → b a\to b ab所有点染上一个新的颜色,一条边为白边当且仅当两个点颜色相同。然后我们树剖后只需要维护线段上不同颜色段数就行了。

CODE

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 200005;
const int MAXQ = 300005;
int n, fir[MAXN], to[MAXN<<1], nxt[MAXN<<1], cnt;
inline void link(int u, int v) {
	to[++cnt] = v; nxt[cnt] = fir[u]; fir[u] = cnt;
	to[++cnt] = u; nxt[cnt] = fir[v]; fir[v] = cnt;
}
int fa[MAXN], dep[MAXN], son[MAXN], top[MAXN], dfn[MAXN], sz[MAXN];
int tmr;
void dfs1(int u, int ff) {
	dep[u] = dep[fa[u]=ff] + (sz[u]=1);
	for(int i = fir[u], v; i; i = nxt[i])
		if((v=to[i]) != ff) {
			dfs1(v, u), sz[u] += sz[v];
			if(sz[v] > sz[son[u]]) son[u] = v;
		}
}
void dfs2(int u, int tp) {
	top[u] = tp;
	dfn[u] = ++tmr;
	if(son[u]) dfs2(son[u], tp);
	for(int i = fir[u], v; i; i = nxt[i])
		if((v=to[i]) != fa[u] && v != son[u])
			dfs2(v, v);
}
struct seg {
	int l, r, ans;
	seg(){}
	seg(int l, int r, int ans):l(l), r(r), ans(ans){}
	inline seg operator !() const{
		return seg(r, l, ans);
	}
	inline seg operator +(const seg &o)const {
		if(!ans) return o;
		if(!o.ans) return *this;
		return (r^o.l) ? seg(l, o.r, ans + o.ans) : seg(l, o.r, ans + o.ans - 1);
	}
}v[MAXN<<2];
int lz[MAXN<<2], now;
inline void upd(int i) { v[i] = v[i<<1] + v[i<<1|1]; }
inline void pd(int i) {
	if(~lz[i]) {
		lz[i<<1] = lz[i<<1|1] = lz[i];
		v[i<<1] = v[i<<1|1] = seg(lz[i], lz[i], 1);
		lz[i] = -1;
	}
}
void build(int i, int l, int r) {
	lz[i] = -1;
	if(l == r) { v[i] = seg(l, l, 1); return; }
	int mid = (l + r) >> 1;
	build(i<<1, l, mid);
	build(i<<1|1, mid+1, r);
	upd(i);
}
void modify(int i, int l, int r, int x, int y, int z) {
	if(x <= l && r <= y) { v[i] = seg(z, z, 1); lz[i] = z; return; }
	pd(i);
	int mid = (l + r) >> 1;
	if(x <= mid) modify(i<<1, l, mid, x, y, z);
	if(y > mid) modify(i<<1|1, mid+1, r, x, y, z);
	upd(i);
}
seg query(int i, int l, int r, int x, int y) {
	if(x <= l && r <= y) { return v[i]; }
	pd(i);
	int mid = (l + r) >> 1; seg re = seg(-1, -1, 0);
	if(x <= mid) re = re + query(i<<1, l, mid, x, y);
	if(y > mid) re = re + query(i<<1|1, mid+1, r, x, y);
	upd(i);
	return re;
}
inline void cover(int a, int b) {
	++now;
	while(top[a]^top[b]) {
		if(dep[top[a]] > dep[top[b]]) modify(1, 1, n, dfn[top[a]], dfn[a], now), a = fa[top[a]];
		else modify(1, 1, n, dfn[top[b]], dfn[b], now), b = fa[top[b]];
	}
	if(dep[a] > dep[b]) modify(1, 1, n, dfn[b], dfn[a], now);
	else modify(1, 1, n, dfn[a], dfn[b], now);
}
inline int getans(int a, int b) {
	seg L = seg(-1, -1, 0), R = seg(-1, -1, 0);
	while(top[a]^top[b]) {
		if(dep[top[a]] > dep[top[b]]) L = L + !query(1, 1, n, dfn[top[a]], dfn[a]), a = fa[top[a]];
		else R = query(1, 1, n, dfn[top[b]], dfn[b]) + R, b = fa[top[b]];
	}
	if(dep[a] > dep[b]) L = L + !query(1, 1, n, dfn[b], dfn[a]) + R;
	else L = L + query(1, 1, n, dfn[a], dfn[b]) + R;
	return L.ans-1;
}
int main () {
	freopen("colour.in", "r", stdin);
	freopen("colour.out", "w", stdout);
	scanf("%d", &n); now = n;
	for(int i = 1, x, y; i < n; ++i)
		scanf("%d%d", &x, &y), link(x, y);
	dfs1(1, 0), dfs2(1, 1);
	build(1, 1, n);
	int q, op, a, b;
	scanf("%d", &q);
	while(q-->0) {
		scanf("%d%d%d", &op, &a, &b);
		if(op == 0) printf("%d\n", getans(a, b));
		else cover(a, b);
	}
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值