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

支配

咕咕咕。

 

posted @ 2021-04-15 21:37  siruiyang_sry  阅读(493)  评论(4编辑  收藏  举报