cunzai_zsy0531

关注我

2021暑假校队集训

模拟赛

Day 1-qyc

T1 ARC059F

注意到 \(s\) 串具体是什么对答案没有影响,考虑先求出 \(m\) 步弄出长度为 \(n\) 的串的方案数,然后直接乘上 \(2^n\) 的逆元即可。

点击查看代码
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N=5000+13,mod=1e9+7;
inline int qpow(int a,int k){int s=1;for(;k;k>>=1,a=(ll)a*a%mod)if(k&1)s=(ll)s*a%mod;return s;}
int n,m;
char useless[N];
int f[N][N<<1];
int main(){
	freopen("typewriter.in","r",stdin);
	freopen("typewriter.out","w",stdout);
	scanf("%d%d",&n,&m);
	scanf("%s",useless+1);
	f[0][0]=1;
	for(int i=1;i<=m;++i){
		for(int j=0;j<=n+m/2;++j){
			if(j) f[i][j]+=f[i-1][j-1]*2%mod,f[i][j]%=mod;
			else f[i][j]+=f[i-1][j],f[i][j]%=mod;
			f[i][j]+=f[i-1][j+1],f[i][j]%=mod;
		}
	}
	printf("%d\n",(ll)f[m][n]*qpow(qpow(2,n),mod-2)%mod);
	return 0;
}

T2 CF17E

这个题原题的数据不随机,考试的时候保证随机了,可以证明回文串个数期望非常少,于是就爆力直接通过。

正解做法是,考虑先用Manacher求出以每个位置作为中心的最长回文子串,把相交化成总的减去不交的,不交的方案数可以用前后缀和快速计算,复杂度 \(O(n)\)

正解代码待补。

T3 CF1439C

由于那个单调性的原因,\(1\) 操作只需要二分加区间赋值,\(2\) 操作往后面取的段数最多也是 \(O(\log v)\) 的,所以可以直接使用线段树二分解决问题,复杂度 \(O(n\log n\log v)\)

我写的是二分套线段树,多一个 \(\log\),亓神数据能过去但是cf过不去。正解代码待补。

T4 ARC058E

注意到 \(x+y+z\) 很小,考虑状压。另外,注意到三连击区间的右端点有一个共同特性,就是后缀和中都有 \(z,y+z,x+y+z\) 这三个值。

考虑一个dp:设 \(dp(i,S)\) 表示长度为 \(i\) 的序列,后缀和集合为 \(S\) 的方案数,状态数是 \(O(2^{x+y+z}n)\),使用刷表总复杂度 \(O(2^{x+y+z}nv)\)

也可以在ACAM上跑dp,复杂度似乎是 \(O(2^{x+y+z}n^2)\),也可以通过。

正解代码待补。

Day 2-ljc

T1 CF1479B2

考虑一个贪心,假设两个末尾元素为 \(x,y\),进来一个 \(z\),如果 \(z\)\(x,y\) 其中一个相等就放到那个序列里,否则放到 \(x,y\) 中离后面一个元素更近的序列里。看起来很对,然后就过了。

我的做法是能放则放,具体来说是维护一个栈表示当前后面待放的一些数,如果进来一个在栈中已经出现过的数,那么就把这个数放在最后,剩下的数放在另一个子序列里,这样看起来比较优,和上边的做法本质上应该是一样的。

点击查看代码
#include<iostream>
#include<cstdio>
#include<stack>
using namespace std;
const int N=1e5+13;
bool vis[N];
stack<int> s;
int main(){
	freopen("unnamedone.in","r",stdin);
	freopen("unnamedone.out","w",stdout); 
	int n,x,pre=0,cnt=0;
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%d",&x);
		if(x==pre){++cnt;continue;}
		pre=x;
		if(!vis[x]) s.push(x),vis[x]=1;
		else{
			++cnt;int tmp=s.top();
			while(!s.empty()) vis[s.top()]=0,s.pop();
			s.push(tmp),vis[tmp]=1;
			s.push(x),vis[x]=1;
		}
	}
	printf("%d\n",n-cnt);
	return 0;
}

T2 P3505

考虑把和 \(1\) 相连的放在一起、和 \(2\) 相连的放在一起,其他点放在一起,这三个子图全部连成完全图,这样看起来很优。

