1. 程式人生 > 其它 >10.14 正睿做題筆記

10.14 正睿做題筆記

連續兩場掉分,心情非常不爽。

T1

水題,直接拿 bitset 優化轉移就可以過掉這道題。

程式碼:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 26
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

bitset<25> bit[N];

int n,m,f[1<<N],cnt[N*N+100];

inline int Lowbit(int x){return x&(-x);}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    // dd d1=clock();
    read(n);read(m);
    for(int i=1;i<=m;i++){
        int from,to;read(from);read(to);
        from--;to--;
        bit[from][to]=1;bit[to][from]=1;
    }
    int maxx=(1<<n)-1;
    for(int s=1;s<=(1<<n)-1;s++){
        int now=Lowbit(s);
        int t=s-now;
        int w=log2(now);
        bitset<25> nowt(t);
        nowt&=bit[w];
        f[s]=f[t]+nowt.count();
        assert(f[s]<=m);
        cnt[f[s]]++;
    }
    cnt[f[0]]++;
    for(int i=0;i<=m;i++) printf("%d ",cnt[i]);puts("");
    // dd d2=clock();
    // printf("%lf\n",d2-d1);
    return 0;
}

T2

有一定技巧性,屬於那種沒見過做不出來的題。真的非常巧妙。

首先是一個莫比烏斯反演轉化,利用莫比烏斯反演,每個詢問可以做成這樣:\(\sum_{d|x}\mu(d)\sum_{i=l}^r[d|a_i]\)

我們考慮維護後面這個東西,不難發現可以把詢問分成兩塊,這樣我們所有的詢問都變成了這樣:\(\sum_{d|x}\mu(d)\sum_{i=1}^r[d|a_i]\)

然後我們考慮做上面這個東西。首先我們可以把詢問離線下來,然後按照 \(r\) 排序。

然後我們用雙指標去做這個東西,當可以回答詢問的時候,我們就回答詢問。然後沒遇到一個 \(a\) 或者是查詢一個 \(x\),我們用 \(\sqrt n\)

的時間複雜度去列舉其因數。這樣總複雜度可以做到 \(O(n \sqrt n)\)

程式碼:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 100010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Max(T a,T b){return a<b?b:a;}

int Prime[N],tail,Mu[N],n,a[N],Q,qcnt,ans[N],c[N];
bool NotPrime[N];

struct Ques{
    int r,x,type,id;
    inline Ques(){}
    inline Ques(int r,int x,int type,int id) : r(r),x(x),type(type),id(id) {}
    inline bool operator < (const Ques &b)const{return r<b.r;}
}ques[N<<1];

inline void GetMu(int n){
    NotPrime[1]=1;Mu[1]=1;
    for(int i=2;i<=n;i++){
        if(!NotPrime[i]) Prime[++tail]=i,Mu[i]=-1;
        for(int j=1;j<=tail&&Prime[j]*i<=n;j++){
            NotPrime[i*Prime[j]]=1;
            if(i%Prime[j]==0) break;
            else Mu[i*Prime[j]]=-Mu[i];
        }
    }
}

inline void Init(){
    read(n);read(Q);
    for(int i=1;i<=n;i++) read(a[i]);
    for(int i=1;i<=Q;i++){
        int l,r,x;read(l);read(r);read(x);
        if(l-1>0) ques[++qcnt]=Ques(l-1,x,-1,i);
        ques[++qcnt]=Ques(r,x,1,i);
    }
    sort(ques+1,ques+qcnt+1);
}

inline void Insert(int x){
    int i;for(i=1;i*i<x;i++) if(x%i==0){c[i]++;c[x/i]++;}if(i*i==x) c[i]++;
}

inline int Query(int x){
    int i,nowans=0;
    for(i=1;i*i<x;i++) if(x%i==0){nowans+=Mu[i]*c[i];nowans+=Mu[x/i]*c[x/i];}if(i*i==x) nowans+=Mu[i]*c[i];
    return nowans;
}

inline void Solve(){
    for(int i=1,j=0;i<=n;i++){
        Insert(a[i]);
        while(j+1<=qcnt&&ques[j+1].r<=i){j++;ans[ques[j].id]+=Query(ques[j].x)*ques[j].type;}
    }
    for(int i=1;i<=Q;i++) printf("%d\n",ans[i]);
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    GetMu(100000);
    Init();Solve();
}

T3

這個題已經把暴力想出來了,但是放棄了進一步優化,這是我的問題,當時在 BC 之間抉擇的時候,應該優先選擇優化 dp 的。這樣起碼還有些盼頭。

暴力 dp 很好想,設 \(f_{l,r,k}\) 表示區間 \(l,r\)\(k\) 是否有可能贏。那麼我們在左邊列舉一個能夠被 \(k\) 打敗的數 \(a\),右邊列舉一個能被 \(k\) 打敗的數 \(b\),然後轉移就可以。

稍微想一下可以列舉到 \(n^4\),就是我們關注的是左邊有沒有,右邊有沒有,所以不用列舉數對。

然後我們考慮如何優化到 \(n^3\),考慮第 \(3\) 維實際上並沒有什麼用處,這是因為:\(f_{l,r,k}=f_{l,k,k}\and f_{k,r,k}\)。然後我們直接可以省掉第 \(3\) 維,把 dp 變成 \(f_{l,r,0/1}\) 其中 \(0\) 表示原來的 \(f_{l,r,l}\)\(1\) 表示原來的 \(f_{l,r,r}\)

因為 dp 值為 bool,所以我們考慮用 bitset 優化,具體來說,就是用 bit1[l] 來存 \(f_{l,k,0}\),用 bit2[r] 來存 \(f_{k,r,1}\),然後因為需要保證能夠打敗,所以我們轉移的時候在 and 上一個 beat[k] 表示能被 \(k\) 打敗的集合即可。

注意我們最好做完一階段 dp,在去更新兩個幫助轉移的 bitset。

程式碼:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 2010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

bitset<N> bit1[N],bit2[N],Beat[N];
int f[N][N][2],n;
char s[N];

inline void Init(){
    read(n);
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        for(int j=1;j<=n;j++) if(s[j]=='1') Beat[i][j]=1;
    }   
}

inline void Dp(){
    for(int i=1;i<=n;i++){
        f[i][i][0]=f[i][i][1]=1;
        bit1[i][i]=bit2[i][i]=1;
    }
    for(int i=2;i<=n;i++){
        for(int j=1;j<=n-i+1;j++){
            int l=j,r=j+i-1;
            f[l][r][0]=(Beat[l]&bit1[l+1]&bit2[r]).any();
            f[l][r][1]=(Beat[r]&bit1[l]&bit2[r-1]).any();
            // printf("f[%d][%d][0]=%d f[%d][%d][1]=%d\n",l,r,f[l][r][0],l,r,f[l][r][1]);
        }
        for(int j=1;j<=n-i+1;j++){
            int l=j,r=j+i-1;
            if(f[l][r][0]) bit2[r][l]=1;
            if(f[l][r][1]) bit1[l][r]=1;
        }
    }
    for(int i=1;i<=n;i++){
        if(f[1][i][1]&&f[i][n][0]) printf("%d ",i);
    }
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    Init();Dp();
    return 0;
}

T4

非常巧妙的一個轉化。首先我們考慮這個題並不是簡單的,邊不在樹邊上,我們就可以隨便走的題目,這是因為我們題目中的主人公不具有預見性,這個題目實際上是一個博弈模型,想像和你做博弈的人,每當你走到一個點時,就會考慮是否刪掉相鄰的一個邊,且其刪邊的機會只有一次,他的目標是把你的路徑權值和最大化,你的目標是最小化權值和。

以下設 \(s\) 為最短路樹根節點。

我們考慮設 \(val_i\) 表示在最短路樹上去掉 \(i\) 與其父親的邊之後走到根的最短路。\(f_i\) 表示節點 \(i\) 的答案,那麼我們考慮如何計算。

