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\)表示两端是不同颜色点的个数,则满足条件
进一步可以得到
那么\(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;
}