考虑那个其他点的集合,由于 \(5\) 条边的限制,所以这个集合的每一个点都只能向上边两种的其中一种连边。如果已经有边连了,那没办法,否则就选择 \(siz\) 更大的那一边连边即可。可以证明没有方案能比这更大。

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=40000+13,M=1e6+13;
struct Edge{int v,nxt;}e[M<<1];
int n,m,h[N],tot;
int vis[N];
inline void add_edge(int u,int v){e[++tot]=(Edge){v,h[u]};h[u]=tot;}
inline int cnt_edge(int x){return x*(x-1)/2;}
int main(){
	freopen("unnamedtwo.in","r",stdin);
	freopen("unnamedtwo.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;++i) scanf("%d%d",&u,&v),add_edge(u,v),add_edge(v,u);
	int cnt1=0,cnt2=0;
	for(int i=h[1];i;i=e[i].nxt) vis[e[i].v]=1,++cnt1;
	for(int i=h[2];i;i=e[i].nxt) vis[e[i].v]=2,++cnt2;
	int ans=cnt_edge(cnt1+1)+cnt_edge(cnt2+1),cnt=0;
	for(int i=3;i<=n;++i){
		if(vis[i]) continue;
		++cnt;int flag=0;
		for(int j=h[i];j;j=e[j].nxt){
			int v=e[j].v;
			if(vis[v]) flag=vis[v];
		}
		if(!flag) ans+=max(cnt1,cnt2);
		else if(flag==1) ans+=cnt1;
		else ans+=cnt2;
	}
	ans+=cnt_edge(cnt);
	printf("%d\n",ans-m);
	return 0;
}

T3 CF883B

先正反两遍求出每个点权值的范围 \([l_u,r_u]\),考虑从小到大对每个 \(i\) 来确定点权为 \(i\) 的点。

当处理到 \(i\) 时,\([1,i-1]\) 已经处理完了,我们只需要处理还没被放点权的、\(l_u\leq i\) 的点。如果这些点中存在 \(r_u=i\) 的点,那么这些点的点权都只能是 \(i\),因为后面他们就不能放了;如果没有 \(r_u=i\) 的点,那么根据上面这个过程,找一个 \(r_u>i\) 的最小 \(r_u\) 的点 \(u\) 来放 \(i\) 一定不会更劣。如果处理到某个 \(i\) 时没有 \(l_u\leq i\)\(r_u\geq i\) 的点,那么就无解。

正解代码待补。

T4 CF1416E

本次集训最难题(李神好毒瘤)。

首先把问题转化成最大化相邻两个数相等的对数。考虑一个dp:令 \(c_i=a_{2i-1},d_i=a_{2i}\),设 \(f(i,j)\) 表示考虑了 \(b\) 的前 \(i\) 个数,此时 \(d_i=j\) 的最大相邻相等对数。则有:

\[f_{i,j}=\max_{k=1}^{a_{i-1}-1}\{f_{i-1,k}+[a_i-j=k]+[2j=b_i]\} \]

由于上面那个式子后面加的最多为 \(2\),说明对于同一个 \(i\) 来说,\(f_{i,j}\) 的最大值和最小值的差最多为 \(2\)。而且,比最小值差 \(2\) 的只有可能在 \(j=b_i\) 处取到。考虑维护 \(f\) 的最小值和比最小值大 \(1\) 的集合,以及比最小值大 \(2\) 的位置是否存在。

后面就是一大堆分类讨论转移,用 set 维护线段可以做到 \(O(n\log n)\)。正解代码待补。

Day 3-zsy

我的题,直接放题解了。

T1 P2668

注意到牌数其实不小,所以直接爆搜显然不是一个好的选择。考虑贪心地爆搜+最优化剪枝,贪心策略就是先顺子再从四个三个两个的出,最后是单牌,随机数据能过。

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
using namespace std;
const int INF=0x7fffffff;
int a[20],n,ans;
bool check(){
	for(int i=1;i<=14;++i)
		if(a[i]) return 0;
	return 1;
}
void dfs(int d){
	if(d>=ans) return;
	for(int i=1;i<=6;++i){
		switch(i){
			case 1:{
				for(int l=3;l<=10;++l){
					if(!a[l]) continue;
					int r=l+1;
					while(r<=13&&a[r]) ++r;--r;
					if(r==13&&a[1]) r++;
					if(r-l+1<5) continue;
					for(int j=l+4;j<=r;++j){
						for(int k=l;k<=j;++k)
							if(k<=13) a[k]--;
							else a[1]--;
						dfs(d+1);
						for(int k=l;k<=j;++k)
							if(k<=13) a[k]++;
							else a[1]++;
					}
				}
			}
			case 2:{
				for(int l=3;l<=12;++l){
					if(a[l]<2) continue;
					int r=l+1;
					while(r<=13&&a[r]>=2) ++r;--r;
					if(r==13&&a[1]>=2) ++r;
					if(r-l+1<3) continue;
					for(int j=l+2;j<=r;++j){
						for(int k=l;k<=j;++k)
							if(k<=13) a[k]-=2;
							else a[1]-=2;
						dfs(d+1);
						for(int k=l;k<=j;++k)
							if(k<=13) a[k]+=2;
							else a[1]+=2;
					}
				}
				break;
			}
			case 3:{
				for(int l=3;l<=13;++l){
					if(a[l]<3) continue;
					int r=l+1;
					while(r<=13&&a[r]>=3) ++r;--r;
					if(r-l+1<2) continue;
					for(int j=l+1;j<=r;++j){
						for(int k=l;k<=j;++k)
							if(k<=13) a[k]-=3;
							else a[1]-=3;
						dfs(d+1);
						for(int k=l;k<=j;++k)
							if(k<=13) a[k]+=3;
							else a[1]+=3;
					}
				}
				break;
			}
			case 4:{
				for(int i=1;i<=13;++i){
					if(a[i]<4) continue;
					a[i]=0;
					for(int j=1;j<=14;++j){
						if(!a[j]) continue;
						a[j]--;
						for(int k=1;k<=14;++k){
							if(a[k]){
								a[k]--;
								dfs(d+1);
								a[k]++;
							}
						}
						a[j]++;
					}
					for(int j=1;j<=13;++j){
						if(a[j]<2) continue;
						a[j]-=2;
						for(int k=1;k<=13;++k){
							if(a[k]>=2){
								a[k]-=2;
								dfs(d+1);
								a[k]+=2;
							}
						}
						a[j]+=2;
					}
					a[i]=4;
				}
				break;
			}
			case 5:{
				for(int i=1;i<=13;++i){
					if(a[i]>=3){
						a[i]-=3;
						for(int j=1;j<=13;++j){
							if(i==j) continue;
							if(a[j]>=2){
								a[j]-=2;
								dfs(d+1);
								a[j]+=2;
							}
						}
						a[i]+=3;
					}
				}
				break;
			}
			case 6:{
				for(int i=1;i<=13;++i){
					if(a[i]>=3){
						a[i]-=3;
						for(int j=1;j<=14;++j){
							if(i==j) continue;
							if(a[j]>=1){
								a[j]--;
								dfs(d+1);
								a[j]++;
							}
						}
						a[i]+=3;
					}
				}
				break;
			}
		}
	}
	for(int i=1;i<=14;++i)
		if(a[i]) ++d;
	ans=min(ans,d);
}
int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	int T;
	scanf("%d%d",&T,&n);
	while(T--){
		ans=INF;
		memset(a,0,sizeof(a));
		for(int i=1,x,useless;i<=n;++i){
			char s[3];
			scanf("%s",s);
			if(strlen(s)==1) x=s[0]-'0';
			else x=(s[0]-'0')*10+s[1]-'0';
			scanf("%d",&useless);
			if(!x) a[14]++;
			else a[x]++;
		}
		dfs(0);
		printf("%d\n",ans);
	}
	return 0;
}

