牛客周赛 Round 31 补题

牛客周赛 Round 31 补题

E-小红的子集取反

E-小红的子集取反_牛客周赛 Round 31 (nowcoder.com)

赛时一直往状压dp那方面想了,但其实用状压有点问题,就是\(1\le n \le200\)如果进行状压的话需要\(2^{200}\)个位,int128都存不下,所以应该是做不了的

这道题我们可以设计dp数组 $dp[i][j]\space, 1\le i\le 200 ,-40000\le j\le 40000 $ 。表示已经处理了前i个数,总和为j的最少次数,因为\(-200\le a[i]\le 200\) 所以j最大为40000,最小为-40000,为了防止数组越界,我们将偏移量设为40000,也就是\(dp[i][40000]\)相当于和为0时的最少次数,所以第二维要开成至少80000

一开始把\(dp\)数组全部赋成最大值,因为要取min,特别的\(dp[0][m]=0\)因为这代表着一开始没处理任何数字,和肯定是0

接下来设计转移方程,很明显,\(dp[i]\)是由\(dp[i-1]\)推过来的,因为每个\(a[i]\)只有两种选择:乘1或乘-1,故如果我这个数选择为乘1,则\(dp[i][j]=min(dp[i][j],dp[i-1][j-a[i]])\),因为我们要预留出\(a[i]\)的位置,其实这道题本质是个01背包,因为只有乘-1才会加次数,所以这里不做改动,如果选择乘-1,则需要预留出\(-a[i]\)的位置,即\(dp[i][j]=min(dp[i][j],dp[i-1][j+a[i]]+1)\),这里次数就要加1了

接下来判断出口,如果\(dp[n][m]>n\)代表无解输出-1,因为如果合法那么次数一定是小于等于n次的;否则输出\(dp[n][m]\)

推导完毕,AC代码如下

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define int long long
using ull = unsigned long long;                                                                             
using ll = long long;
using i128 = __int128_t;
using pii = pair<int,int>;
using psi = pair<string,int>;
constexpr ll MOD = 1e9+7;
//-------------------------------------------------------->>>>>>>>>>
const int N = 205;
const int M = 8e4+10;
int dp[N][M];
inline void solve(){
    ms(dp,0x3f);
    int n;
    cin>>n;
    int m=40000;
    dp[0][m]=0;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        for(int j=0;j<=2*m;j++){
            dp[i][j]=min({dp[i][j],dp[i-1][j-x],dp[i-1][j+x]+1});
        }
    }
    if(dp[n][m]>n){
        cout<<-1<<"\n";
    }else{
        cout<<dp[n][m]<<"\n";
    }
}
inline void prework(){
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);cout.tie(nullptr);
    cout<<fixed<<setprecision(12);
    prework();
    int T=1; 
    // cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

F-小红的连续段

F-小红的连续段_牛客周赛 Round 31 (nowcoder.com)

很明显的隔板法

因为只有a,b两个数,所以我们a要分成的部分数aa就是\(aa=\lceil \frac{i}{2}\rceil\),b要分成的部分数bb就是\(bb=i-aa\)

当然这只是压缩后的情况,所以我们在做完这一步之后还要swap一下aa和bb累加才是全部方案数

如何求划分呢?就是隔板法了,设a的数量是x,则x中间有x-1个空隙,那么把a划分成aa个部分的方案数就是从x-1个空中选aa-1个进行划分,即\(\binom{x-1}{aa-1}\),b同理为\(\binom{y-1}{bb-1}\)累乘即为总数

AC代码如下:

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define int long long
using ull = unsigned long long;                                                                             
using ll = long long;
using i128 = __int128_t;
using pii = pair<int,int>;
using psi = pair<string,int>;
constexpr ll MOD = 1e9+7;
//-------------------------------------------------------->>>>>>>>>>
const int mod = 1e9+7;
const int N = 1e6+100;
int fac[N+2],invfac[N+2];
int qpow(int a,int b){
    int res=1;while(b){if(b&1){res=res*a%mod;}a=a*a%mod;b>>=1;}return res;
}
int inv(int x){return qpow(x,mod-2);}
void init(int n){
    fac[0] = 1;for (int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
    invfac[n] = inv(fac[n]);for (int i = n - 1; i >= 0; --i) invfac[i] = (invfac[i + 1] * (i + 1)) % mod;    
}
int C(int n,int m){
    if (n < m || m < 0) return 0;return fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}
int A(int n,int m){
    if (n < m || m < 0) return 0;return fac[n]*invfac[n-m]%mod;
}
inline void solve(){
    int a,b;
    cin>>a>>b;
    for(int i=1;i<=a+b;i++){
        int aa=i/2,bb=i-aa;
        if(aa==0||bb==0){
            cout<<0<<"\n";
            continue;
        }
        int ans=0;
        if(a>=aa&&b>=bb){
            ans=(ans+C(a-1,aa-1)*C(b-1,bb-1)%mod)%mod;
        }
        swap(aa,bb);
        if(a>=aa&&b>=bb){
            ans=(ans+C(b-1,bb-1)*C(a-1,aa-1)%mod)%mod;
        }
        cout<<ans<<"\n";
    }
}
inline void prework(){
    init(1e6+10);
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);cout.tie(nullptr);
    cout<<fixed<<setprecision(12);
    prework();
    int T=1; 
    // cin>>T;
    while(T--){
        solve();
    }
    return 0;
}
posted @ 2024-02-05 01:01  KrowFeather  阅读(61)  评论(0)    收藏  举报