BJOI 2021 游記&題解


由於 $\text{BJOI 2020}$ 因疫情停辦,所以上次參加省選還是在初二的時候,歲月如煙,已然從非正式選手到正式選手了。

Day -?

高一開學搞了個 $CF$ 的新號,雖然沒咋打比賽但題已經寫了很多,基本上從 $2400\sim 3000$ 中順着做,為了工整在省選前題目數達到了 $300$ 。

$ZR$ 的 $rating$ 也沖到 $2000$ 了,雖然還是很菜。

北京科協放送了四場免費的省選模擬賽,在我們學校舉行,打的基本上都是暴力分,只有一場到了隊線,感覺要完。

題目質量很高,教會了我基礎容斥原理,謝謝 $dengls$ ,$dlstsdy$ !

Day 0 04/09

考前的最后一天拿出了寒假寫的 $50+$ 頁題目整理看了看,一看就是一天(因為有些題忘記咋做了),感覺愈發的緊張,畢竟是當前最重要的考試。

晚上在學校操場隨機游走,放松了好久才得以緩解。

得知座位表是按照 $NOIP$ 考試成績順着排的,那么???離譜操作。

還發現 $xtq$ 與 $caoyue$ 被捆綁在一起,互搞對方心態(

得知座位后試了試鍵盤,感覺不錯敲了敲板子就回家睡覺了。

曾經有一個時刻,我想復習一下支配樹。

Day 1 04/10

省選一試。

拿到題后心情就舒緩了一點。先把三題都看了一遍,$A$ 感覺是個簡單的送分題,$B$ 是個構造,$C$ 是個不太可做的圖論題,於是決定順序開題。

然后就去搞 $A$ 了,大概 $30\space min$ 就寫完過拍了。

開了發 $B$ 題,看到 $m=2$ 送了 $30$ 分,就寫了寫,發現自己竟然不會第一個子任務 $n,m\leq 3$,這咋整?

於是打了打 $C$ 題送的暴力分,事實上寫的時候我也不知道復雜度多大,$n\leq 10$ ,找個多項式做法就過去了(

回去想了想 $B$ 題,會了 $n=3,m=3$ 的 $\mathcal O(10^6)$ 的做法,沖了發,寫了個拍子,調了調比賽就結束了。 感覺 $B$ 題細節好多。

預計得分 $100+50+16=166$ 。

Day 2 04/11

省選二試。

由於程序回收系統掛了,等了好久才拿到題。

開幕雷擊,“支配”。

發現自己語文優秀,$A$ 題瞬間就看懂了,但發現自己不是很會做,$B,C$ 也是一樣不太會做,感覺離退役不遠了。

冷靜下來發現 $A$ 是個詐騙題,沖了好久終於過拍了,看了一下時間過去了 $2$ 個多小時了。

發現 $B$ 的 $60$ 分是送的,那我還想正解?打打打。

發現 $C$ 的 $30$ 分是送的,那我還想正解?打打打。

於是就到了 $12:00$ 了,感覺自己這個分如果都得到了就挺高了就一直在檢查自己的代碼。 考后發現人均過 $A,B$ ,心態崩了。

預計得分 $100+60+30=190$ 。

發現初三和 $wxjor$ 都沒有走,於是一起打 $generals$ 。鬼知道為啥能從一點打到六點的,從 $FFA$ 到 高中vs初中 ,手速驚人。

准備快樂 $whk$ 。

Day 3~6

回班上課,感覺文化課真的好玩兒。看了看數學/生物/物理,發現能聽到老師在說啥了(

然而發現自己連二元二次方程都不會解了,更加神秘的是 $x+y=z\rightarrow z=-x-y$ 。

(化學太難了)

感覺同學都好強,又要被吊打了。

周四中午吃完飯后發現 $wxjor$ 在操場踢球,問了下是否出成績了,他說我差不多進隊了???就去實驗樓問了問老師,得知一分沒掛 $100+50+16+100+60+30=356$ 。

發現自己進省隊了,震撼。中午本身要去自習也沒什么心情了。

於是下午就開始摸魚,生物課講到一半掉線了,化學課就翹掉去機房玩了(化學太難了)。

Day 7~INF

開始多校聯訓,外省 $dalao$ 恐怖如斯,被吊打了。

Day 12 04/21

正式出榜,成為省隊選手中省選分最低的,幸虧有 $NOIP$ 拉我一把。 

$wxjor$ 惜敗 $caoyue$ ,成為 $B$ 隊隊長,$ysf$ 省隊線下第一名,十分可惜。

感想

事實證明考前寫板子是沒有用的,因為啥都沒考,最難的算法僅是主席樹。

每天的策略都有問題,兩天 $C$ 題思考時間均不超過 $10$ 分鍾,導致這兩題基本上是省隊/學校中的最低分。

自己思維能力還是太弱了,兩天的 $B$ 題均為中檔題卻均打的暴力分。

唯一良好的是每天的 $A$ 題都過了,而且最終預期分等於實際得分,沒有掛分。

$\text{NOI 2021}$ 加油!


 

卡牌游戲

題意

有 $n$ 張卡牌,第 $i$ 號卡牌正面為 $a_i$ ,反面為 $b_i$ ,開始時均正面朝上。可以選擇翻 $m$ 次,最小化極差。

$3\leq n\leq 10^6$ 。

題解

將 $a,b$ 放在一起排序,枚舉最大值,那么只要選取一段區間使得每個元素在其中均有,且能選 $a$ 就選 $a$ ,選 $b$ 不能超過 $m$ 個。

上述過程可以 $\text{two pointers}$ 優化 ,時間復雜度 $\mathcal O(n\log n)$ 。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<climits>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define pii pair<int,int>
using namespace std;
inline int read(){
    int f=1,ans=0; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();}
    return f*ans;
}
const int MAXN=2e6+11;
int N,M,A[MAXN],B[MAXN],tot; pii tmp[MAXN];
bool cmp(pii x,pii y){return x.fi<y.fi;}
int vis[MAXN];
bool calc(int k){
    int l=1; int Minn=INT_MAX;
    for(int i=1;i<=tot;i++){
        while(l<=tot&&tmp[l].fi<tmp[i].fi-k) ++l;
        for(int j=1;j<=N;j++) vis[j]=-1;
        for(int j=l;j<=i;j++){
            int id,w;
            if(tmp[j].se<=N) id=tmp[j].se,w=0;
            else id=tmp[j].se-N,w=1;
            if(vis[id]==-1) vis[id]=w;
            else if(!w) vis[id]=0;
        }
        int res=0; bool ff=0;
        for(int j=1;j<=N;j++){
            if(vis[j]==-1){ff=1;break;}
            res+=vis[j];
        }
        if(ff) continue;
        Minn=min(Minn,res);
    }return Minn<=M;
}
int num[MAXN],cnt,sum;
void del(int x){
    int id,w;
    if(tmp[x].se<=N) id=tmp[x].se,w=0; else id=tmp[x].se-N,w=1;    
    if(num[id]==2){if(!w) sum++; num[id]--;}
    else if(num[id]==1){cnt--; if(w) sum--; num[id]--;}
    return;
}
void ins(int x){
    int id,w;
    if(tmp[x].se<=N) id=tmp[x].se,w=0; else id=tmp[x].se-N,w=1;
    if(num[id]==0){cnt++;if(w) sum++; num[id]++;}
    else if(num[id]==1){if(!w) sum--; num[id]++;}
    return;
}
bool calc1(int k){
    int l=1; int Minn=INT_MAX;
    memset(num,0,sizeof(num)); sum=0,cnt=0;
    for(int i=1;i<=tot;i++){
        ins(i);
        while(l<=tot&&tmp[l].fi<tmp[i].fi-k) del(l),++l;
        if(cnt==N) Minn=min(Minn,sum);
    }return Minn<=M;
}
int main(){
    freopen("card.in","r",stdin);
    freopen("card.out","w",stdout);
    N=read(),M=read(); 
    for(int i=1;i<=N;i++) A[i]=read(),tmp[++tot]=mp(A[i],i);
    for(int i=1;i<=N;i++) B[i]=read(),tmp[++tot]=mp(B[i],i+N);
    sort(tmp+1,tmp+tot+1,cmp);
    int l=1,Minn=INT_MAX;
    for(int i=1;i<=tot;i++){
        ins(i);
        while(l<=i){
            del(l); if(cnt==N&&sum<=M){l++;continue;}
            ins(l); break;
        }
        if(cnt==N&&sum<=M) Minn=min(Minn,tmp[i].fi-tmp[l].fi);
    }
    printf("%d\n",Minn); return 0;
}/*
6 3
8 11 13 14 16 19 
10 18 2 3 6 7 
*/
View Code

矩陣游戲

題意

給定一個 $(n-1)\times (m-1)$ 的矩陣 $B_{i,j}$ ,你需要構造一個 $n\times m$ 的矩陣 $A$ 滿足

$$B_{i,j}=A_{i,j}+A_{i+1,j}+A_{i,j+1}+A_{i+1,j+1}\\0\leq A_{i,j}\leq 10^6$$

$2\leq n,m\leq 300$ 。

題解

如果沒有 $0\leq A_{i,j}\leq 10^6$ 的限制那么必定存在解,也很好構造,只要確定第一行與第一列的元素那么每個位置就唯一確定了,設其為 $A'$ 。

設最后的答案矩陣為 $A$ ,那么肯定可以從 $A'$ 通過一下兩種操作操作。(讓一行中交替加/減一個數,或對列進行這種操作。顯然這樣操作肯定合法)。

證明可以考慮我們肯定能將第一行與第一列的元素通過兩種操作構造,那么就唯一確定了一個矩陣。

那么我們設給第 $i$ 行加/減 $x_i$ ,給第 $i$ 列加/減 $y_i$ ,那么對於位置 $(i,j)$ ,其真正的矩陣值為 $(-1)^{j-1}\cdot x_i+(-1)^{i-1}\cdot y_j+A_{i,j}$ 。

現在考慮 $0\leq A_{i,j}\leq 10^6$ 的條件,二元關系可以對其差分約束,但會產生 $0\leq x_i+y_j\leq 10^6$ 的情況,但我們反轉 $x,y$ 使得變成 $x_i-y_j$ 或 $-x_i+y_j$ 。

時間復雜度 $\mathcal O(能過)$ 。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
#include<queue>
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
using namespace std;
inline int read(){
    int f=1,ans=0; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();}
    return f*ans;
}
const int MAXN=311;
int A[MAXN][MAXN],N,M,cas,head[MAXN<<1],B[MAXN][MAXN],Lim=1000000;
struct Edge{int u,v,w,nex;}E[MAXN*MAXN*2]; queue<int> que;
int cnt,vis[MAXN<<1],dis[MAXN<<1],num[MAXN<<1]; void add(int u,int v,int w){E[cnt].u=u,E[cnt].v=v,E[cnt].w=w,E[cnt].nex=head[u],head[u]=cnt++;return;}
bool spfa(){
    memset(dis,-127/3,sizeof(dis)),memset(vis,0,sizeof(vis)),memset(num,0,sizeof(num));
    dis[1]=0,que.push(1); while(!que.empty()){
        int xx=que.front(); que.pop(); vis[xx]=0; if(num[xx]==N+M) return 0;
        for(int i=head[xx];i!=-1;i=E[i].nex){int v=E[i].v,w=E[i].w; if(dis[v]<dis[xx]+w){dis[v]=dis[xx]+w; if(++num[v]>=N+M) return 0;if(!vis[v]) que.push(v),vis[v]=1;}} 
    }
    return 1;
}
signed main(){
    freopen("matrix.in","r",stdin);
    freopen("matrix.out","w",stdout);
    cas=read(); while(cas--){
        N=read(),M=read(); memset(head,-1,sizeof(head)),cnt=0;
        for(int i=1;i<N;i++) for(int j=1;j<M;j++) B[i][j]=read();
        for(int i=1;i<=N;i++) for(int j=1;j<=M;j++) if(i!=1&&j!=1) A[i][j]=B[i-1][j-1]-A[i-1][j]-A[i][j-1]-A[i-1][j-1];
        for(int i=1;i<=N;i++) for(int j=1;j<=M;j++){
            if((i&1)&&(j&1)) add(j+N,i,-A[i][j]),add(i,j+N,A[i][j]-Lim);
            if((i&1)&&!(j&1)) add(i,j+N,-A[i][j]),add(j+N,i,A[i][j]-Lim);
            if(!(i&1)&&!(j&1)) add(j+N,i,-A[i][j]),add(i,j+N,A[i][j]-Lim);
            if(!(i&1)&&(j&1)) add(i,j+N,-A[i][j]),add(j+N,i,A[i][j]-Lim);
        }
        if(!spfa()){printf("NO\n");continue;}
        printf("YES\n");
        for(int i=1;i<=N;i++){
            for(int j=1;j<=M;j++){
                if((i&1)&&(j&1)) printf("%lld ",A[i][j]+dis[i]-dis[j+N]);
                if((i&1)&&!(j&1)) printf("%lld ",A[i][j]-dis[i]+dis[j+N]);
                if(!(i&1)&&(j&1)) printf("%lld ",A[i][j]-dis[i]+dis[j+N]);
                if(!(i&1)&&!(j&1)) printf("%lld ",A[i][j]+dis[i]-dis[j+N]);
            }printf("\n");
        }
    }
}/*
3
3 3
28 25
24 25
3 3
15 14
14 12
3 3
0 3000005
0 0
*/
View Code

圖函數

題意

對於一張 $n$ 個點 $m$ 條邊的有向圖 $G$(頂點從 $1 \sim n$ 編號),定義函數 $f(u, G)$:

1. 初始化返回值 $cnt = 0$,圖 $G' = G$。
2. 從 $1$ 至 $n$ 按順序枚舉頂點 $v$,如果當前的圖 $G'$ 中,從 $u$ 到 $v$ 與從 $v$ 到 $u$ 的路徑都存在,則將 $cnt + 1$,並在圖 $G'$ 中刪去頂點 $v$ 以及與它相關的邊。
3. 第 $2$ 步結束后,返回值 $cnt$ 即為函數值。

現在給定一張有向圖 $G$,請你求出 $h(G) = f(1, G) + f(2, G) + \cdots + f(n, G)$ 的值。

更進一步地,記刪除(按輸入順序給出的)第 $1$ 到 $i$ 條邊后的圖為 $G_i$($1 \le i \le m$),請你求出所有 $h(G_i)$ 的值。

$2 \le n \le {10}^3,1 \le m \le 2 \times {10}^5$

題解

送分題沒看出來。

考慮計算的過程,若 $u,v$ 在同一強聯通分量中那么將 $v$ 所連的邊刪去,再考慮 $v\leftarrow v+1$ 的情況。

事實上如果 $u,v$ 不在同一個強聯通分量中依然刪去 $v$ 所連的邊不會改變答案。證明考慮反證,若之后存在 $v'$ 借助沒有刪去的 $v$ 到達 $u$ ,那么 $u,v,v'$ 形成環,說明 $u$ 與 $v$ 是強聯通的,與假設不符。

由於答案求 $\sum_{u=1}^n f(u,G)$ ,考慮計算對於 $v$ 有多少個 $u$ 合法,事實上考慮 $(v,u)$ 的貢獻,顯然他貢獻的是一段前綴。

那么,我們考慮 $v$ 的貢獻是默認將小於 $v$ 的點刪去,而 $(v,u)$ 貢獻的值為從 $v$ 到 $u$ 的所有路徑的最大值與 $u$ 到 $v$ 的所有路徑的最大值的最小值。

該過程可以洪水填充 $bfs$ ,$u$ 到 $v$ 的可以建反圖。

時間復雜度 $\mathcal O(nm)$ 。 

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
#include<queue>
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
using namespace std;
inline int read(){
    int f=1,ans=0; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();}
    return f*ans;
}
const int MAXN=2e5+11;
int U[MAXN],V[MAXN],N,M,suf[MAXN];
vector<int> vec[2][MAXN]; int vis[2][MAXN];
queue<int> Que[2];
void bfs(int opt){
    while(!Que[opt].empty()){
        int xx=Que[opt].front(); Que[opt].pop();
        for(int i=0;i<vec[opt][xx].size();i++) {int v=vec[opt][xx][i]; if(!vis[opt][v]) vis[opt][v]=vis[opt][xx],Que[opt].push(v);}
    }return;
}
signed main(){
    freopen("graph.in","r",stdin);
    freopen("graph.out","w",stdout);
    N=read(),M=read(); for(int i=1;i<=M;i++) U[i]=read(),V[i]=read();
    for(int u=1;u<=N;u++){
        memset(vis,0,sizeof(vis)); for(int i=1;i<=N;i++) vec[0][i].clear(),vec[1][i].clear();
        vis[0][u]=vis[1][u]=M+1;
        for(int i=M;i>=1;i--)
            if(min(U[i],V[i])>=u){
                if(!vis[0][U[i]]) vec[0][U[i]].pb(V[i]);
                else if(!vis[0][V[i]])  Que[0].push(V[i]),vis[0][V[i]]=i,bfs(0);
                if(!vis[1][V[i]]) vec[1][V[i]].pb(U[i]);
                else if(!vis[1][U[i]]) Que[1].push(U[i]),vis[1][U[i]]=i,bfs(1);
            }
        for(int v=1;v<=N;v++){
            int w=min(vis[0][v],vis[1][v]); suf[w]++;
            //if(w) cerr<<"u:"<<u<<" v:"<<v<<endl;
        }
    }
    for(int i=M;i>=1;i--) suf[i]+=suf[i+1];
    for(int i=1;i<=M+1;i++) printf("%d ",suf[i]);printf("\n");
    return 0;
}/*
9 10
5 6
8 6
6 8
8 5
7 8
7 4
6 9
9 7
5 8
3 7
*/
View Code

