2023.11.14测试

\[\text{NOIP模拟赛-2023.11.14} \]

T1 简单的题

给三个数 \(n,G,L\),要求从 \(1\dots n\) 中选出一个非空子集使 \(\gcd=G\)\(\operatorname{lcm}=L\)。问方案数。同时有若干询问,给定 \(a_i\),求在包含 \(a_i\) 的前提下的方案数。

\(n,G,L\leq 10^8\)\(Q\leq 5000\)

\(L\) 所有因数,\(G\) 所有倍数取出来,记为 \(a_1,\dots,a_n\)\(n\) 的规模 \(\leq 800\)。把它们写作 \(Gd_1,Gd_2,\dots,Gd_n\) 的形式,现在变成从 \(d\) 里面选数,记选出的为 \(x_1,x_2\dots,x_m\),则 \(\gcd(x_1,\dots ,x_m)=1\)\(\operatorname{lcm}(x_1,\dots,x_m)=\dfrac{L}{G}\)。分解质因数变成每一位因数的指数 \(\max\) 要与 \(L\) 该位相等,指数的 \(\min\)\(0\)。发现 \(\dfrac{L}{G}\) 最多 \(8\) 个质因数,可以状压

\(f_{i,s,t}\) 表示前 \(i\) 位的数的指数 \(\max\) 满足了 \(s\) 集合,指数 \(\min\) 满足了 \(t\) 集合的方案数。枚举 \(i\) 的贡献,则 \(f_{i,s,t}\rightarrow f_{i+1,s',t'}\)。同时 \(i\) 可以不选,\(f_{i,s,t}\rightarrow f_{i+1,s,t}\)。答案即 \(f_{n,S,T}\)

但是现在还要求必须选某个数 \(a_i\)。然后不会了……我的想法是,对于每个询问 \(a_i\),将 \(i-1\)\(i+1\)\(f_{,s,t}\) 记录下来,枚举超集计算贡献(不会高维前缀和),然后合并,时间复杂度 \(\mathcal{O}(800\times 3^{16})\),无法通过。后来想要是一个数 \(x\) 它没有任何一位的指数取到 \(0\),也没有取到 \(\max\),那它在转移的时候转移系数就是 \(0\),所以它的答案应该是 \(\dfrac{f_{n,S,T}}{2}\)。估计不满足这样条件的数应该不会很多,所以其它的暴力做感觉能通过 。

UPD:获得 \(80\) 分,正解就是高维前缀和,时间复杂度 \(\mathcal{O}(8\times 2^{16} \times 800)\)

code
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define LL long long
using namespace std;

bool Mbe;

const int N=800,QQ=5010,SS=(1<<8)+10;
const int MOD=998244853;

int n,G,L,des,Q,S,q[QQ],ans[N];
int a[N],d[N],m;
int p[N][10],cnt,legal[N][2];
int f[N][SS][SS],g[N][SS][SS];
map <int,int> desp;
bool isque[N];

void prework()
{
	for(int i=1; i*i<=L; i++)
	{
		if(L%i==0)
		{
			if(i%G==0 && i<=n)
				a[++m]=i;
			if(i*i!=L && (L/i)%G==0 && L/i<=n)
				a[++m]=L/i;
		}
	}
	sort(a+1,a+1+m);
	for(int i=1; i<=m; i++)
		d[i]=a[i]/G;
}

void divide(int x,int i)
{
	for(int j=2; j*j<=x; j++)
	{
		if(x%j==0)
		{
			if(i==0)
				desp[j]=++cnt;
			int id=desp[j];
			while(x%j==0)
				p[i][id]++,x/=j;
		}
	}
	if(x>1)
	{
		if(i==0)
			desp[x]=++cnt;
		p[i][desp[x]]++;
	}
}

void work(int i)
{
	for(int j=1; j<=cnt; j++)
	{
		if(p[i][j]==p[0][j])
			legal[i][0]^=(1<<j-1);
		if(p[i][j]==0)
			legal[i][1]^=(1<<j-1);
	}
}

void SOS_DP()
{
	for(int i=1; i<=cnt; i++)
		for(int s=0; s<=S; s++)
			if(!(s&(1<<i-1)))
				for(int t=0; t<=S; t++)
					for(int j=1; j<=m; j++)
						(g[j][s][t]+=g[j][s|(1<<i-1)][t])%=MOD;
	for(int i=1; i<=cnt; i++)
		for(int t=0; t<=S; t++)
			if(!(t&(1<<i-1)))
				for(int s=0; s<=S; s++)
					for(int j=1; j<=m; j++)
						(g[j][s][t]+=g[j][s][t|(1<<i-1)])%=MOD;
}

int solve(int i)
{
	int res=0;
	for(int s=0; s<=S; s++)
	{
		for(int t=0; t<=S; t++)
		{
			int ss=s|legal[i][0],tt=t|legal[i][1];
			(res+=1LL*f[i-1][s][t]*g[i+1][S^ss][S^tt]%MOD)%=MOD;
		}
	}
	return res;
}

bool Med;