T2 CF1547G

首先要注意这个题的自环。

先把整个图缩强连通分量,在缩点后的DAG上跑拓扑排序,判一下强连通分量 \(siz>1\) 的(它和后面肯定都是 \(-1\)),判一下自环。剩下的 \(1\)\(2\) 就简单dp就行了。

缩点我用的 kosaraju 算法。

点击查看代码
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#define pb push_back
#define mp make_pair
using namespace std;
typedef pair<int,int> pii;
const int N=4e5+13;
struct Edge{int v,nxt;}e[N];
int n,m,uu[N],vv[N],s[N],top,c[N],cnt_scc,h[N],tot,ind[N],ans[N],answer[N];
vector<int> e1[N],e2[N],e3[N],scc[N];
bool zihuan[N],have[N],vis[N],novis[N];
inline void clear(){
	tot=0;
	for(int i=1;i<=n;++i) zihuan[i]=have[i]=h[i]=ind[i]=ans[i]=0;
	for(int i=1;i<=n;++i) e1[i].clear(),e2[i].clear(),e3[i].clear();
}
void dfs1(int u){
	vis[u]=1;
	for(auto v:e1[u])
		if(!vis[v]) dfs1(v);
	s[++top]=u;
}
void dfs2(int u,int num){
	c[u]=num,scc[num].pb(u);
	for(auto v:e2[u])
		if(!c[v]) dfs2(v,num);
}
inline void kosaraju(){
	for(int i=1;i<=n;++i) vis[i]=c[i]=0,scc[i].clear();
	top=cnt_scc=0;
	for(int i=1;i<=n;++i)
		if(!vis[i]) dfs1(i);
	for(int i=n;i;--i){
		if(c[s[i]]) continue;
		dfs2(s[i],++cnt_scc);
	}
}
inline void bfs(int st){
	for(int i=1;i<=cnt_scc;++i) vis[i]=0;
	queue<int> q;while(!q.empty()) q.pop();
	q.push(st);vis[st]=1;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(auto v:e3[u])
			if(!vis[v]) vis[v]=1,q.push(v);
	}
	for(int i=1;i<=cnt_scc;++i) novis[i]=!vis[i];
}
inline void add_edge(int u,int v){e[++tot]=(Edge){v,h[u]};h[u]=tot;ind[v]++;}
inline void topsort(){
	queue<pii> q;while(!q.empty()) q.pop();
	if(have[c[1]]) ans[c[1]]=-1;
	else ans[c[1]]=1;
	q.push(mp(c[1],ans[c[1]]));
	while(!q.empty()){
		int u=q.front().first,t=q.front().second;q.pop();
		for(int i=h[u];i;i=e[i].nxt){
			int v=e[i].v;ind[v]--;
			if(ans[v]!=-1){
				if(t!=1) ans[v]=t;
				else if(ans[v]!=-1&&ans[v]!=2) ++ans[v];	
			}
			if(!ind[v]){
				if(have[v]) ans[v]=-1;
				q.push(mp(v,ans[v]));
			}
		}
	}
}
inline void solve(){
	scanf("%d%d",&n,&m);
	clear();
	for(int i=1;i<=m;++i){
		scanf("%d%d",&uu[i],&vv[i]);
		int u=uu[i],v=vv[i];
		if(u==v) zihuan[u]=1;
		e1[u].pb(v),e2[v].pb(u);
	}
	kosaraju();
	for(int i=1;i<=n;++i)
		if(zihuan[i]) have[c[i]]=1;
	for(int i=1;i<=cnt_scc;++i)
		if(scc[i].size()>1) have[i]=1;
	for(int i=1;i<=m;++i)
		if(c[uu[i]]!=c[vv[i]]) e3[c[uu[i]]].pb(c[vv[i]]);
	bfs(c[1]);
	for(int i=1;i<=m;++i)
		if(c[uu[i]]!=c[vv[i]]&&!novis[c[uu[i]]]&&!novis[c[vv[i]]]) add_edge(c[uu[i]],c[vv[i]]);
	topsort();
	for(int i=1;i<=cnt_scc;++i)
		for(auto u:scc[i]) answer[u]=ans[i];
	for(int i=1;i<=n;++i) printf("%d ",answer[i]);puts("");
}
int main(){
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
	int T;scanf("%d",&T);
	while(T--) solve();
	return 0;
}