寶石

題意

給定一個 $n$ 個結點的有根樹,其中根為 $1$ 。每個點有顏色 $w_i$ 。並且給定一個長度為 $m$ 的序列 $P$ 。

$q$ 次詢問,每次詢問 $u\rightarrow v$ 路徑最多能匹配多少個數。其中匹配的定義為必須匹配為當前元素后才能匹配下一個元素,一開始必須匹配 $P_1$ 。

$1\leq n,q\leq 2\times 10^5,1\leq m\leq 5\times 10^4$ 。

題解

由於 $p_i$ 互不相同,那么每個點匹配的位置均確定了。若詢問 $(u,v)$ 可以將其拆成 $(u,lca)$ 與 $(lca,v)$ 。對於 $(u,lca)$ 直接貪心倍增維護,而 $(v,lca)$ 可以先二分后貪心倍增維護。

那么我們僅需要一個查詢 $u$ 到根節點中第一個顏色為 $x$ 的位置,可以主席樹維護。 時間復雜度 $\mathcal O(q\log n\log m)$ 。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
using namespace std;
inline int read(){
    int f=1,ans=0; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();}
    return f*ans;
}
const int MAXN=2e5+11;
int N,M,p[MAXN],col[MAXN],tot,Q,head[MAXN],cnt;
struct Edge{int u,v,nex;}E[MAXN<<1]; 
struct Query{int u,v,id;}Que[MAXN];
void add(int u,int v){E[cnt].u=u,E[cnt].v=v,E[cnt].nex=head[u],head[u]=cnt++;return;}
int fa1[MAXN][18],fa2[MAXN][18],dep[MAXN],P[MAXN],Vis[MAXN],Fa[MAXN][18];
int SS[MAXN],TT[MAXN],rt[MAXN];
struct Segment{
    int ls[MAXN*21],rs[MAXN*21],tot,Pos[MAXN*21];
    void build(int &k,int l,int r){
        k=++tot; if(l==r) return;
        int mid=(l+r)>>1; build(ls[k],l,mid),build(rs[k],mid+1,r);
        return;
    }
    void Modify(int &p,int q,int l,int r,int ps,int w){
        p=++tot; ls[p]=ls[q],rs[p]=rs[q]; if(l==r){Pos[p]=w;return;}
        int mid=(l+r)>>1;
        if(ps<=mid) Modify(ls[p],ls[q],l,mid,ps,w);
        if(mid<ps) Modify(rs[p],rs[q],mid+1,r,ps,w);
        return;
    }
    int Query(int k,int l,int r,int ps){
        if(l==r) return Pos[k];
        int mid=(l+r)>>1;
        if(ps<=mid) return Query(ls[k],l,mid,ps); return Query(rs[k],mid+1,r,ps);
    }
}Seg;
void dfs(int u,int fath){
    dep[u]=dep[fath]+1;    Fa[u][0]=fath; Seg.Modify(rt[u],rt[fath],1,M,col[u],u);
    if(P[col[u]]){
        if(P[col[u]]==1) fa1[u][0]=u;
        else fa1[u][0]=Vis[p[P[col[u]]-1]];
        if(P[col[u]]==tot) fa2[u][0]=u;
        else fa2[u][0]=Vis[p[P[col[u]]+1]];
    }
    int pp=Vis[col[u]]; Vis[col[u]]=u;
    SS[u]=Vis[p[1]];
    for(int i=1;(1<<i)<=dep[u];i++) fa1[u][i]=fa1[fa1[u][i-1]][i-1],fa2[u][i]=fa2[fa2[u][i-1]][i-1],Fa[u][i]=Fa[Fa[u][i-1]][i-1];
    for(int i=head[u];i!=-1;i=E[i].nex){
        int v=E[i].v; if(v==fath) continue;
        dfs(v,u);
    }
    Vis[col[u]]=pp;
    return;
}
int Lca(int u,int v){
    if(dep[u]<dep[v]) swap(u,v); for(int i=17;i>=0;i--) if(dep[u]-(1<<i)>=dep[v]) u=Fa[u][i];
    if(u==v) return u; for(int i=17;i>=0;i--) if(Fa[u][i]!=Fa[v][i]) u=Fa[u][i],v=Fa[v][i];
    return Fa[u][0];
}
int lca,p1,S;
bool calc(int u,int v,int k){
    int T=Seg.Query(rt[v],1,M,p[k]);
    int p2=T; if(dep[T]<dep[lca]) p2=0; for(int i=17;i>=0;i--) if(dep[fa1[p2][i]]>=dep[lca]) p2=fa1[p2][i];
    int len1=0,len2=0;
    if(p1) len1=P[col[p1]]; if(p2) len2=k-P[col[p2]]+1;
    int l1=1,r1=len1,r2=k,l2=k-len2+1;
    r1=min(r1,k);
    if(l1>r1){if(l2==1) return 1;return 0;}
    if(l2>r2){if(r1==k) return 1;return 0;}
    int len=r1-l1+1+r2-l2+1;
    if(r1>=l2) len-=(r1-l2+1); 
    return len==k;
}
int main(){
    //freopen("B.in","r",stdin);
    freopen("gem.in","r",stdin);
    freopen("gem.out","w",stdout);
    memset(head,-1,sizeof(head));
    N=read(),M=read(),tot=read();
    for(int i=1;i<=tot;i++) p[i]=read(),P[p[i]]=i;
    for(int i=1;i<=N;i++) col[i]=read();
    bool ff=1;
    for(int i=1;i<N;i++){int u=read(),v=read();ff&=((u+1)==v);add(u,v),add(v,u);}
    Seg.build(rt[0],1,tot); 
    Q=read(); for(int i=1;i<=Q;i++) Que[i].u=read(),Que[i].v=read(),Que[i].id=i;
    dfs(1,0); 
    for(int i=1;i<=Q;i++){
        int u=Que[i].u,v=Que[i].v; lca=Lca(u,v); S=SS[u];
        p1=S; if(dep[S]<dep[lca]) p1=0; for(int i=17;i>=0;i--) if(dep[fa2[p1][i]]>=dep[lca]) p1=fa2[p1][i];
        S=SS[u]; 
        int l=1,r=tot,res=0;
        while(l<=r){
            int mid=(l+r)>>1;
            if(calc(u,v,mid)) res=mid,l=mid+1;
            else r=mid-1;
        }
        printf("%d\n",res);
    }return 0;
}/*
10 5 5
1 5 2 4 3 
4 5 1 5 1 1 2 4 2 4 
1 2
2 3
2 4
4 5
3 6
1 7
3 8
7 9
1 10
1
9 9
*/
View Code

