【Petrozavodsk Programming Camp, Winter 2020 Problem B】 Binomial DP 详解

There are people that wont be happy with solving a problem, unless the actual task is obscured by an elaborate
and somewhat unnecessary story. If you are one of these people, then this problem is NOT for you.
You are given a sequence of non-negative integers a1, a2, . . . , an. You need to find the number of pairs (i, j) such
that 1 ≤ i, j ≤ n and
ai
aj

is odd.
Note that n
k

is the number of ways, disregarding order, that k objects can be chosen from among n objects. In
particular, if n < k then n
k

= 0.
Input
The first line of input contains the number of test cases z (1 ≤ z ≤ 10). The descriptions of the test cases follow.
The first line of each test case contains the number of elements in the sequence n (1 ≤ n ≤ 106
).
The second line contains n integers ai (1 ≤ ai ≤ 106
) – the elements of the sequence.
Output
For each test case, output a single line which contains a single integer – the answer for the problem.
Example
standard input standard output
2
3
1 5 6
3
1 1 1
4

题意:在序列中任取两个数使得组合数C(a[i], a[j])为奇数, 问有多少个这样的对

思路:

首先要知道满足C(n,k)为奇数的条件是n&k==k。
那么,既然是与运算,我们就从二进制的角度考虑。要想,n&k=k,那么肯定满足这样的性质,k中二进制上位上为1的,n中对应位也肯定是1,这样才满足等式。那么,完全就可以把k看成是n的一个子集,换句话说,将n的二进制上剔除若干个1可以得到k。
既然如此,我们就可以求n的一个二进制子集。怎么求呢?我们可以每次剔除一个位。状态转移方程就是
dp[i] += dp[i - (1<<j)]
其中j就是i中的第j位的1 。 相当于我当前i作为n的时候,对其有直接贡献的就是k = i-(i<<j),而 k又可以向下分解,往它的子集取贡献。如11010的图示:
在这里插入图片描述11010 分解为成分别去掉三个位1的子树,子树又可以分解成子子树。那么对于这棵树上的任意一个值,若它在数列中出现过,都可以当做k,对11010提供贡献。如序列中有2(010),那么就可以和11010(26)组合。
按照图示的思路,采用记忆化递归的代码是:

inline ll DP(ll cur)
{
    if(vis[cur]) return vis[cur];
    if(cur==0) return 0;
    ll ans = 0;
    ll up = log2(cur) + 1;
    rep(i,0,up)
    {
        if(cur>>i&1)
        ans+= DP(cur^(1<<i));
    }
    vis[cur] = ans;    if(Map[cur]) vis[cur] += Map[cur];//Map记录有没有在序列中出现过
    return vis[cur] ;
}

但是递归会超时(虽然记忆化了),所以还是老老实实写递推,递推的话就要枚举1->最大值之间的所有点了:

#include<iostream>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include <queue>
#include<sstream>
#include <stack>
#include <set>
#include <bitset>
#include <unordered_map>
#include<vector>
#define FAST ios::sync_with_stdio(false)
#define abs(a) ((a)>=0?(a):-(a))
#define sz(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()
#define mem(a,b) memset(a,b,sizeof(a))
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define rep(i,a,n) for(int i=a;i<=n;++i)
#define per(i,n,a) for(int i=n;i>=a;--i)
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<ll,ll> PII;
const int maxn = 1e6+2;
const int inf=0x3f3f3f3f;
const double eps = 1e-7;
const double pi=acos(-1.0);
const int mod = 1e9+7;
inline int lowbit(int x){return x&(-x);}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
void ex_gcd(ll a,ll b,ll &d,ll &x,ll &y){if(!b){d=a,x=1,y=0;}else{ex_gcd(b,a%b,d,y,x);y-=x*(a/b);}}//x=(x%(b/d)+(b/d))%(b/d);
inline ll qpow(ll a,ll b,ll MOD=mod){ll res=1;a%=MOD;while(b>0){if(b&1)res=res*a%MOD;a=a*a%MOD;b>>=1;}return res;}
inline ll inv(ll x,ll p){return qpow(x,p-2,p);}
inline ll Jos(ll n,ll k,ll s=1){ll res=0;rep(i,1,n+1) res=(res+k)%i;return (res+s)%n;}
inline ll read(){ ll f = 1; ll x = 0;char ch = getchar();while(ch>'9'||ch<'0') {if(ch=='-') f=-1; ch = getchar();}while(ch>='0'&&ch<='9') x = (x<<3) + (x<<1) + ch - '0',  ch = getchar();return x*f; }
int dir[4][2] = { {1,0}, {-1,0},{0,1},{0,-1} };

ll  Map[maxn];
ll a[maxn];
ll n;
int main()
{
    ll kase; kase = read();
    while(kase--)
    {
        mem(Map,0);
        n = read();
        ll ma  = -1;
        rep(i,1,n)
        {
             a[i] = read();
            Map[a[i]] ++;
            ma = max(ma, a[i]);
        }
        ll up = log2(ma) + 1;
        rep(i,0LL,up)
        {
            rep(j,1LL,ma+1)
            {
                if((j>>i)&1)
                {
                    Map[j] += Map[j - (1<<i)];
                }
            }
        }
        ll sum = 0;
        rep(i,1,n) sum += Map[a[i]];
        printf("%lld\n",sum);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章