T3 P5024

算法一:考虑动态dp求最大权独立集,把单点修改转化成把点权改为正/负无穷就能转化为板子 P4719 了。

算法二:

考虑一个朴素的dp做最大权独立集,每次询问重新做一遍,复杂度 \(O(nm)\)。期望得分 \(44\)

对于树高 \(\leq 100\) 的情况,考虑记录一个“整棵树除去 \(i\) 子树的最小费用”,每次只需要对两个节点之间的链爆力跑dp即可。期望得分 \(52\)

用倍增优化上述过程,一条链上的转移可以倍增进行,预处理出每个点到 \(2^j\) 级祖先,链头链尾选或不选时链上的dp值,每次询问的时候爆力往上跳即可。复杂度 \(O((n+m)\log n)\),期望得分 \(100\)。正解代码待补。

T4 P3622

注意到 \(5\) 很小,可以直接状压,每一位用 \(0/1\) 表示是否移走。

定义 \(num_{i,s}\) 表示开头为 \(i\) 的五个景观状态为 \(s\) 时,高兴的人个数。

\(dp_{i,s}\) 表示前 \(i\) 个位置为开头且第 \(i\) 个位置开头后面五个状态为 \(s\) 的时候,最多高兴的人个数,则有

\[dp_{i,s}=\max(dp_{i-1,(s\&15)<<1},dp_{i-1,(s\&15)<<1|1})+num_{i,s} \]

