DAY 1

T2 :

#include <stdio.h>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long ll;
const ll mod=998244353;
const int maxn=1e5+10;
ll s[maxn],js[maxn],inv[maxn];
int cnt[40];
int k,n;

ll ksm(ll a,ll k,ll q)
{
    ll temp=1;
    while(k)
    {
        if(k&1) temp=temp*a%mod;
        a=a*a%mod;
        k>>=1;
    }
    return temp;
}
ll C(ll a,ll b)
{
    if(a<0||b<0||a>b) return 0;
    return js[b]*inv[a]%mod*inv[b-a]%mod;
}
int main()
{
    freopen("card.in","r",stdin);
    freopen("card.out","w",stdout);
    scanf("%d%d",&n,&k);
    js[0]=1;inv[0]=1;
    k--;//因为异或最开始的时候需要有一个初始值,根据初始值的1来分类
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&s[i]);
        js[i]=js[i-1]*i%mod;
        for(int j=0;s[i];j++,s[i]>>=1) cnt[j]+=(s[i]&1);//取出每一位有多少个1
    }
    inv[n]=ksm(js[n],mod-2,mod);//注意这里是计算inv
    //printf("%d \n",js[n]);
    for(int i=n;i>=1;i--) inv[i-1]=inv[i]*i%mod;//
    //for(int i=0;i<31;i++) printf("%d ",inv[i]);
    ll ans=0;
    for(int i=0;i<32;i++)
    {
        ll s1=0,s2=0;
        for(int j=0;j<=k;j+=2) s1=(s1+C(j,cnt[i]-1)*C(k-j,n-cnt[i])%mod)%mod;//如果第一个初始的该位置上面是'1'的话,那么就需要再来偶数个1就可以//此时可以选择的奇数数就少了一个
        for(int j=1;j<=k;j+=2) s2=(s2+C(j,cnt[i])*C(k-j,n-cnt[i]-1)%mod)%mod;//奇数同理
        ans=(ans+(1ll<<i)*(s1*cnt[i]%mod+s2*(n-cnt[i])%mod))%mod;
    }
    printf("%lld",ans*ksm(k+1,mod-2,mod)%mod);//这里在注意一下,因为在k张牌当中,我们选择任意一张作为起始都是可以的,所以说多算了k次,
    return 0;
}
 
View Code

思路分析:

  • 对于位运算的题目一定是运用位运算和二进制拆分(因为n<32)来进行求解
  • 关于这种有关二进制位的运算
    我们照例是对每一位进行考虑 
  • 要抓住位运算的本质特征:异或就在于只有奇数个1相互异或才有意义
  • 但同时我们要进行分类讨论,对于第一个选择数的初始值来判断
  • 最后再减去多余情况
  • 一道非常棒的组合数练习题

T4:斜率优化:

 

  •  本质是在【max或者min的操作下】对dp的递推式子进行一定的改写,在计算每一个状态f(i)时利用单调队列或者单调栈在横坐标单调性和斜率来判断决策j之间的优劣关系,在O(n)的时间内求解线性方程
  • 斜率优化要注意三个地方:
  • 1.如何建立横坐标之间的单调性
  • 2.f(i)的计算式
  • 3.询问斜率的构建式子
  • 4.考虑怎样的同一类j可以转移到i上面
  • 具体步骤:只含 LL 的项对于每一个 ii的择优筛选过程都是完全一样的值,只含 Function(i) 的项在一次 ii的择优筛选过程中不变,含 Function(j)的项可能会不断变化(在本题中表现为为严格单增)。
    我们以此为划分依据,把同类型的项用括号括起来,即:
  • 第一步,求解出对应的dp方程,dp[i]=..dp[j]此时方程左边通常有一些Fun(i)*Fun(j)的形式,此时单调队列就没有办法优化
  • 接下来可以分两部分走:根据斜率优化出来的递推式子
  • 我们需要化简成DP(j)=.....f(j) .....+fun(i)+dp(i)....的形式
  • 然后就可以化简成Y(和决策j相关)=K(询问斜率,和i相关)X(具有单调性的某一个量,一般随着i或者j的变化而变化)+B(i相关,表示出线性规划就是球截距最小)的形式
  • 记住一句话:状态点的斜率是用(y,x)来表示的两个点所计算出来的状态,而询问斜率则是判断式子中的K(和i有关的量)在决策判断式的帮助下维护队列首或者栈首的最优化决策
  • 以玩具装箱问题的dp方程为例子:
  • 2.1研究两个决策之间的单调性质:具体来讲(也就是代数法)

     