滾榜

題意

Alice 圍觀了一場 ICPC 競賽的滾榜環節。本次競賽共有 $n$ 支隊伍參賽,隊伍從 $1 \sim n$ 編號,$i$ 號隊伍在封榜前通過的題數為 $a_i$。排行榜上隊伍按照過題數從大到小進行排名,若兩支隊伍過題數相同,則編號小的隊伍排名靠前。

滾榜時主辦方以 $b_i$ 不降的順序依次公布了每支隊伍在封榜后的過題數 $b_i$(最終該隊伍總過題數為 $a_i + b_i$),並且每公布一支隊伍的結果,排行榜上就會實時更新排名。Alice 並不記得隊伍被公布的順序,也不記得最終排行榜上的排名情況,只記得每次公布后,本次被公布結果的隊伍都成為了新排行榜上的第一名,以及所有隊伍在封榜后一共通過了 $m$ 道題(即 $\sum_{i = 1}^{n} b_i = m$)。

現在 Alice 想請你幫她算算,最終排行榜上隊伍的排名情況可能有多少種。

$1\leq n\leq 13,1\leq m\leq 500$ 。

題解

一個顯然的 $\mathcal O(n!\cdot n^2)$ 的暴力是貪心 $b_i$ ,每次均正好超過第一名。注意到每次他都會成為第一名那么可以將其優化到 $\mathcal O(n!)$ 。