顯然,如果對手打算在我們走到這個節點的時候刪邊,那麼其一定刪最短路上與 \(i\) 相連的父邊,這樣答案就是 \(val_i\),否則,我們可以走到某個節點 \(j\),然後這個時候狀態變成了 \(f_j\),所以我們有轉移 \(f_i=\max(val_i,\min\limits_{(j,i)\in E}(f_j+w(i,j))\)

現在我們有兩個問題需要解決:

  • 如何計算 \(val\)
  • 以什麼順序轉移 \(f\)

我們首先考慮第二個問題。

首先 \(f_s=0\),其次,不難發現,如果 \(f_i\) 最終選擇從 \(f_j\) 轉移過來,那麼一定有 \(f_i>f_j\),如果從 \(val_i\) 轉移過來,那麼一定要麼節點 \(i\) 沒有任何出邊,要麼 \(val_i\) 最大。

上述性質標明,這個轉移一定沒有環,換句話說,我們一定能夠找到一個轉移方式。

不難發現,如果我們每次找當前 \(f\) 值最小的還沒有被擴充套件的節點,那麼一定不存在其餘節點能夠更新它。

所以我們可以通過迪傑斯特拉的方式來更新,因為滿足貪心性質,所以正確性顯然。

然後我們考慮如何計算 \(val_x\)

首先一個性質是它一定只走了一條非樹邊,如果走了兩條的話,不滿足最短路樹的性質,這個容易證明。且這個非樹邊的兩個節點在樹上之間的路徑一定經過這個節點 \(x\),這個畫圖不難證明,否則的話不滿足是一棵樹。當然前提得是走這個邊是最優邊。

我們發現這個最優邊一定是從 \(x\) 往深走,走到某個節點,然後通過一條非樹邊,走到另一顆子樹,然後走到根節點。

我們考慮設 \((a,b,w)\) 為這個非樹邊。那麼 \(val_x=dis_a+dis_b+w+dis_x\),其中 \(dis\) 為最短路樹上節點到根的距離。

暴力的想法是列舉這個最右邊,不過這樣複雜度爆炸。

我們不如把所有的非樹邊拿出來,按照 \(dis_a+dis_b+w\) 進行排序,然後對於一個非樹邊 \((a,b,w)\),覆蓋這兩個點到 \(lca(a,b)\) 的所有點的 \(val\),當然不覆蓋 \(lca\),我們用並查集維護覆蓋的點,具體來說,當一個點被覆蓋,我們就把它和它的父親合併,這樣我們查詢的時候會直接跳到其父親。

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 2010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

bitset<N> bit1[N],bit2[N],Beat[N];
int f[N][N][2],n;
char s[N];

inline void Init(){
    read(n);
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        for(int j=1;j<=n;j++) if(s[j]=='1') Beat[i][j]=1;
    }   
}

inline void Dp(){
    for(int i=1;i<=n;i++){
        f[i][i][0]=f[i][i][1]=1;
        bit1[i][i]=bit2[i][i]=1;
    }
    for(int i=2;i<=n;i++){
        for(int j=1;j<=n-i+1;j++){
            int l=j,r=j+i-1;
            f[l][r][0]=(Beat[l]&bit1[l+1]&bit2[r]).any();
            f[l][r][1]=(Beat[r]&bit1[l]&bit2[r-1]).any();
            // printf("f[%d][%d][0]=%d f[%d][%d][1]=%d\n",l,r,f[l][r][0],l,r,f[l][r][1]);
        }
        for(int j=1;j<=n-i+1;j++){
            int l=j,r=j+i-1;
            if(f[l][r][0]) bit2[r][l]=1;
            if(f[l][r][1]) bit1[l][r]=1;
        }
    }
    for(int i=1;i<=n;i++){
        if(f[1][i][1]&&f[i][n][0]) printf("%d ",i);
    }
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    Init();Dp();
    return 0;
}