2023.11.14测试

NOIP模拟赛-2023.11.14

T1 简单的题

给三个数 n,G,L,要求从 1n 中选出一个非空子集使 gcd=Glcm=L。问方案数。同时有若干询问,给定 ai,求在包含 ai 的前提下的方案数。

n,G,L108Q5000

L 所有因数,G 所有倍数取出来,记为 a1,,ann 的规模 800。把它们写作 Gd1,Gd2,,Gdn 的形式,现在变成从 d 里面选数,记选出的为 x1,x2,xm,则 gcd(x1,,xm)=1lcm(x1,,xm)=LG。分解质因数变成每一位因数的指数 max 要与 L 该位相等,指数的 min0。发现 LG 最多 8 个质因数,可以状压

fi,s,t 表示前 i 位的数的指数 max 满足了 s 集合,指数 min 满足了 t 集合的方案数。枚举 i 的贡献,则 fi,s,tfi+1,s,t。同时 i 可以不选,fi,s,tfi+1,s,t。答案即 fn,S,T

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

UPD:获得 80 分,正解就是高维前缀和,时间复杂度 O(8×216×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,把区间 单点操作。原来的区间操作是将一段长度为奇质数的区间全部取反,对应到差分序列就是把两个相隔奇质数的位置取反。

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

根据「哥德巴赫猜想」,任何一个 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 @   xishanmeigao  阅读(17)  评论(0编辑  收藏  举报
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示