ABC 261

D - I Hate Non-integer Number(DP、更改转移顺序)

Problem

给定一个长度为\(N\)的序列\(\{a\}\),问有多少个子序列满足它们的平均数是整数

$1\le N\le 100 \text{;}1\le a_i\le 10^9 $

Solve

一种很好想到的状态表示是\(dp_{i,j,k}\)表示前\(i\)个数选了\(j\)个,并且它们的和模个数\(j\)的余数是\(k\)。但是如果按顺序枚举,就是先枚举第\(i\)个数,然后枚举选了几个数,最后枚举余数是多少。但发现这样无法转移,就是你已知\(j\)个数的和模\(j\)的余数是\(k\)的情况下,无法推出\(j\)个数的和模\(j+1\)的大小。观察一下这个过程困扰我们的是什么,是数字个数的变化导致我们不好确定余数,那么我们就多加一重循环,固定选取的数字个数为\(c\),那么\(dp\)的意义变为前\(i\)个数,选取了\(j\)个,并且这\(j\)个数的和模\(c\)\(k\)的个数,然后\(O(n^4)\)的枚举,其实就可以过了。并且我们采用递推的方式来更新答案,就是从小的推到大的。

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
int dp[105][105][105];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin>>n;
    vector<int >a(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];
    ll ans=0;

    for(int c=1;c<=n;c++){
        memset(dp,0,sizeof dp);
         dp[0][0][0]=1;
         for(int i=1;i<=n;i++){
             for(int j=0;j<=c;j++){
                for(int k=0;k<c;k++){
                    dp[i][j][k]=(dp[i][j][k]+dp[i-1][j][k])%mod;
                    if(j!=0) dp[i][j][(k+a[i])%c]=(dp[i][j][(k+a[i])%c]+dp[i-1][j-1][k])%mod;
                }
             }
         }
         ans=(ans+dp[n][c][0])%mod;
    }
   cout<<ans<<'\n';
   return 0;
}

E - Red and Blue Graph(计数)

Problem

给定一个\(n\)个点\(m\)条边的简单无向图,现在可以给每个点染红色或蓝色,问满足以下条件的染色方法数

  • 红色点必须且只能染\(k\)
  • 两端点颜色不同的边的条数是偶数

\(1\le N\le 2\times 10^5\)

Solve

假设一个已经染完色的图,令\(S\)表示其中红色点的度数,\(R\)表示两端都是红色点的边数数,\(D\)表示两端是不同颜色点的个数,则满足条件

\[S=2R+D \]

进一步可以得到

\[S\equiv D\pmod{2} \]

那么\(D\)是偶数当且仅当\(S\)是偶数,而\(S\)又是所有红色点的度数,那么说明红色点中度数为奇数的个数只能为偶数个,所以枚举那些奇度数点被选并且选偶数个即可。

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
ll power(ll x ,int y){
    ll res=1;
    while(y){
        if(y&1) res=res*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return res;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n,m,k;
    cin>>n>>m>>k;
    vector<ll>fac(n+1),infac(n+1);
    fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
    infac[n]=power(fac[n],mod-2);
    for(int i=n;i>=1;i--) infac[i-1]=infac[i]*i%mod;
    auto binom=[&](int x,int y)->ll{
        if(x<y || y<0) return 0;
        return fac[x]*infac[y]%mod*infac[x-y]%mod;
    };
    int odd=0;
    vector<int>deg(n+1);
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        deg[u]++,deg[v]++;
    }
    for(int i=1;i<=n;i++)
        if(deg[i]&1) odd++;
    ll ans=0;
    for(int i=0;i<=k;i+=2)
        ans=(ans+binom(odd,i)*binom(n-odd,k-i)%mod)%mod;
    if(ans<0) ans+=mod;
    cout<<ans<<'\n';
    return 0;
}

G.G - LIS with Stack (记忆化搜索、DP)

Problem

一开始有一个空的序列\(X\)和空的栈\(S\),还有一个长度为\(N\)数字序列\(A\)。按照从\(1-N\)的顺序,每次可以进行以下之一的操作

  • \(a_i\)加入\(S\)
  • \(a_i\)\(A\)中丢弃

并且只要\(S\)不为空,每次还可以把一个栈中的元素移动到\(X\)的末尾。如果\(X\)最后是非递减的,那么\(X\)的价值就是\(|X|\),否则就是\(0\)。问\(X\)的最大可能价值

\(1\le N,a_i\le 50\)

Solve

定义\(dp_{l,r,a,b}\)表示使用了在区间\([l,r]\)中且值域在\([a,b]\)中的\(A_i\)组成序列\(X\)可以获得的最大价值。对于\(a[l]\),考虑它从什么时候加入到\(X\)中,即它在哪个区间\([l+1,r]\)中的哪个位置\(k\)被操作后从\(S\)中加入\(X\)里面,那么转移很明显就是\(dp_{l,r,a,b}=\text{max}(dp_{l+1,k,a,a[l]}+dp_{k+1,r,a[l],b}+1)+1\)。就是看一下\(a[l]\)\(X\)中的什么位置,满足它前面的数不大于它,后面的数不小于它。

Code

#include <bits/stdc++.h>
using namespace std;
int dp[55][55][55][55];
int a[50];
int dfs(int l,int r,int mn,int mx){
    if(l>r) return 0;
    if(l==r) return a[l]>=mn&&a[l]<=mx;
    if(~dp[l][r][mn][mx]) return dp[l][r][mn][mx];
    int res=0;
    res=max(res,dfs(l+1,r,mn,mx));
    if(a[l]>=mn&&a[l]<=mx){
        for(int k=l;k<=r;k++)
            res=max(res,dfs(l+1,k,mn,a[l])+dfs(k+1,r,a[l],mx)+1);
    }
    return dp[l][r][mn][mx]=res;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    memset(dp,-1,sizeof dp);
    int n;
    cin>>n;

    for(int i=1;i<=n;i++) cin>>a[i];
    cout<<dfs(1,50,1,50)<<'\n';
    return 0;
}
posted @ 2022-08-01 13:11  Arashimu  阅读(45)  评论(0编辑  收藏  举报