1. 程式人生 > 實用技巧 >Luogu2150 壽司晚宴

Luogu2150 壽司晚宴

Description

link

給定 \(n\) 求將 \(2 \to n\) 的所有數分成兩份,使得兩份中的每一對數都互質的方案數,可以有數不在任何一份中

\(n\le 500\)

Solution

不互質就是有共同的因子

那就看兩個人的方案裡面統計因子個數然後狀壓質數嗎?

質數顯然是不能被狀壓的,因為壓不動……

但是一個數必然的構成要不是單獨是一個質數,要不必然有一個 \(\sqrt{500}\) 一下的質因子,目測是隻有$1\to 19\ \ $ \(8\)個素數

啊這裡結論沒有想到位!!!

光想到 \(\sqrt{500}\)

Each number could only have one divisor which is greater than \(\sqrt{500}\)

So what we can do is that sorting the number \(2 \to n\) by its greater divisor

It means that if the two num has the same greater divisor, they can' t be chosen by the same person

This is the way how we deal it

嚇死我了,我還以為輸入不了中文了……又能成了就好……

下面的 ”段“ 指那些較大質因子相同的數字組成的那個段,每個素數自成一個段

具體地來說……設一個 \(ans_{i,j}\)

來統計答案,\(f1_{x,y}\) 表示在段內的給第一個人統計的方法……\(f2_{x,y}\) 表示在段內給第二個人統計的方法

在段開始處理的時候把 \(ans\) 的值賦給 \(f_1,f_2\)

如果 \(S\&T=0\), 那麼這種方案合法

對於段內的每個數字, \(calc\) 一下 它包含的小質數集合

能與上的就加

處理完每個段之後要 \(ans_{S_1,S_2}=f1_{S_1,S_2}+f2_{S_1,S_2}-ans_{S_1,S_2}\)

這裡還得寫滾動陣列

我直接反向處理了,感覺相對好寫一點

最後把所有的合法狀態一統計就完事了

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define For(i,a,b) for(int i=a;i<=b;++i)
namespace yspm{
	inline int read()
	{
		int res=0,f=1; char k;
		while(!isdigit(k=getchar())) if(k=='-') f=-1;
		while(isdigit(k)) res=res*10+k-'0',k=getchar();
		return res*f;
	}
	const int N=510,SZ=1<<8;
	int n,pri[N],cnt,fl[N],f[SZ][SZ],g[SZ][SZ],ans[SZ][SZ],mod;
	inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
	inline int del(int x,int y){return x-y<0?x-y+mod:x-y;}
	struct node{
		int id,val,k;
		#define k(i) num[i].k
		#define val(i) num[i].val
		#define id(i) num[i].id
		bool operator <(const node &a)const{return val<a.val;}
	}num[N];
	inline void calc(int x)
	{
		int t=x; id(x)=x;
		for(int i=1;i<=min(cnt,8ll);++i)
		{
			if(x%pri[i]==0) 
			{
				while(x%pri[i]==0) x/=pri[i];
				k(t)|=1<<(i-1);
			}
		} if(x>1) val(t)=x; else val(t)=-t; return ;
	}
	inline void seive(int x)
	{
		For(i,2,x)
		{
			if(!fl[i]) pri[++cnt]=i;
			for(int j=1;j<=cnt&&i*pri[j]<=x;++j)
			{                           
				fl[i*pri[j]]=1; 
				if(i%pri[j]==0) break; 
			}
		}
		for(int i=2;i<=x;++i) calc(i); 
		return ;
	}
	inline int find(int x)
	{
		int res=x; while(val(res+1)==val(res)) ++res; 
		return res;
	}
	int s=0,r;
	inline void work()
	{
		For(i,0,s) For(j,0,s) if(!(i&j)) ans[i][j]=del(add(f[i][j],g[i][j]),ans[i][j]);
		return ;
	}
	signed main()
	{
		n=read(); mod=read(); seive(n); s=1;
		for(int i=1;i<=cnt;++i) if(pri[i]<=19) s<<=1; else break;
		--s;  sort(num+2,num+n+1); 
		ans[0][0]=1; 
		for(int i=2;i<=n;++i) 
		{
			r=find(i);
			For(j,0,s) For(k,0,s) f[j][k]=ans[j][k],g[j][k]=ans[j][k];
			For(j,i,r)
			{
				for(int s1=s;s1>=0;--s1)
				{
					for(int s2=s;s2>=0;--s2)
					{
						if(s1&s2) continue;
						if((k(j)&s1)==0) f[s1][s2|k(j)]=add(f[s1][s2|k(j)],f[s1][s2]);
						if((k(j)&s2)==0) g[s1|k(j)][s2]=add(g[s1|k(j)][s2],g[s1][s2]);
					}
				} 
			} i=r; work();
		} 	
		int tot=0;
		For(i,0,s) For(j,0,s) if(!(i&j)) tot=add(tot,ans[i][j]);
		printf("%lld\n",tot);
		return 0;
	}
}
signed main(){return yspm::main();}

隨想

唉……最後還是沒想到這題的 \(idea\)

可能到時候還是淪為 \(30pts\) 暴力選手了

積累個經驗吧,按說最近沒少做數論題呀

如果出現有質因子不能被狀態壓縮下來的情況,那麼就按照較大質因子排序,然後每段每段處理

細節上的東西必須要注意,畢竟一個字母讓100->0的事情太常見了