此时我们就知道了对于同一个状态i,两个决策点j1,j2在哪一种状态下能够计算出更优答案f(i)

  • 此时根据决策优劣的判断:

     

     可以证明出,我们需要维护一个下凸包,并且在某个点k1<k k2>2的节点取到最优点

  • 2.2:直接根据式子进行化简:
  • 回到最开始的问题,斜率优化我们需要两个部分,个是决策的优劣根据斜率的不等式求解,一个是横坐标的单调性保证了下凸包的维护
  • 这一题当中,根据移项我们可以得到
  •  

     

  •  注意这一个式子:三个要素 决策点(),询问斜率以及他的单调性,横坐标的单调性:
  • 发现横坐标不具有单调性,这个时候我们需要探究决策的单调性来构造横坐标的单调性
  • 对于两个j1<j2,如果说v1>v2,那么,我们可以由j1->i,肯定不如j1->j2->i来的更优,这就保证了横坐标v一定出现在了坐标轴的最右端
  • 之后维护下凸壳,根据询问斜率递减,那么把比询问斜率大的部分都删掉,就可以保证了
  •  

     

    实际上只要让维护的凸包方向相同,两种思考方式的代码是一模一样的。

    用单调队列维护凸包点集,操作分三步走:
    (1).(1). 进行择优筛选时,在凸包上找到最优决策点 jj 。
    (2).(2). 用最优决策点 jj 更新 dp[i]dp[i] 。
    (3).(3). 将 ii 作为一个决策点加入图形并更新凸包(如果点 ii 也是 dp[i]dp[i] 的决策点之一,则需要将 (3)(3) 换到最前面)。

    在本题中步骤 (3)(3) 的具体操作为:判断当队尾的点与点 ii 形成可删点图形时,出队直至无法再删点,然后将 ii 加入队列。

    在判断可删图形时有两种方法(以 下凸包 为例),一种是 slope(Q[t-1],Q[t])<=slope(Q[t],i),另一种是 slope(Q[t-1],Q[t])<=slope(Q[t-1],i),都表示出现了可以删去点 Q[t]Q[t] 的情况(只要对边界、去重的处理足够严谨,两种写法是没有区别的)。其中 QQ 是维护凸包点集的队列。

    该做法时间复杂度为 O(nlogn)O(nlog⁡n),瓶颈在于二分寻找最优决策点

关于斜率优化的一些性质:

五.【各种玄学问题】

(ノ°ο°)ノ前方高能预警 (*°ω°*)ノ"非战斗人员请撤离!! *・_・)ノ

(1).(1). 写出 dpdp 方程后,要先判断能不能使用斜优,即是否存在 function(i)function(j)function(i)∗function(j) 的项或者 Y(j)Y(j)X(j)X(j)Y(j)−Y(j′)X(j)−X(j′) 的形式。

(2).(2). 通过大小于符号或者 bb 中 dp[i]dp[i] 的符号结合题目要求 (min/max)(min/max) 判断是上凸包还是下凸包,不要见一个方程就直接盲猜一个下凸。

(3).(3). 当 X(j)X(j) 非严格递增时,在求斜率时可能会出现 X(j1)=X(j2)X(j1)=X(j2) 的情况,此时最好是写成这样的形式:return Y(j)>=Y(i)?inf:-inf,而不要直接返回 infinf 或者 inf−inf,在某些题中情况较复杂,如果不小心画错了图,返回了一个错误的极值就完了,而且这种错误只用简单数据还很难查出来。

(4).(4). 注意比较 k0[i]k0[i] 和 slope(j1,j2)slope(j1,j2) 要写规范,要用右边的点减去左边的点进行计算(结合 (3)(3) 来看,可防止返回错误的极值),如果用的代数法理解,写出了 (X(j2)-X(j1))*k0<=Y(j2)-Y(j1) 或 (X(j2)-X(j1))*k0<=Y(j2)-Y(j1),而恰巧 j1,j2j1,j2 又写反了,便会出现等式两边同除了负数却没变号的情况。当然用 k0k0 和 Y(j2)Y(j1)X(j2)X(j1)Y(j2)−Y(j1)X(j2)−X(j1) 进行比较是没有这种问题的。

(5).(5). 队列初始化大多都要塞入一个点 P(0)P(0),比如 玩具装箱 toytoy,需要塞入 P(S[0],dp[0]+(S[0]+L)2)P(S[0],dp[0]+(S[0]+L)2) 即 P(0,0)P(0,0),其代表的决策点为 j=0j=0。

(6).(6). 手写队列的初始化是 h=1,t=0,由于塞了初始点导致 tt 加 11,所以在一些题解中可以看到 h=t=1甚至是 h=t=0h=t=2 之类的写法,其实是因为省去了塞初始点的代码。它们都是等价的。

(7).(7). 手写队列判断不为空的条件是 h<=t,而出入队判断都需要有至少 22 两个元素才能进行操作。所以应是 h<t 。

(8).(8). 计算斜率可能会因为向下取整而出现误差,所以 slopeslope 函数最好设为 longlong doubledouble 类型。

(9).(9). 有可能会有一部分的 dpdp 初始值无法转移过来,需要手动提前弄一下,例如 摆渡车 [P5017][P5017]

(10).(10). 在比较两个斜率时,尽量写上等于,即 <= 和 >= 而不是 < 和 >。这样写对于去重有奇效(有重点时会导致斜率分母出锅),但不要以为这样就可以完全去重,因为要考虑的情况可能会非常复杂,所以还是推荐加上 (3)(3) 中提到的特判,确保万无一失。

posted @ 2020-11-02 18:44  ILH  阅读(108)  评论(0)    收藏  举报