那么若 $i<j$ ,且先選擇 $i$ 后選擇了 $j$ ,那么 $a_i+b_i<a_j+b_j,b_j-b_i\leq 0$ ,可以推出 $max(0,a_i-a_j+1)\leq b_j-b_i$ 。同理得 $i>j$ 。

那么我們每次僅需要維護 $b_j-b_i$ ,設計 $f_{S,i,j}$ 表示當前選取狀態為 $S$ ,其中最后一個人為 $i$ ,且當前和為 $j$ ,每次枚舉下一次選什么。

時間復雜度 $\mathcal O(2^n\cdot n^2\cdot m)$ 。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
#include<queue>
#define LL long long
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
using namespace std;
inline int read(){
    int f=1,ans=0; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();}
    return f*ans;
}
const int MAXN=14;
const int MAXM=511;
LL f[1<<13][MAXN][MAXM];
int N,M,A[MAXN],siz[1<<13],Log[1<<13];
int lowbit(int x){return x&-x;}
signed main(){
    //freopen("B.in","r",stdin);
    freopen("ranklist.in","r",stdin);
    freopen("ranklist.out","w",stdout);
    N=read(),M=read(); for(int i=1;i<=N;i++) A[i]=read(); Log[0]=-1;
    for(int i=1;i<(1<<N);i++) siz[i]=siz[i>>1]+(i&1),Log[i]=Log[i>>1]+1;
    for(int i=1;i<=N;i++){
        int Maxn1=-1,Maxn2=-1; for(int j=1;j<i;j++) Maxn1=max(Maxn1,A[j]); for(int j=i+1;j<=N;j++) Maxn2=max(Maxn2,A[j]);
        int nw=A[i]; if(nw<=Maxn1) nw=Maxn1+1; if(nw<Maxn2) nw=Maxn2;
        if((nw-A[i])*N<=M) f[1<<(i-1)][i][(nw-A[i])*N]=1;
    }
    for(int S=1;S<(1<<N);S++){
        int p=S; while(p){
            int i=lowbit(p); p-=i; i=Log[i]+1;
            for(int j=0;j<=M;j++) if(f[S][i][j]){
                for(int k=1;k<=N;k++) if(!(S&(1<<(k-1)))){
                    if(k<i){
                        int del=max(0,A[i]-A[k])*(N-siz[S]);
                        if(del+j<=M) f[S|(1<<(k-1))][k][del+j]+=f[S][i][j];
                    }else{
                        int del=max(0,A[i]-A[k]+1)*(N-siz[S]);
                        if(del+j<=M) f[S|(1<<(k-1))][k][del+j]+=f[S][i][j];
                    }
                }
            }
        }
    } LL Ans=0;
    for(int i=1;i<=N;i++) for(int j=0;j<=M;j++) if(f[(1<<N)-1][i][j]) Ans+=f[(1<<N)-1][i][j];
    printf("%lld\n",Ans); return 0;
}/*
3 6
1 2 1
*/
View Code

支配

咕咕咕。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM