1. 程式人生 > 實用技巧 >DAY 1

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函式最好設為longlongdoubledouble型別。

(9).(9).有可能會有一部分的dpdp初始值無法轉移過來,需要手動提前弄一下,例如擺渡車[P5017][P5017]

(10).(10).在比較兩個斜率時,儘量寫上等於,即<=>=而不是<>。這樣寫對於去重有奇效(有重點時會導致斜率分母出鍋),但不要以為這樣就可以完全去重,因為要考慮的情況可能會非常複雜,所以還是推薦加上(3)(3)中提到的特判,確保萬無一失。