2019.9.7 多校联考题解【提高组】

2019.9.7 多校联考题解

T1.四个质数的和

题目

题目描述

给定了一个正整数 N N N

有多少种方法将 N N N分解成为四个质数 a , b , c , d a,b,c,d a,b,c,d的和。例如: 9 = 2 + 2 + 2 + 3 = 2 + 2 + 3 + 2 = 2 + 3 + 2 + 2 = 3 + 2 + 2 + 2 9 = 2 + 2 + 2 + 3 = 2 + 2 + 3 + 2 = 2 + 3 + 2 + 2 = 3 + 2 + 2 + 2 9=2+2+2+3=2+2+3+2=2+3+2+2=3+2+2+2,故共有 4 4 4种方法将 9 9 9分解成为四个整数。

输入

本题多组数据测试:

第一行读入一个整数 T T T表示数据组数。

接下来共 T T T行,每行包含一个正整数 N N N

输出

T T T行,每行一个整数表示答案。

样例输入

2
9
10

样例输出

4
6

数据范围

对于 10 % 10% 10的数据, N ≤ 10 N\le10 N10

对于 40 % 40% 40的数据, N ≤ 100 N\le100 N100

对于 70 % 70% 70的数据, N ≤ 1000 N\le 1000 N1000

对于 100 % 100% 100的数据, T ≤ 10 , N ≤ 100000 T\le 10,N\le 100000 T10,N100000

分析

打表发现, 100000 100000 100000以内的质数有9500多个,显然直接枚举是过不了的。

考虑中途相遇法。

对于每一个数 n n n,设 f ( i ) , i ≤ n f(i),i\le n f(i),in为用两个质数凑出 i i i的方案数,显然这个函数可以快速地打表,然后再枚举剩下的两个质数。

但这样时间复杂度是过高的,多打几次 f ( i ) f(i) f(i)就发现每次 f ( i ) f(i) f(i)都是相同的,那么我们就预先打出这个表即可。

参考代码

#include<cstdio>

typedef long long ll;
const int Maxn=100000;

int N;
bool ispri[Maxn+5];
ll f[Maxn+5];
int pri[10000+5],tot;

inline void Init() {
	for(int i=2;i<=Maxn;i++) {
		if(!ispri[i])
			pri[++tot]=i;
		for(int j=1;j<=tot&&i*pri[j]<=Maxn;j++) {
			ispri[i*pri[j]]=true;
			if(i%pri[j]==0)break;
		}
	}
	for(int i=1;i<=tot;i++)
		for(int j=1;j<=tot&&pri[i]+pri[j]<=Maxn;j++)
			f[pri[i]+pri[j]]++;
}

int main() {
	freopen("plus.in","r",stdin);
	freopen("plus.out","w",stdout);
	int T;
	scanf("%d",&T);
	Init();
	while(T--) {
		scanf("%d",&N);
		ll ans=0;
		for(int i=4;i<=N-4;i++)
			ans+=(f[i]*f[N-i]);
		printf("%lld\n",ans); 
	}
	return 0;
}

T2.匹配最大异或

题目

题目描述

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

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

答案对 1000000007 ( 1 0 9 + 7 ) 1000000007(10^9+7) 1000000007(109+7)取模。

输入

第一行两个用空格隔开的整数 m , n m,n m,n。其中 2 m 2^m 2m p p p序列的长度, n n n x x x序列的长度。

之后 2 m 2^m 2m行,每行一个整数,表示 p p p序列。保证 1 1 1 n n n中的每一个数在输入中都至少出现一次。

输出

输出一行一个整数表示答案。

样例输入

样例1:

3 6
1
1
2
2
3
4
5
6

样例2:

2 3
1
2
1
3

样例3:

3 8
1
2
3
4
5
6
7
8

样例输出

样例1:

4

样例2:

0

样例3:

1

数据范围

对于 30 % 30% 30的数据: m ≤ 3 , n ≤ 4 m\le3,n\le4 m3,n4

另外 10 % 10% 10的数据: m = 0 m=0 m=0

另外 10 % 10% 10的数据: n = 1 n=1 n=1

另外 10 % 10% 10的数据: p i = i p_i=i pi=i

对于 100 % 100% 100的数据: 0 ≤ m ≤ 16 , 1 ≤ n ≤ 2 m 0\le m\le16,1\le n\le2^m 0m161n2m

分析

考虑由高位向低位一位一位的考虑。

然后就。。。 我不想写了QAQ。。。 真的脑洞。。。

参考代码

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

typedef long long ll;
const ll Mod=1000000007;
const int Maxn=(1<<16);

int N,M,P[Maxn+5];

