HDU5921-Binary Indexed Tree
原题链接:https://vjudge.net/contest/403983#problem/K
CCPC 2016 长春K
赛场上想到了第一种思路的拆分情况,但是后期并没有深入想下去。
赛后题解更多的都是用数位Dp。受到隔壁队伍和网上博客的启发,整理出了三种思路。
虽然可能第一种是混合型的。
并在此感谢xhk老板的指导
一、数位Dp+二进制拆分
题意:
(sum^{n}_{l=1})(sum^{n}_{r=1})f(l,r)=(sum^{n}_{i=1})(sum^{n}_{j=1}{[g(l-1)+g(r)-2*g(lcp(l-1,r))]})
g(i)表示i在二进制下1的个数
将n二进制拆位,考虑从右往左第i位的贡献。
记[i+1...lens]在十进制下为nxt[i+1],[1...i-1]在十进制下为pre[i-1]。
1.计算gi第一步:左边的数不改变
那么为了不大于原数,右边的数应取0...pre[i-1]。
当ai==1时,有pre[i-1]+1的贡献
2.计算gi第二步:左边的数可改变
与上一种情况相对应,左边的数应取0...nxt[i+1]-1.
对于从右往左数第i位1,显然此时右边的数可以任意取值,有2^(i-1)种取值。
所以这种情况下,无论ai取值如何,都有nxt[i+1]*2^i的贡献。
这个思路其实是二进制拆分下的数位Dp,只是把数位Dp的递归换了一种形式。
代码实现
#include<bits/stdc++.h>
typedef long long ll;
const ll MO = 1000000007;
int T,bin[103],lens;
ll n,ans,power[103],pre[103],nxt[103];
ll read()
{
char ch = getchar();
ll num = 0;
bool fl = 0;
for (; !isdigit(ch); ch=getchar())
if (ch=='-') fl = 1;
for (; isdigit(ch); ch=getchar())
num = (num<<1)+(num<<3)+ch-48;
if (fl) num = -num;
return num;
}
int main()
{
T = read(), power[0] = 1;
for (int i=1; i<=60; i++) power[i] = (power[i-1]<<1)%MO;
while (T--)
{
n = read(), ans = lens = 0;
for (ll x=n; x; x>>=1) bin[++lens] = x&1;
pre[0] = nxt[lens+1] = 0;
for (int i=1; i<=lens; i++) pre[i] = (pre[i-1]+power[i-1]*bin[i])%MO;
for (int i=lens; i>=1; i--) nxt[i] = ((nxt[i+1]<<1)+bin[i])%MO;
for (int i=1; i<=lens; i++)
{
if (bin[i]) pre[i-1]++, ans = (ans+pre[i-1])%MO;
ans = (ans+nxt[i+1]*power[i-1]%MO)%MO;
}
ans = (n+1ll)%MO*ans%MO;
for (int i=1; i<=lens; i++)
{
if (bin[i]) ans = (ans+MO-pre[i-1]*pre[i-1]%MO)%MO;
ans = (ans-nxt[i+1]*power[i-1]%MO*power[i-1]%MO+MO)%MO;
}
printf("%lld
",ans);
}
return 0;
}
二、纯二进制拆分(zx)
上一种是二进制拆分,但是核心思路还是用到了数位DP,下面的这种是纯二进制,来自zx
题解:对于每一位i(i从1开始),pow2[i]一组,组数记为tuan,除组数以外的1的数量记为san
以第三位为例,可以分成tuan=1,san=0;第四位tuan=0,san=1;
为了把0算作一个数,所以n++,n表示从0开始的数字的数量
tuan=n/pow2[i],san=(n%pow2[i]/pow2[i-1])*(n%pow2[i-1])
遍历每一位的时候,ansleft+=n·(tuan·pow2[i-1]+san)//把该位是1的个数加上
ansright+=tuan·pow2[i-1]·pow2[i-1]+san·san//因为对于每一组它们的lcp都包括该位
pow2[i]: 32 16 8 4 2 1
num=0 0 0 0 0 0 0
num=1 0 0 0 0 0 1
num=2 0 0 0 0 1 0
num=3 0 0 0 0 1 1
num=4 0 0 0 1 0 0
num=5 0 0 0 1 0 1
num=6 0 0 0 1 1 0
num=7 0 0 0 1 1 1
num=8 0 0 1 0 0 0
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
#define debug(x) cout<<#x<<':'<<x<<endl;
const int mod=1e9+7;
ull pow2[65];
ull mul(ull a,ull b){
a%=mod,b%=mod;
return a*b%mod;
}
int main()
{
pow2[0]=1;
for(int i=1;i<64;i++)pow2[i]=pow2[i-1]*2;
int T,t=0;scanf("%d",&T);
while(T--)
{
ull n;scanf("%llu",&n);n++;//n为算上零后的个数,
ull ansleft=0,ansright=0;
for(int i=63;i>=1;i--)
{
ull san=mul((n%pow2[i])/pow2[i-1],n%pow2[i-1]);
ull tuan=(n/pow2[i])%mod;
ansright=(ansright+mul(tuan,mul(pow2[i-1],pow2[i-1]))+mul(san,san))%mod;
ansleft=(ansleft+mul(n,mul(tuan,pow2[i-1])+san))%mod;
}
printf("Case #%d: %lld
",++t,(ansleft+mod-ansright)%mod);
}
}
三、数位DP
思路:
每个节点对答案的贡献为 子树个数*(n-子树个数+1),只和子树个数相关,计算出子树个数为2^k的树有多少个.
注意:最大节点编号的限制那些编号特别大的子节点是不能算入答案的。所以我们需要把这种边界情况单独计算。
代码实现
#include <bits/stdc++.h>
#define INF 2147483640
#define eps 1e-9
#define MAXN 30010
#define MOD 1000000007
typedef long long ll;
using namespace std;
int t,pos,j,a[65];
ll n,dp[65][2];
ll dfs(int pos,int limit)
{
if(pos == j) return !limit;
if(dp[pos][limit] >= 0) return dp[pos][limit];
int up = limit ? a[pos] : 1;
ll ans = 0;
for(int i = 0;i <= up;i++)
{
ans += dfs(pos-1,limit && i == up);
if(ans >= MOD) ans %= MOD;
}
dp[pos][limit] = ans;
return ans;
}
void solve(ll x)
{
pos = 0;
while(x)
{
a[pos++] = x & 1;
x >>= 1;
}
}
int main()
{
scanf("%d",&t);
for(int T = 1;T <= t;T++)
{
scanf("%I64d",&n);
solve(n);
ll ans = 0;
for(j = 0;(1ll<<j) <= n;j++)
{
memset(dp,-1,sizeof(dp));
ans = (ans + ((dfs(pos-1,1)*((1ll<<j) % MOD) % MOD)*((n + 1 - (1ll<<j)) % MOD)) % MOD) % MOD;
if(a[j])
{
ll temp = (n - ((n>>j)<<j) + 1) % MOD;
ans = (ans + (temp*((n + 1 - temp) % MOD)) % MOD) % MOD;
}
}
printf("Case #%d: %I64d
",T,ans);
}
}