Problem A:
【题目描述】
对于给定的一个正整数\(n\), 判断\(n\)是否能分成若干个正整数之和 (可以重复) ,
其中每个正整数都能表示成两个质数乘积。
【输入描述】
第一行一个正整数 \(q\),表示询问组数。
接下来 \(q\) 行,每行一个正整数 \(n\),表示询问。
【输出描述】
\(q\) 行,每行一个正整数,为 0 或 1。0 表示不能,1 表示能。
【输入样例】
5
1
4
5
21
25
【输出样例】
0
1
0
1
1
【样例解释】
4=22
21=6+15=23+35
25=6+9+10=23+33+25
25=4+4+4+4+9=22+22+22+22+3*3
【数据范围】
30%的数据满足:\(q<=20,n<=20\)
60%的数据满足:\(q<=10000,n<=5000\)
100%的数据满足:\(q<=10^5,n<=10^18\)
【思路】
题意:
\(q\)次询问:能否用形如 $x = p1 * p2 \((\)p1\(,\)p2\(均为质数且可以相等) 的数相加得到给定的数\)n\(。 题解: 当\)n <= 20 \(时,只有1,2,3,5,7,11无解,其余均有解。 当\)n > 20 \(时,因为\)n = (n-4) + 4 = (n-4) + 2 * 2\(,而\)(n-4)\(这个数\)>=16\(,是一定有解的。 所以,我们证明了对于任意的正整数\)n\(, 只有\)n = 1,2,3,5,7,11\(时无解,其余均有解。 那么我们只需要在每组数据中判断一下\)n$是否等于\(1,2,3,5,7,11\)中的任意一个即可。
复杂度\(O(q)\).
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int q;
LL n;
int rd(){
int re=0,f=1;char c=getchar();
while ((c<'0')||(c>'9')) {if (c=='-') f=-f;c=getchar();}
while ((c>='0')&&(c<='9')) {re=re*10+c-'0';c=getchar();}
return re*f;
}
int main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d",&q);
for (;q>0;--q){
scanf("%lld",&n);
if ((n>3ll)&&(n!=5ll)&&(n!=7ll)&&(n!=11ll)) puts("1");else puts("0");
}
return 0;
}
Problen B:
【题目描述】
有一个长度为 \(n\) 的自然数序列\(a\),要求将这个序列恰好分成至少\(m\)个连续子
段。 每个子段的价值为该子段的所有数的按位异或。要使所有子段的价值按位与
的结果最大,输出这个最大值
【输入描述】
第一行一个正整数 \(q\),表示询问组数。
接下来 $q $组输入,每组输入两行:
第一行两个正整数 \(n,m\)
第二行 \(n\) 个正整数,描述序列$ a $
【输出描述】
一行一个正整数,表示答案
【输入样例】
1
5 3
1 2 3 4 5
【输出样例】
1
【数据范围】
20%\(的数据:\)n<=20$
40%的数据:\(n<=100,ai<256\)
60%的数据:\(n<=100\)
100%的数据:\(1<=q<=12,1<=m<=n<=1000,0<=ai<2^30\)
【思路】
题意:
\(q\)组数据。
给你一个长为\(n\)的非负数序列\(a\)。
定义一个区间的权值为区间的异或和。
定义一个划分的权值为分出的区间的权值的按位与的值。
要求至少把序列分成m个非空字段,求满足条件的划分的最大权值。
题解:
因为异或运算具有交换律且(\(x\)异或\(x = 0\)),所以区间异或和可以由两个前缀异或和异或得到。
考虑从高位到低位确定答案的二进制位,然后考虑判断是否能分成至少\(m\)段。
如何判断是否能分成至少\(m\)段?
"能分成至少\(m\)段"与"最多能分成的段数\(>=m\)"等价。
令\(dp[i] = "a[1..i]\)这一段数最多能分成多少段"
转移就是直接枚举上一段的末尾\(j\),如果\([j+1,i]\)可以组成一段,那么就把\(dp[i] = max(dp[i],dp[j] + 1)\);
这样DP的复杂度是\(O(n^2)\)。
总复杂度就是\(O(q * log(值域) * DP) = O(30qn^2) ≈ 3.6 * 10^8\),因为常数较小可以过。
值得一提的是,直接\(O(N^3)DP\)是错误的。因为它不满足最优子结构。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int q,n,m,ans,x,s[1005],f[1005];
int rd(){
int re=0,f=1;char c=getchar();
while ((c<'0')||(c>'9')) {if (c=='-') f=-f;c=getchar();}
while ((c>='0')&&(c<='9')) {re=re*10+c-'0';c=getchar();}
return re*f;
}
int Max(int x,int y){
return ((x>y)?x:y);
}
int main(){
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
q=rd();
for (;q>0;--q){
n=rd();m=rd();
s[0]=0;
for (int i=1;i<=n;++i){
x=rd();s[i]=s[i-1]^x;
}
ans=0;
for (int i=29;i>=0;--i){
memset(f,0,sizeof(f));
f[0]=1;
for (int j=1;j<=n;++j){
for (int k=0;k<j;++k) if (f[k]>0){
x=s[j]^s[k];
if (((ans&x)>=ans)&&((x&(1<<i))>0))
f[j]=Max(f[k]+1,f[j]);
}
}
if (f[n]>m) ans|=(1<<i);
}
cout<<ans<<'\n';
}
return 0;
}
Problem C:
【题目描述】
定义一个排列 \(a\) 的价值为满足$|a[i]-i|<=1 $的 $i $的数量。
给出三个正整数 \(n,m,p\),求出长度为$ n \(且价值恰好为\) m$ 的排列的个数对 $p $取
模的结果
【输入描述】
第一行两个正整数 \(T,p,T\) 为数据组数,$p \(为模数。 接下来\) T \(行,每行两个正整数\) n,m$
【输出描述】
$ T \(行,每行一个非负数,表示答案 ### 【输入样例】 5 1887415157 3 1 3 2 3 3 50 10 1500 200 ### 【输出样例】 1 2 3 621655247 825984474 ### 【数据范围】 10%的数据:\)n<=10$
30%的数据:\(n<=15\)
50%的数据:\(n<=200\)
另有 10%的数据:\(m=1\)
另有 10%的数据:\(m=n-1\)
100%的数据:\(1<=T,n,m<=2000,2<=p<=10^12\)
【思路】
题意:
定义一个排列 $a \(的价值为满足\)|a[i]-i|<=1 $的 $i \(的数量。 给出三个正整数\) n,m,p\(,求出长度为\) n$ 且价值恰好为 \(m\) 的排列的个数对$ p$ 取模的结果。
\(T\)组询问,\(p\)事先给出。
题解:
因为\(n,m <= 2000,\)而且\(p\)是事先给出的,所以我们可以一次性预处理出\(n,m <= 2000\)的答案。
考虑一个长度为\(i\)的排列如何变成长度为\(i+1\)的排列。
一种情况是我在它末尾加入了一个数\(i+1\),另一种情况是我用\(i+1\)替换掉了原来排列中的一个数,然后把被换掉的数放到排列的末尾。
那么,这个排列权值的变化就是:
第一种情况:在它末尾加入了一个数\(i+1\),权值\(+1\)。
第二种情况:用\(i+1\)替换掉一个数,\(权值 += 加的贡献 - 换掉的数的贡献。\)
在\(DP\)当中,我们只需要考虑替换掉的数是否是\(i\),以及\(i\)是否在位置\(i/i-1\)即可。总共有\(5\)种本质不同的状态,分类讨论转移即可。
复杂度\(O(nm)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2005;
int q,n,m,u,v;
LL p,ans,f[N][N][5],x;
int rd(){
int re=0,f=1;char c=getchar();
while ((c<'0')||(c>'9')) {if (c=='-') f=-f;c=getchar();}
while ((c>='0')&&(c<='9')) {re=re*10+c-'0';c=getchar();}
return re*f;
}
int main(){
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
cin>>q>>p;
memset(f,0,sizeof(f));
f[1][1][0]=1ll;
f[2][2][0]=1ll;f[2][2][1]=1ll;
n=2000;
for (int i=2;i<n;++i)
for (int j=0;j<=n;++j){
for (int k=0;k<=4;++k){
x=f[i+1][j+1][0]+f[i][j][k];
x=(x<p)?x:(x-p);
f[i+1][j+1][0]=x;
u=j+((k%2)==0);
v=1+(k!=0);
x=f[i+1][u][v]+f[i][j][k];
x=(x<p)?x:(x-p);
f[i+1][u][v]=x;
}
if (f[i][j][0]>0ll){
f[i+1][j-1][4]=(f[i+1][j-1][4]+f[i][j][0]*(LL)(j-1))%p;
f[i+1][j][4]=(f[i+1][j][4]+f[i][j][0]*(LL)(i-j))%p;
}
if (f[i][j][1]>0ll){
x=f[i+1][j][3]+f[i][j][1];
x=(x<p)?x:(x-p);
f[i+1][j][3]=x;
f[i+1][j-1][4]=(f[i+1][j-1][4]+f[i][j][1]*(LL)(j-2))%p;
f[i+1][j][4]=(f[i+1][j][4]+f[i][j][1]*(LL)(i-j))%p;
}
if (f[i][j][2]>0ll){
x=f[i+1][j][3]+f[i][j][2];
x=(x<p)?x:(x-p);
f[i+1][j][3]=x;
f[i+1][j-1][4]=(f[i+1][j-1][4]+f[i][j][2]*(LL)(j-1))%p;
f[i+1][j][4]=(f[i+1][j][4]+f[i][j][2]*(LL)(i-j-1))%p;
}
if (f[i][j][3]>0ll){
x=f[i+1][j+1][3]+f[i][j][3];
x=(x<p)?x:(x-p);
f[i+1][j+1][3]=x;
f[i+1][j-1][4]=(f[i+1][j-1][4]+f[i][j][3]*(LL)(j-1))%p;
f[i+1][j][4]=(f[i+1][j][4]+f[i][j][3]*(LL)(i-j-1))%p;
}
if (f[i][j][4]>0ll){
x=f[i+1][j+1][3]+f[i][j][4];
x=(x<p)?x:(x-p);
f[i+1][j+1][3]=x;
if (j>0) f[i+1][j-1][4]=(f[i+1][j-1][4]+f[i][j][4]*(LL)(j))%p;
f[i+1][j][4]=(f[i+1][j][4]+f[i][j][4]*(LL)(i-j-2))%p;
}
}
for (;q>0;--q){
cin>>n>>m;
ans=(f[n][m][0]+f[n][m][1]+f[n][m][2]+f[n][m][3]+f[n][m][4])%p;
cout<<ans<<'\n';
}
return 0;
}