1. 程式人生 > 其它 >HDU 6156 題解

HDU 6156 題解

HDU 6156 題解

題目傳送門

中文版題目

題目分析

\(cnt\) 表示在 \(k\) 進位制下,\([L,R]\) 範圍內迴文數的個數。故有 (R-L+1)-cnt 個數字不是迴文數。根據題意,在 \(k\) 進位制下對答案的貢獻為 cnt*k+(R-L+1)-cnt

不難想到用數位 DP 去求出 \(cnt\) 的值。

設計狀態 \(dp[pos][len][k]\),表示當前填到第 \(pos\) 位,總長度為 \(len\),進位制數位 \(k\) 下的數的個數。

Q1 :為何將 \(len\) 設計入狀態?

對於 \(k\) 進位制下的數 \(x\),必然沒有前導零。因此判斷它是否為迴文數,就看這個長度為 \(len\)

的字串是否左右對應相等。

Q2 :如何在函式中下傳 \(len\)

\(len\) 的值與前導零密切相關。如果前面為前導零,且填的這一位仍為 \(0\),那麼接下來 dfs 的數的長度就 -1。否則,\(len\) 的值就固定了。

Q3 :如何方便地判斷是否為迴文串?

  1. 如果填的為前導零,無需判斷,繼續搜。
  2. 如果當前已填的長度小於總長 \(len\) 的一半,則可以任意填 \(0-9\) 間的任意數字,畢竟不影響是否迴文。
  3. 如果當前已填的長度大於總長 \(len\) 的一半,那麼填的數就已經固定了,為之前填過的關於 \(\dfrac{len+1}{2}\) 位對稱的數字。(\(len\) 為偶數時假想最中間有一位)

Q4 :你怎麼知道前面填了什麼數?

由於深搜是一條路徑往黑裡搜,當前所搜的狀態之前的東西是暫時不會改變的,因此開一個數組記錄當前已填的每位所對應所填入的數字即可。

程式碼展示

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

typedef long long ll;
const int M=80;

int k,a[M],c[M];
ll dp[M][M][40];
char s[M];

ll dfs(int pos,int len,int k,bool lead,bool limit)
{
	if(pos==0)return 1;
	if(!limit&&dp[pos][len][k]!=-1)
		return dp[pos][len][k];
	int up=limit?a[pos]:k-1;
	ll ans=0;
	for(int i=0;i<=up;i++)
	{
		ll tmp=0;c[pos]=i;
		if(lead&&(i==0))
			tmp=dfs(pos-1,len-1,k,true,limit&&i==up);
		else if(pos>len/2)
			tmp=dfs(pos-1,len,k,false,limit&&i==up);
		else if(c[len-pos+1]==i)
			tmp=dfs(pos-1,len,k,false,limit&&i==up);
		ans=ans+tmp;
	}
	if(!limit)dp[pos][len][k]=ans;
	return ans;
}

ll solve(ll x,int k)
{
	int len=0;
	while(x)
	{
		a[++len]=x%k;
		x/=k;
	}
	return dfs(len,len,k,true,true);
}

int main()
{
	memset(dp,-1,sizeof(dp));
	int T;scanf("%d",&T);
	for(int t=1;t<=T;t++)
	{
		ll L,R,ans=0;int l,r;
		scanf("%lld%lld%d%d",&L,&R,&l,&r);
		for(int i=l;i<=r;i++)
		{
			ll sum=solve(R,i)-solve(L-1,i);
			ans+=sum*i;
			ans+=(R-L+1-sum);
		}
		printf("Case #%d: %lld\n",t,ans);
	}
	return 0;
}