DP训练7 重新复健
很久没写题了 大概有半个月吧 中间有许多忙事
然后这几天开学也是 手机坏掉了 电脑坏掉了 然后又要招新
最重要的是 复健 ccpc今年去不了了 因为报名没注意过时间了
第一道错排题目

做了这道题 我才知道错排的 首先错排是什么
就是说 a b c d....这么多个人 没有一个人可以站在各自的位置上
这个方案书 可以通过数学公式求出来
假设现在我们有n个元素 第n个元素放在一个位置上有n-1种放法对吧
对于这个位置k来说 这个元素k可以放在n 也可以不放 想象以下 你吃我的
我可以吃回 也可以不吃回对吧
于是有fn=(n-1)*(fn-2+fn-1) 特别的 f1=0 f2=1
下面是改编 我差点以为也是错排了
这题


第二个
当时看来一眼题目数据范围 以为dp d了半天做不出来
猜猜是什么算法

一道很好的数学dp题

这道题蛮难的
对于一个ax的拆分 他可以给前一个人的x或者y进行配对
所以有4种配对方式 我们每次都要取出最小的情况
还要考虑一个事情 就是这个拆分的数字是多少
一个数x 拆分相乘最大就是拆分的两个数字相差最小,反之,则最小
于是 我们就可以知道拆分的数字了
那么问题来了 怎么求拆分的数字 题目说了
(x-s)*(y-s)>=0 要求了 必须 两个人必须都大于或者小于s 或者至少有一个等于s
我们要对这个ax进行枚举分析
如果说这个ax很大的话 我们要拆分出极差最大的两个数 肯定是一个为s 一个为ax-s
如果ax很小的话 注意都是非负整数 两个数极差最大 肯定是一个为0 一个直接为ax了
那如果说ax恰好处于中间呢 那这个中间是什么意思呢 其实就是说大于s小于2s这种情况
这种拆分我们不能让一个数大于s 一个数小于的 这种时候想让他满足条件 我们只能让一方为s 另一方取差值 这是最优分配了 两个人都小于s 肯定不是最优的极差
于是就写出来了
for(int i=2;i<=n-1;i++){
cin>>a[i];
if(a[i]>=2*s){
mini[i]=s;maxn[i]=a[i]-s;
}
else {
mini[i]=max(0*1LL,a[i]-s);
//不可以直接定义maxn=s
maxn[i]=a[i]-mini[i];
}
}
然后注意一个dp书写 由于有4种配对方式
分别是前一个大配小大 或者前一个小配小大 于是开二维就行了
dp[i][0]=min(dp[i-1][0]+maxn[i-1]*mini[i],dp[i-1][1]+mini[i-1]*mini[i]);
dp[i][1]=min(dp[i-1][0]+maxn[i-1]*maxn[i],dp[i-1][1]+mini[i-1]*maxn[i]);
于是这题就写出来了 真有点难的
https://www.luogu.com.cn/problem/CF1829H

9.11补
DP 好题
首先一定要观察数据范围 ai的取值仅仅到63 还有k也只是6而已
如果数据打了这题就没法做了
所以我们完全可以第二维暴力存数值就行了 然后等会把第二维单独
抽出来看满足k就行了
开一个二维dp
对于任何一个ai来说 我可以继承上一个 也可以不继承
不继承那就是从他这边重新开 然后这个也可以继承之前的数值
f[i][a[i]] = 1;
f[i][j] = (1ll * f[i][j] + f[i - 1][j]) % mod;
f[i][j & a[i]] = (1ll * f[i][j & a[i]] + f[i - 1][j]) % mod;****
然后本题结束
cin>>n;cin>>k;
int ans=0;
for(int i=1;i<=n;i++){cin>>a[i];}
for(int i=1;i<=n;i++)
{
dp[i][a[i]]=1;
for(int j=0;j<=63;j++)
{
dp[i][j]=(dp[i-1][j]+dp[i][j])%mod;
// dp[i][j&a[i]]=dp[i-1][j&a[i]]+dp[i][j&a[i]]%mod;
dp[i][j&a[i]]=(dp[i-1][j]+dp[i][j&a[i]])%mod;
}
}
for(int i=0;i<=63;i++)
{ int cnt=0;
for(int j=0;j<=6;j++)
{
if(i>>j&1){
cnt++;
}
}
if(cnt==k){
ans+=dp[n][i]%mod;
}
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<=63;j++)
{
dp[i][j]=0;
}
}
cout<<ans%mod<<endl;

浙公网安备 33010602011771号