1395C,J-Melborp Elcissalc,P1896 [SCOI2005] 互不侵犯

112 阅读1分钟

1395C - Boboniu and Bit Operations 状压dp 或 思维

先说状压dp的做法,f[i][j]表示已经求出了c[i],前i个c[i]是否可以或成j,我们枚举a,枚举b,枚举当前状态也就是前i个c[i]或的和,然后枚举a[i]&b[j]的子集也就是c[i]的子集,看看是否可以转移到f[i][j],由于是枚举子集,所以时间复杂度会减少很多

C. Boboniu and Bit Operations(状压)_issue是fw的博客-CSDN博客

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(i) ((i)&(-i))
const ll mod=9999973;
const int N = 2e5+5;
ll n,m,a[205],b[205],f[205][1<<10];
int main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(int j=1;j<=m;j++) scanf("%lld",&b[j]);
    f[0][0]=1;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++){
        ll v=a[i]&b[j];
        for(int u=0;u<(1<<9);u++){
            if((u&v)!=v) continue;//v中的1 u应该都有
            for(int w=v;w;w=(w-1)&v)
                f[i][u]|=f[i-1][u^w];
            f[i][u]|=f[i-1][u];
        }
    }
    for(int i=0;;i++) 
        if(f[n][i]){printf("%d ",i);break;}
    system("pause");
    return 0;
}

思维的做法:因为或运算只会让数不变或增加,所以最优的状态应该是不变,所以我们应该让c[i]都相同且尽量最小,如果c[i]不相同的话或出来的值会大于所有的c[i]这显然是不行的,所以最小值应该会出现在所有c[i]都相同的情况下,那我们就枚举测试每个答案行不行就可以了,因为数据范围小也用不到二分

C. Boboniu and Bit Operations(位运算操作+思维)Codeforces Round #664 (Div. 2)_unique_pursuit的博客-CSDN博客

ll n,m,a[205],b[205];
bool check(ll s){
    for(int i=1;i<=n;i++){
        ll flag=0;
        for(int j=1;j<=m;j++){
            ll tmp=a[i]&b[j];
            if((tmp|s)==s) flag=1;
            if(flag) break;
        }
        if(!flag) return false;
    }
    return true;
}
int main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(int i=1;i<=m;i++) scanf("%lld",&b[i]);
    for(int i=0;i<(1<<10);i++){
        if(check(i)){printf("%d",i);break;}
    }
    system("pause");
    return 0;
}

[J-Melborp Elcissalc_"蔚来杯" dp 前缀和小技巧](ac.nowcoder.com/acm/contest… "J-Melborp Elcissalc_"蔚来杯" dp 前缀和小技巧")

这个题也是用到了之前用到的前缀和技巧,但是比之前那个好像更难了点,对于一个前缀和来说,要求有多少区间的和是k的倍数,那我们可以对前缀和数组的每个数都取余k,之后每个数出现了x次就有C(x,2)个区间是k的倍数,0例外是C(x+1,2),因为1个0也满足条件;对于一个数组我们需要知道这个数组用到了0-k-1中的哪些数,这些数出现了多少次以及一共有多少区间符合条件,那我们就设一个dp[i][len][s]表示前i个数中用了len个,可以产生s个符合条件的区间,枚举到i,枚举想要用num个i,那么转移方程就是dp[i][len+num][s+C(num,2)]=dp[i-1][len][s],要是放0的话因为例外所以要是dp[i][len+num][s+C(num+1,2)],代码应该是因为方便转移将i都加了1位

2022牛客暑期多校训练营7 个人题解 更新至5题 - 知乎 (zhihu.com)

ll C(ll x,ll y){//求组合数模板
    return fac[x]*getinv(fac[y],mod)%mod*getinv(fac[x-y],mod)%mod;
}

ll n,k,t,dp[70][70][3005],c[105][105];
int main(){
    fac[0]=1;
    for(ll i=1;i<=70;i++) fac[i]=fac[i-1]*i%mod;
    for(ll i=0;i<=64;i++)
    for(ll j=i;j<=65;j++) 
        c[j][i]=C(j,i);
    scanf("%lld%lld%lld",&n,&k,&t);
    dp[0][0][0]=1;
    for(int i=1;i<=k;i++)
        for(int len=0;len<=n;len++)
            for(int s=0;s<=t;s++)
            if(dp[i-1][len][s])
            for(int num=0;num+len<=n;num++){
                if(i==1) dp[i][num+len][c[num+1][2]+s]=(dp[i][num+len][c[num+1][2]+s]+dp[i-1][len][s]*c[num+len][num]%mod)%mod;
                else dp[i][num+len][c[num][2]+s]=(dp[i][num+len][c[num][2]+s]+dp[i-1][len][s]*c[num+len][num]%mod)%mod;
            }
    printf("%lld",dp[k][n][t]);
    system("pause");
    return 0;
}

P1896 [SCOI2005] 互不侵犯 - 洛谷 | 状态压缩dp

之前做过的一道题,当时的思想没有跟上现在来重新做一遍,这其实很像n皇后问题,之前是用搜索做,现在是用代码更简单的dp来做,用一个二进制数来表示一行的放置情况,预处理出所有合法情况之后枚举每行的情况,和上一行作比较如果可行就枚举已经放了多少个国王来进行转移

ll n,m,dp[10][1<<9][105];
ll ok[1<<9],cnt=0;
int main(){
    scanf("%lld%lld",&n,&m);
    for(int i=0;i<1<<n;i++){
        if(i&i>>1||i&i<<1) continue;
        ok[++cnt]=i;
    }
    dp[0][1][0]=1;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=cnt;j++)
    for(int k=1;k<=cnt;k++){
        if(ok[j]&ok[k]||ok[j]>>1&ok[k]||ok[j]<<1&ok[k]) continue;
        ll x=__builtin_popcount(ok[j]),y=__builtin_popcount(ok[k]);
        for(int l=y;l<=m-x;l++) dp[i][j][l+x]+=dp[i-1][k][l];
        //第k种情况已经有y个了,前i-2行可能也有放置的国王,所以从y开始枚举
    }
    ll ans=0;
    for(int i=1;i<=cnt;i++) ans+=dp[n][i][m];
    printf("%lld\n", ans);
    system("pause");
    return 0;
}