[SDOI2016]儲能表

儲能表

題解

很明顯,這道題是暴力。

好吧,很明顯暴力只能拿20pts。

用數位dp來完成這道題的做法還是十分普遍的。

首先,我們要用二進制來表示數,畢竟有異或的操作。從第n爲往前推,g_{i,s1,s2,s3}就表示現在是第i位,是否達到上界爲n時的最大數,爲m時的最大數以及爲k時的最大數時的總能量。而dp_{i,s1,s2,s3}表示此時的情況總數。

那麼轉移式子也很好想了:

dp_{i,S1,S2,S3}+=dp_{i-1,s1,s2,s3}g_{i,S1,S2,S3}+=g_{i-1,s1,s2,s3}+[S]2^{i-1}dp_{i-1,s1,s2,s3}

而它總共長度爲64,於是乎很快就可以得到答案了。

源碼

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>
#include<set>
#include<time.h>
using namespace std;
typedef long long LL;
#define int LL
typedef pair<int,int> pii;
#define gc() getchar()
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=gc();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=gc();}
	while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=gc();}
	x*=f;
}
int t,n,m,k,p,ans;
int dp[100][2][2][2],g[100][2][2][2];
signed main(){
	read(t);
	while(t--){
		read(n);read(m);read(k);read(p);ans=0;n--;m--;
		memset(dp,0,sizeof(dp));memset(g,0,sizeof(g));
		dp[0][1][1][1]=1;
		for(int i=0;i<64;i++)
			for(int s1=0;s1<2;s1++)
				for(int s2=0;s2<2;s2++)
					for(int s3=0;s3<2;s3++){
						int j=63-i,num1=n>>j&1,num2=m>>j&1,num3=k>>j&1;
						for(int s4=0;s4<2;s4++)
							if(!s1||s4<=num1)
								for(int s5=0;s5<2;s5++)
									if(!s2||s5<=num2){
										int s6=s4^s5;
										if(!s3||num3<=s6){
											int S1=s1&&s4==num1;
											int S2=s2&&s5==num2;
											int S3=s3&&s6==num3;
											dp[i+1][S1][S2][S3]=(dp[i+1][S1][S2][S3]+dp[i][s1][s2][s3])%p;
											g[i+1][S1][S2][S3]=(g[i+1][S1][S2][S3]+g[i][s1][s2][s3])%p;
											if(s6)g[i+1][S1][S2][S3]=(g[i+1][S1][S2][S3]+(1LL<<j)%p*dp[i][s1][s2][s3]%p)%p;
										}
									}
					}
		k%=p;
		for(int s1=0;s1<2;s1++)
			for(int s2=0;s2<2;s2++)
				for(int s3=0;s3<2;s3++)
					ans=(ans+g[64][s1][s2][s3]-k*dp[64][s1][s2][s3]%p)%p;
		printf("%lld\n",(ans+p)%p);
	}
	return 0;
}

謝謝!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章