环的话,最后处理注意一下细节即可。

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=5e4+4,M=44;
int n,m,num[N][M],f[2][M],ans;
int main(){
	freopen("d.in","r",stdin);
	freopen("d.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1,a,b,E,p1,p2,t;i<=m;++i){
		scanf("%d%d%d",&E,&p1,&p2);
		a=0,b=0;
		while(p1--){
			scanf("%d",&t);
			t=(t-E+n)%n;
			a|=(1<<t);
		}
		while(p2--){
			scanf("%d",&t);
			t=(t-E+n)%n;
			b|=(1<<t);
		}
		for(int j=0;j<32;++j)
			if((j&b)||((~j)&a)) num[E][j]++;
	}
	for(int i=0;i<32;++i){
		memset(f,0xcf,sizeof(f));
		f[0][i]=0;
		bool v=1;
		for(int j=1;j<=n;++j,v^=1){
			for(int s=0;s<32;++s){
				f[v][s]=max(f[v^1][(s&15)<<1],f[v^1][(s&15)<<1|1])+num[j][s];
			}
		}
		ans=max(ans,f[n&1][i]);
	}
	printf("%d\n",ans);
	return 0;
}

Day 4-zrz

T1 P4838

\(f_{i,j}\) 表示处理了前 \(i\) 位,最后有连续 \(j\)\(0\) 的方案数,发现这个东西可以矩阵优化,直接矩阵快速幂即可,复杂度 \(O(k^3\log n)\)

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=20+13,mod=1e8+7;
ll n;int m;
struct Matrix{
	int d[N][N];
	Matrix(){memset(d,0,sizeof d);}
	Matrix operator *(Matrix A){
		Matrix Ans;
		for(int i=1;i<=m;++i)
			for(int j=1;j<=m;++j)
				for(int k=1;k<=m;++k)
					Ans.d[i][j]=(Ans.d[i][j]+(ll)d[i][k]*A.d[k][j]%mod)%mod;
		return Ans;
	}
};
Matrix qpow(Matrix A,ll k){Matrix S=A;--k;for(;k;k>>=1,A=A*A)if(k&1)S=S*A;return S;}
inline void solve(){
	scanf("%lld%d",&n,&m);
	Matrix A;
	for(int i=1;i<=m;++i) A.d[1][i]=1;
	for(int i=2;i<=m;++i) A.d[i][i-1]=1;
	Matrix Ans=qpow(A,n);
	int res=0;
	for(int i=1;i<=m;++i) res=(res+Ans.d[i][1])%mod;
	printf("%d\n",res);
}
int main(){
	freopen("these.in","r",stdin);
	freopen("these.out","w",stdout);
	int T;scanf("%d",&T);
	while(T--) solve();
	return 0;
}

T2 P4318

首先,这个“第 \(k\) 个”看起来很难求,考虑二分答案,计算 \(mid\) 以内的个数即可。个数的计算需要一个容斥,大概的式子是

\[ans=\sum_{p\in prime,p\leq n}\frac{mid}{p^2}+\sum_{q\in prime,q\leq n}\frac{mid}{q^2}-\sum_{p,q\in prime,p,q\leq n}\frac{mid}{p^2q^2}+\ldots-\ldots \]

预处理出所有答案范围内的这样的只有平方质因子的数,统计答案即可。注意到 \(k\) 范围内这样的数只有 \(O(\sqrt k)\) 个,所以总复杂度 \(O(\sqrt k\log k)\)

这题也可以把刚才那个式子化成 \(\mu^2\) 前缀和,但是更麻烦一点,复杂度不变常数稍大。