int main()
{
	freopen("easy.in","r",stdin);
	freopen("easy.out","w",stdout);

	// fprintf(stderr, "%.3lfMB\n",(&Mbe-&Med)/1048576.000);

	scanf("%d%d%d%d",&n,&G,&L,&Q);
	for(int i=1; i<=Q; i++)
		scanf("%d",&q[i]);
	
	if(L%G!=0)
	{
		for(int i=0; i<=Q; i++)
			puts("0");
		return 0;
	}

	prework();

	divide(des=L/G,0);
	for(int i=1; i<=m; i++)
	{
		divide(d[i],i);
		work(i);
	}

	for(int i=1; i<=Q; i++)
	{
		int cur=lower_bound(a+1,a+1+m,q[i])-a;
		if(a[cur]!=q[i])
			continue;
		isque[cur]=1;
	}

	f[0][0][0]=1;  S=(1<<cnt)-1;
	for(int i=0; i<m; i++)
	{
		for(int s=0; s<=S; s++)
		{
			for(int t=0; t<=S; t++)
			{
				(f[i+1][s|legal[i+1][0]][t|legal[i+1][1]]+=f[i][s][t])%=MOD;
				(f[i+1][s][t]+=f[i][s][t])%=MOD;
			}
		}
	}
	g[m+1][0][0]=1;
	for(int i=m+1; i>1; i--)
	{
		for(int s=0; s<=S; s++)
		{
			for(int t=0; t<=S; t++)
			{
				(g[i-1][s|legal[i-1][0]][t|legal[i-1][1]]+=g[i][s][t])%=MOD;
				(g[i-1][s][t]+=g[i][s][t])%=MOD;
			}
		}
	}

	SOS_DP();

	for(int i=1; i<=m; i++)
	{
		if(!isque[i])
			continue;
		ans[i]=solve(i);
	}

	printf("%d\n",f[m][S][S]);
	for(int i=1; i<=Q; i++)
	{
		int cur=lower_bound(a+1,a+1+m,q[i])-a;
		if(a[cur]!=q[i])
			puts("0");
		else
			printf("%d\n",ans[cur]);
	}

	return 0;
}

T4 疯癫兄弟

[ARC080F] Prime Flip

一个很经典但是想不到的 Trick,把区间 \(\rightarrow\) 单点操作。原来的区间操作是将一段长度为奇质数的区间全部取反,对应到差分序列就是把两个相隔奇质数的位置取反。

一开始的差分序列上有若干个 \(1\),现在要全部变成 \(0\)。增加 \(1\) 肯定不优,所以要尽量让 \(1\) 之间两两匹配的代价最小。

根据「哥德巴赫猜想」,任何一个 \(\geq 6\) 的偶数都可以拆成两个奇质数相加,所以相隔奇质数的需要 \(1\) 次,相隔偶数的需要 \(2\) 次,相隔非质奇数的需要 \(3\) 次。

贪心的考虑,我们要让奇质数的匹配最多,发现这一定是一奇一偶之间作差,于是建出二分图,求出最大匹配,再加上其它的贡献即可。

这种差分和前缀和、区间与单点之间的转化还是蛮常见的。

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e3+10,NN=1e7+10,M=1e7+10,INF=1e9;

int n,m,a[N<<1],b[NN],cnt[2];
int s,t,maxflow,d[N<<1];
int head[N<<1],nxt[M<<1],ver[M<<1],edge[M<<1],now[N<<1],tot=1;

void add(int x,int y,int z)
{
	ver[++tot]=y;  edge[tot]=z;  nxt[tot]=head[x];  head[x]=tot;
	ver[++tot]=x;  edge[tot]=0;  nxt[tot]=head[y];  head[y]=tot;
}

bool bfs()
{
	memset(d,0,sizeof(d));
	queue <int> q;
	q.push(s);  d[s]=1;
	now[s]=head[s];
	while(q.size())
	{
		int x=q.front();  q.pop();
		for(int i=head[x]; i; i=nxt[i])
		{
			int y=ver[i];
			if(edge[i] && !d[y])
			{
				d[y]=d[x]+1;
				now[y]=head[y];
				q.push(y);
				if(y==t)
					return 1;
			}
		}
	}
	return 0;
}

int dinic(int x,int flow)
{
	if(x==t)
		return flow;
	int rest=flow;
	for(int &i=now[x]; i && rest; i=nxt[i])
	{
		int y=ver[i];
		if(edge[i] && d[y]==d[x]+1)
		{
			int k=dinic(y,min(rest,edge[i]));
			if(k<=0)
				d[y]=0;
			edge[i]-=k;
			edge[i^1]+=k;
			rest-=k;
			if(rest<=0)
				break;
		}
	}
	return flow-rest;
}

void Dinic()
{
	int flow=0;
	while(bfs())
		while(flow=dinic(s,INF))
			maxflow+=flow;
}

bool isprime(int x)
{
	if(x==1)
		return 0;
	if(x==2)
		return 1;
	for(int i=2; i*i<=x; i++)
		if(x%i==0)
			return 0;
	return 1;
}

int main()
{
	freopen("crazy.in","r",stdin);
	freopen("crazy.out","w",stdout);

	scanf("%d",&n);
	for(int i=1; i<=n; i++)
	{
		int x;
		scanf("%d",&x);
		b[x]^=1;  b[x+1]^=1;
	}
	for(int i=0; i<=1e7+1; i++)
		if(b[i])
			a[++m]=i,cnt[i&1]++;

	for(int i=1; i<=m; i++)
		for(int j=i+1; j<=m; j++)
			if(((a[j]-a[i])&1) && isprime(a[j]-a[i]))
			{
				if(a[i]&1)
					add(i,j,1);
				else
					add(j,i,1);
			}
	s=0;  t=m+1;
	for(int i=1; i<=m; i++)
	{
		if(a[i]&1)
			add(s,i,1);
		else
			add(i,t,1);
	}

	Dinic();

	int ans=maxflow;
	int tmp[2]={(cnt[0]-ans)/2*2,(cnt[1]-ans)/2*2};
	ans+=tmp[0]+tmp[1];
	if(maxflow+tmp[0]!=cnt[0])
		ans+=3;

	printf("%d\n",ans);
	
	return 0;
}
posted @ 2023-11-15 09:22  xishanmeigao  阅读(31)  评论(0)    收藏  举报