ll DFS(int l,int r) {
	if(l==r)return 1LL;
	int mid=(l+r)>>1;
	bool flag=true;
	for(int i=l,j=mid+1;i<=mid;i++,j++)
		if(P[i]!=P[j]) {
			flag=false;
			break;
		}
	if(flag)return DFS(l,mid)*2LL%Mod;
	for(int i=l,j=mid+1;i<=mid;i++,j++)
		if(P[i]==P[j])return 0;
	return DFS(l,mid)*DFS(mid+1,r)%Mod;
}

int main() {
	freopen("match.in","r",stdin);
	freopen("match.out","w",stdout);
	scanf("%d %d",&M,&N);
	for(int i=0;i<(1<<M);i++)
		scanf("%d",&P[i]);
	printf("%lld\n",DFS(0,(1<<M)-1));
	return 0;
}

T3.染色相邻的边

题目

题目描述

给定一个 N N N个点的树,点的标号从 1 1 1 N N N

一条树上点 a a a到点 b b b的简单路径 P P P是一个 k k k个点的序列( a = P 1 , P 2 , … , P k = b a=P_1,P_2,\ldots,P_k=b a=P1,P2,,Pk=b),相邻的两个点之间有边连接且任何一个点至多在序列中出现一次。注意 a a a可能和 b b b是相等的。简单路径上的边就是连接序列上相邻两个点的边。

一条简单路径的邻边是只有一个端点在简单路径上的边。树上的每条边是黑色的或者白色的。最开始所有的边都是黑色的。

Q Q Q次操作,有两种操作类型。

  • 0:计算 a a a b b b的简单路径上有多少条边是黑色的。
  • 1:将简单路径 a a a b b b上的边全部设置成白色的。将简单路径a到b上的邻边设置成黑色的。
输入

第一行一个整数 N ( 1 ≤ N ≤ 200000 ) N(1\le N\le200000) N(1N200000)

接下来 N − 1 N-1 N1行,每行两个整数 a i , b i a_i,b_i aibi,表示一条树边。保证读入的是一棵树。

接下来一行一个整数 Q ( 1 ≤ Q ≤ 300000 ) Q(1\le Q\le300000) Q(1Q300000)

接下来 Q Q Q行,每行三个整数 t i , a i , b i t_i, a_i, b_i ti,ai,bi。其中 t i t_i ti表示操作类型。

输出

对于每个0操作,输出一行一个整数表示答案。

样例输入

样例1:

8
1 7
7 8
1 5
1 3
5 2
5 6
6 4
5
1 2 7
0 2 8
1 6 7
0 1 2
0 3 8

样例2:

19
1 2
2 3
1 5
5 4
5 6
6 7
6 8
1 11
11 12
11 13
11 10
10 9
13 14
13 15
15 16
15 17
15 18
15 19
6
1 19 8
0 16 2
0 16 3
1 12 9
0 19 8
0 16 9

样例输出

样例1:

1
1
2

样例2:

2
3
2
2

数据范围

对于 5 % 5% 5的数据: N = 1 N=1 N=1
对于 20 % 20% 20的数据: N ≤ 200 N\le 200 N200
对于 30 % 30% 30的数据: N ≤ 2000 N\le2000 N2000
另外 20 % 20% 20的数据:树的形态是一条链。
另外 30 % 30% 30的数据:操作1 a i = b i a_i=b_i ai=bi,且 a i a_i ai是随机生成的。
对于 100 % 100% 100的数据: 1 ≤ N ≤ 200000 , 1 ≤ Q ≤ 300000 1\le N\le 200000,1\le Q\le 300000 1N200000,1Q300000

提示

如果你在Windows环境下,请在编译选项中添加如下的内容:

-Wl,--stack=200000000

分析

看到这个题我的第一反应是树链剖分 (然而我考场上没有搞出来)

考虑修改操作。对于每一条链,我们用线段树维护链上的修改时间即可。

考虑查询操作。对于每一条链,我们可以用链上的修改时间戳来判定,即对于两个点,若两个点的时间戳不同则该边为黑色,否则为白色。两条重链的连接处用两个单点查询即可。

真的板子。。。

参考代码

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

const int Maxn=200000;

struct Edge {
	int to;
	Edge *nxt;
};
Edge pool[Maxn*2+5];
Edge *ecnt=&pool[0],*G[Maxn+5];
inline void addedge(int u,int v) {
	Edge *p=++ecnt;
	p->to=v;
	p->nxt=G[u],G[u]=p;
}

int N;

