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; }
思路分析:
- 对于位运算的题目一定是运用位运算和二进制拆分(因为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(nlogn),瓶颈在于二分寻找最优决策点。
关于斜率优化的一些性质:
五.【各种玄学问题】
(ノ°ο°)ノ前方高能预警 (*°ω°*)ノ"非战斗人员请撤离!! *・_・)ノ
(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=0,h=t=2 之类的写法,其实是因为省去了塞初始点的代码。它们都是等价的。
(7).(7). 手写队列判断不为空的条件是 h<=t,而出入队判断都需要有至少 22 两个元素才能进行操作。所以应是 h<t 。
(8).(8). 计算斜率可能会因为向下取整而出现误差,所以 slopeslope 函数最好设为 longlong doubledouble 类型。
(9).(9). 有可能会有一部分的 dpdp 初始值无法转移过来,需要手动提前弄一下,例如 摆渡车 [P5017][P5017]。
(10).(10). 在比较两个斜率时,尽量写上等于,即 <= 和 >= 而不是 < 和 >。这样写对于去重有奇效(有重点时会导致斜率分母出锅),但不要以为这样就可以完全去重,因为要考虑的情况可能会非常复杂,所以还是推荐加上 (3)(3) 中提到的特判,确保万无一失。



浙公网安备 33010602011771号