点击查看代码
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
bool b[100005];
int prm[100005],pcnt,cnt;
ll a[100005],lim,tot;
int k;
inline void init(){
	for(int i=2;i<=100000;++i){
		if(!b[i]) prm[++pcnt]=i;
		for(int j=1;j<=pcnt&&i*prm[j]<=100000;++j){
			b[i*prm[j]]=1;
			if(i%prm[j]==0) break;
		}
	}
	for(int i=1;i<=pcnt;++i) a[i]=1ll*prm[i]*prm[i];
}
void dfs(int k,int flag,ll sum){
	tot+=flag*(lim/sum);
	if(k==cnt+1) return;
	for(int i=k;i<=cnt;++i){
		if(sum<=lim/a[i]) dfs(i+1,flag*-1,sum*a[i]);
		else break;
	}
}
inline bool check(ll x){
	cnt=pcnt;
	while(a[cnt]>x) --cnt;
	tot=0,lim=x;
	for(int i=1;i<=cnt;++i) dfs(i+1,1,a[i]);
	return x-tot>=k;
}
inline void solve(){
	scanf("%d",&k);
	ll l=k,r=10ll*k,mid;
	while(l<r){
		mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%lld\n",l);
}
int main(){
	freopen("all.in","r",stdin);
	freopen("all.out","w",stdout);
	init();
	int T;scanf("%d",&T);
	while(T--) solve();
	return 0;
}

T3 CF45G

考虑哥德巴赫猜想:任何一个大于 \(4\) 的偶数都可以拆成两个奇质数的和,这个猜想在偶数很小的时候都是成立的。

分类讨论之后,对于偶数的情况,可以先找出两个质数,再用 \(1\sim n\) 的这些数拼出这两个质数即可。可以证明一定有方案可以拼出来。

点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
inline bool isprime(int x){
	for(int i=2;i*i<=x;++i)
		if(x%i==0) return 0;
	return 1;
}
const int N=6000+13;
bool vis[N];
int main(){
	freopen("tick.in","r",stdin);
	freopen("tick.out","w",stdout);
	int n;scanf("%d",&n);
	int tmp=n*(n+1)/2;
	if(tmp&1){
		if(isprime(tmp)){
			for(int i=1;i<=n;++i) printf("1 ");
			return puts(""),0;
		}
		if(isprime(tmp-2)){
			printf("1 2 ");for(int i=3;i<=n;++i) printf("1 ");
			return puts(""),0;
		}
		tmp-=3;
		int k=0;
		for(int i=2;i<=tmp/2;++i)
			if(isprime(i)&&isprime(tmp-i)){k=i;break;}
		int p=n;
		while(k>p) vis[p]=1,k-=p,--p;
		if(k) vis[k]=1;
		if(vis[3]==1) vis[1]=vis[2]=1;
		for(int i=1;i<=n;++i){
			if(i==3) printf("3 ");
			else printf("%d ",1+vis[i]);
		}
	}
	else{
		int k=0;
		for(int i=2;i<=tmp/2;++i)
			if(isprime(i)&&isprime(tmp-i)){k=i;break;}
		int p=n;
		while(k>p) vis[p]=1,k-=p,--p;
		if(k) vis[k]=1;
		for(int i=1;i<=n;++i) printf("%d ",1+vis[i]);
		puts("");
	}
	return 0;
}

这个题还有一个点在于,李神的代码只枚举了极少的情况,好像是说两个质数中一定有一个可以只被两个 \(1\sim n\) 的数相加得到。不知道这是因为数据水,还是可以证明这是对的。

T4 P3653

注意到 \(r-l\leq 10^5\),考虑用 \(10^6\) 以内的质数对这些数做类似埃氏筛的操作,复杂度应该是 \(O(10^5\cdot \log 10^6)\) 左右。

把那些 \(\mu=0\) 的数都去掉,剩下的数,对于大质数来说只有三种可能:\(P^2,P,PQ\)\(P^2\) 是好判的,直接判完全平方和质数即可,\(P\) 可以使用 Miller-Rabin 算法来快速判质数(判个 \(10\sim 30\) 次就行了),由于这 \(10^5\) 个数是连续的,所以不会有很多进入 Miller-Rabin 测试,可以通过此题。

正解代码待补。

posted @ 2022-04-22 16:45  cunzai_zsy0531  阅读(41)  评论(0编辑  收藏  举报