struct SegmentTree {
	struct Segment {
		int tag,cnt;
		int lef_col,rig_col;
	};
	Segment t[Maxn*4+5];
	void pushup(int rt) {
		t[rt].lef_col=t[rt<<1].lef_col;
		t[rt].rig_col=t[rt<<1|1].rig_col;
		t[rt].cnt=t[rt<<1].cnt+t[rt<<1|1].cnt+(t[rt<<1].rig_col!=t[rt<<1|1].lef_col);
	}
	void pushdown(int rt) {
		if(t[rt].tag!=-1) {
			t[rt<<1].tag=t[rt<<1].lef_col=t[rt<<1].rig_col=t[rt].tag;
			t[rt<<1|1].tag=t[rt<<1|1].lef_col=t[rt<<1|1].rig_col=t[rt].tag;
			t[rt<<1].cnt=t[rt<<1|1].cnt=0;
			t[rt].tag=-1;
		}
	}
	void build(int rt,int l,int r) {
		t[rt].tag=-1;
		if(l==r) {
			t[rt].lef_col=t[rt].rig_col=l,t[rt].cnt=0;
			return;
		}
		int mid=(l+r)>>1;
		build(rt<<1,l,mid),build(rt<<1|1,mid+1,r);
		pushup(rt);
	}
	void modify(int rt,int l,int r,int ml,int mr,int val) {
		if(l>mr||r<ml)return;
		if(ml<=l&&r<=mr) {
			t[rt].tag=t[rt].lef_col=t[rt].rig_col=val;
			t[rt].cnt=0;
			return;
		}
		pushdown(rt);
		int mid=(l+r)>>1;
		modify(rt<<1,l,mid,ml,mr,val);
		modify(rt<<1|1,mid+1,r,ml,mr,val);
		pushup(rt);
	}
	int query(int rt,int l,int r,int ql,int qr) {
		if(l>qr||r<ql)return 0;
		if(ql<=l&&r<=qr)return t[rt].cnt;
		pushdown(rt);
		int mid=(l+r)>>1;
		int ret=query(rt<<1,l,mid,ql,qr)+query(rt<<1|1,mid+1,r,ql,qr);
		if(ql<=mid&&mid+1<=qr)
			ret+=(t[rt<<1].rig_col!=t[rt<<1|1].lef_col);
		return ret;
	}
	int query(int rt,int l,int r,int pos) {
		if(l==r)return t[rt].lef_col;
		pushdown(rt);
		int mid=(l+r)>>1;
		if(pos<=mid)return query(rt<<1,l,mid,pos);
		else return query(rt<<1|1,mid+1,r,pos);
	}
};

int siz[Maxn+5],dep[Maxn+5],son[Maxn+5],fa[Maxn+5];
int top[Maxn+5],tid[Maxn+5],rnk[Maxn+5],dcnt;
void dfs(int u,int pre,int depth) {
	dep[u]=depth,fa[u]=pre,siz[u]=1;
	for(Edge *p=G[u];p!=NULL;p=p->nxt) {
		int v=p->to;
		if(v==pre)continue;
		dfs(v,u,depth+1);
		siz[u]+=siz[v];
		if(son[u]==-1||siz[v]>siz[son[u]])
			son[u]=v;
	}
}
void dfs(int u,int tp) {
	top[u]=tp,tid[u]=++dcnt,rnk[dcnt]=u;
	if(son[u]==-1)return;
	dfs(son[u],tp);
	for(Edge *p=G[u];p!=NULL;p=p->nxt) {
		int v=p->to;
		if(v==fa[u]||v==son[u])
			continue;
		dfs(v,v);
	}
}

SegmentTree tree;
void Modify(int u,int v,int col) {
	while(top[u]!=top[v]) {
		if(dep[top[u]]<dep[top[v]])
			swap(u,v);
		tree.modify(1,1,N,tid[top[u]],tid[u],col);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	tree.modify(1,1,N,tid[v],tid[u],col);
}
int Query(int u,int v) {
	int ret=0;
	while(top[u]!=top[v]) {
		if(dep[top[u]]<dep[top[v]])
			swap(u,v);
		ret+=tree.query(1,1,N,tid[top[u]],tid[u]);
		u=top[u];
		ret+=(tree.query(1,1,N,tid[u])!=tree.query(1,1,N,tid[fa[u]]));
		u=fa[u];
	}
	if(dep[u]<dep[v])swap(u,v);
	ret+=tree.query(1,1,N,tid[v],tid[u]);
	return ret;
}

int main() {
	freopen("colour.in","r",stdin);
	freopen("colour.out","w",stdout);
	scanf("%d",&N);
	for(int i=1;i<N;i++) {
		int u,v;
		scanf("%d %d",&u,&v);
		addedge(u,v),addedge(v,u);
	}
	memset(son,-1,sizeof son);
	dfs(1,0,1);
	dfs(1,1);
	tree.build(1,1,N);
	int Q;
	scanf("%d",&Q);
	for(int i=1;i<=Q;i++) {
		int op,u,v;
		scanf("%d %d %d",&op,&u,&v);
		if(op==1)Modify(u,v,i+N);
		else printf("%d\n",Query(u,v));
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值