当前位置: 代码迷 >> 综合 >> NOIP 20190907 联考(未完成)
  详细解决方案

NOIP 20190907 联考(未完成)

热度:5   发布时间:2023-12-16 11:26:54.0

T1 四个质数的和

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

【输入格式】
第一行读入一个整数 T 表示数据组数。
接下来共 T 行,每行包含一个正整数 N。

【输出格式】
共 T 行,每行一个整数表示答案。

【输入样例】

2
9
10

【输出样例】

4
6

【数据范围】
对于 10%的数据,N≤10。
对于 40%的数据,N≤100。
对于 70%的数据,N≤1000。
对于 100%的数据,T≤10,N≤100000。

【题解】
这题思路其实是很好想的。
1.首先肯定要处理出N范围之内的质数
2.题目要求一个数要被分成四个质数,将它分为两组,如果一个数能被分为两个数a, b,且a,b均能由两个质数组成,那么将两边的方案数相乘即可得到方案数。

 例如91,8不行2,7不行3,6不行4,5,4=2+2,5=2+3或3+2(因为排列不同是不同的方案)那么就有2,2,5,3以及2,2,3,5两种方案,由1*2得到5,4这个的方案数其实是和4,5一样的

所以每次枚举只枚举到N/2,最后答案乘以2即可
还有一种特殊情况要考虑,就是当N为偶数且N/2可以由两个质数得到时,它的方案数是不需要乘2的

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int Max = 1e5+5;
int vis[Max]={
    }, prime[Max/2], num[Max]={
    }, N[15]={
    }, maxx = 0;
//vis判断质数,prime储存质数,num是数分解成两个质数的方案数
inline int dread()
{
    int x = 0, f = 1;char ch = getchar();while(ch<'0' || ch>'9'){
    if(ch == '-') f=-1;ch = getchar();}while(ch>='0'&&ch<='9'){
    x = (x<<1) + (x<<3) + (ch^48);ch = getchar();}return x*f;
}void getprime(){
    int cnt = 0;for(int i = 2; i <= maxx; i++){
    if(!vis[i]) {
    vis[i] = i; prime[++cnt] = i;}for(int j = 1; j <= cnt; j++){
    if(prime[j] > vis[i] || prime[j]*i > maxx) break;vis[ i*prime[j] ] = prime[j];}}//处理质数for(int i = 1; i <= cnt && prime[i] < maxx; i++)for(int j = 1; j <= cnt && prime[j] + prime[i] < maxx; j++)num[ prime[i]+prime[j] ]++;//如果可以由两个质数相加得到,这个数的方案数就加一
}ll work(int a){
    ll ans = 0; for(int i = 1; i <= a/2 && i != a-i; i++){
    if(!num[i] || !num[a-i]) continue;ans += num[i] * num[a-i];}//计算方案数ans *= 2;//将后半部分的情况加上去if(a & 1) return ans;else if(num[a/2]) ans += num[a/2] * num[a/2];//判断特殊情况return ans;
}int main()
{
    //freopen("plus.in","r",stdin);//freopen("plus.out","w",stdout);int T = dread();for(int i = 1; i <= T; i++){
    N[i] = dread();maxx = max(N[i], maxx);}//找到最大的N,这样说不定能少算一些质数,当然这其实没有什么用getprime();for(int i = 1; i <= T; i++) printf("%lld\n",work(N[i]));return 0;
}

T2 匹配最大异或

【题目描述】
假设给定了两个整数 m,n。有n 个互不相同的整数 x 1 , x 2 , . . . , x n x1,x2,...,xn x1,x2,...,xn 0 ≤ x i ≤ 0≤xi≤ 0xi 2 m ? 1 2^{m}-1 2m?1)。对于每一个
属于 0 到 2 m ? 1 2^{m}-1 2m?1 的 y,我们找到 p y py py使得 x p y xp_y xpy? 异或 y 有最大值。即对于任意的 i ≠ py, 有
y?xp_y >y? xi。(其中?表示二进制异或)。
现在我们把这个问题反过来。给定 m 和n,以及序列 p0,p1,…,p2^m-1,计算有多少个不
同序列 x1,x2,…,xn 可以通过上文描述的问题生成出序列 p。两个序列是不同的当且仅当存在
至少一个 i 使得两个序列中 xi 是不同的。
答案对 1000000007( 1 0 9 10^{9} 109+7)取模。

【输入格式】
第一行两个用空格隔开的整数 m,n。其中 2 m 2^{m} 2m是p 序列的长度,n 是 x 序列的长度。
之后 2 m 2^{m} 2m 行,每行一个整数,表示 p 序列。保证 1 到n 中的每一个数在序列 p 中都至少出现一次。

【输出格式】
输出一行一个整数表示答案。

【输入输出样例】
样例输入 1:

3 6
1
1
2
2
3
4
5
6

样例输出 1:

4

样例输入 2:

2 3
1
2
1
3

样例输出2:

0

样例输入3:

3 8
1
2
3
4
5
6
7
8

样例输出3:

1

【数据范围】
对于 30%的数据:m≤3,n≤4
另外 10%的数据:m=0
另外 10%的数据:n=1
另外 10%的数据:pi=i, 2 m 2^{m} 2m=n
对于 100%的数据:0≤m≤16,1≤pi≤n,1≤n≤ 2 m 2^{m} 2m

【题解】
考试时毫无头绪……

首先转化一下题目的意思
有A数组,长度为 2 m 2^{m} 2m,从0到 2 m ? 1 2^{m}-1 2m?1
有X数组,长度为n
有P数组,长度为 2 m 2^{m} 2m,为 对应A数组中的数 与 每一个X数组中的数 异或 得到的最大值
给A数组和P数组,求X数组的方案数( 0 ≤ x i ≤ 0≤xi≤ 0xi 2 m ? 1 2^{m}-1 2m?1

看到 2 m 2^{m} 2m,就想到了分治的方法
l l l, m i d mid mid, r r r
下面分为三类情况讨论
1.
l l l m i d mid mid中的所有数的二进制第一位都是0(或者 m i d mid mid r r r中的所有数的二进制第一位都是1)
(因为A数组是个递增的等差数列)
那么无论X数组中的数最高位是什么,对它们的影响都是同步的,可以忽略最高位,再往下做
同时如果忽略了最高位,那么前半部分与后半部分必然相同

举个例子:
m=3
000 001 010 011 100 101 110 111
分治后分为两块
000 001 010 011
100 101 110 111
去掉最高位两组相等为00 01 10 11

2
l l l m i d mid mid中的所有数的二进制第一位有0有1(或者 m i d mid mid l l l中的所有数的二进制第一位有0有1)
继续分治下去
3
l l l m i d mid mid中的所有数的二进制第一位是0 且 m i d mid mid l l l中的所有数的二进制第一位是1
但是存在 l < = a 1 < = m i d l<=a1<=mid l<=a1<=mid m i d < = a 2 < = r mid<=a2<=r mid<=a2<=r使得 P [ a 1 ] = P [ a 2 ] P[a1]=P[a2] P[a1]=P[a2],那么这组数据就不存在解
这个可以通过手模证明,证明略

然后在判断时,对方案数进行处理就可以了

【代码】

#include<bits/stdc++.h>
#define ll long long
using namespace std;const int mod = 1e9 + 7;
const int MAX = 1 << 16;
int m, n, p[MAX];
bool v[MAX];inline int read(){
    int x = 0, f = 1;char ch = getchar();while(ch<'0' || ch>'9'){
    if(ch == '-') f = -1;ch = getchar();}while(ch>='0' && ch<='9'){
    x = (x<<1) + (x<<3) + (ch^48);ch = getchar();}return x*f;
}inline ll solve(int l, int r) {
    if(l == r) return 1;int mid = (l + r) / 2;ll ans = 0;bool flag = 1;for(int i = l; i <= mid; i++)if(p[i] != p[mid + i - l + 1]) {
    flag = 0; break;}//判断是否满足情况1if(flag){
    ans += 2 * solve(l, mid); ans %= mod;}//处理方案数,因为满足情况1,左右两块完全相等,方案数自然也相等flag = 1;for(int i = l; i <= mid; i++) v[p[i]] = 1;for(int i = mid + 1; i <= r; ++i) if(v[p[i]]) {
    flag = 0; break;}//判断是否满足情况3 for(int i = l; i <= mid; i++) v[p[i]] = 0;if(flag){
    ans += solve(l, mid) * solve(mid + 1, r);ans %= mod;} //如果不满足情况3,方案数自然由左边的方案数乘上右边的方案数return ans;
}int main() {
    freopen("match.in", "r", stdin);freopen("match.out", "w", stdout);m = read();n = read();for(int i = 1; i <= 1 << m; i++) p[i] = read();printf("%lld\n", solve(1, 1 << m));
}

T3染色相邻的边

【题目描述】
给定一个 N 个点的树,点的标号从 1 到 N。
一条树上点 a 到点 b 的简单路径 P 是一个 k 个点的序列(a=P1,P2,…,Pk=b),相邻的
两个点之间有边连接且任何一个点至多在序列中出现一次。注意 a 可能和 b 是相等的。
简单路径上的边就是连接序列上相邻两个点的边。
一条简单路径的邻边是只有一个端点在简单路径上的边。
树上的每条边是黑色的或者白色的。最开始所有的边都是黑色的。有 Q 次操作,有
两种操作类型。
0计算 a 到 b 的简单路径上有多少条边是黑色的。
1将简单路径 a 到 b 上的边全部设置成白色的。将简单路径 a 到 b 上的邻边设置成
黑色的。

【输入格式】
第一行一个整数 N(1≤N≤200000)。
NOIP2019 多校联测-提高组
接下来 N-1 行,每行两个整数 ai,bi,表示一条树边。保证读入的是一棵树。
接下来一行一个整数 Q(1≤Q≤300000)。
接下来 Q 行,每行三个整数 ti, ai, bi。其中 ti表示操作类型。

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

【样例输入】

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

【样例输出】

2322

【数据范围】
对于 5%的数据:N=1
对于 20%的数据:N≤200
对于 30%的数据:N≤2000
另外 20%的数据:树的形态是一条链
另外 30%的数据:操作 1 中 ai=bi,且 ai是随机生成的。
对于 100%的数据:1≤N≤200000,1≤Q≤300000

博主蒟蒻还没有学树链剖分……没有代码题解……
【官方题解】
5 分:直接输出 0 即可。
20 分:O(N^3)模拟操作。
10 分:O(N^2)模拟。
另外 20 分(一条链):修改操作为单点修改,线段树维护。
另外 30 分(单点修改数据随机):暴力枚举边修改,树链剖分后用线段树维护。
100 分:
一条边为黑色的充要条件,是这条边的两个端点被不同的修改操作路径覆盖过。
修改操作给树边打上修改的时间,查询的时候相当于查询路径上有多少段不同的颜色,树链
剖分后用线段树维护即可。
O(Nlog^2N)