亿些防不住 AK 的水题(Part II)

CF601E A Museum Robbery

题意

洛谷的翻译极其**

\(n\) 个物品依次加入一个博物馆,第 \(i\) 次加入的物品体积为 \(w_i\),价值为 \(v_i\)

现在有 \(q\)顺序进行的操作,每次操作可能如下:

  • 加入一个体积为 \(w\),价值为 \(v\) 的物品。
  • 删除第 \(x\) 次加的物品,保证其存在。
  • \(s(m)\) 为容积为 \(m\) 的背包能装下的物品的最大价值,求 \((\sum_{m=1}^ks(m)(10^7+19)^{m-1})\text{ mod }(10^9+7)\)

\(n\le 5\cdot 10^3,w,k\le 10^3,v\le 10^6,q\le 3\cdot 10^4\)。其中保证存在操作 \(3\) 且操作 \(1\) 最多存在 \(10^4\) 次。

解法

可以将所有操作看成一段时间轴,然后每个物品造成的影响的操作即可以抽象成时间轴上的区间的一段操作。

考虑使用线段树分治,在整个时间轴上建一棵线段树,在每个物品影响的时间区间对应在线段树上的 \(O(\log q)\) 个子节点上插入这些物品(可以对每个节点建一个 std::vector),最后 dfs 一遍整棵线段树即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxl=16;
const int maxk=1010;
const int maxn=15010;
const int maxq=30010;
const ll P=10000019;
const ll Q=1000000007;
int n,k,a,i,q,o,j; 
int lt[maxn],dp[maxl][maxk];
int s[maxq],v[maxn],w[maxn];
ll ans,pw[maxk];
struct seg{
    int l,r,m;bool c;
    vector<int> it;
}tr[maxq<<2];
#define l(p) tr[p].l
#define r(p) tr[p].r
#define m(p) tr[p].m
#define c(p) tr[p].c
#define ls(p) p<<1
#define rs(p) p<<1|1
#define it(p) tr[p].it
void Build(const int p,const int l,const int r){
    l(p)=l;r(p)=r;
    m(p)=(l+r)>>1;
    if(l(p)==r(p)) return;
    Build(ls(p),l,m(p));
    Build(rs(p),m(p)+1,r);
}
void Add(const int p,const int l,const int r){
    if(l<=l(p)&&r>=r(p)){
        it(p).push_back(a);
        return;
    }
    if(l<=m(p)) Add(ls(p),l,r);
    if(r>m(p)) Add(rs(p),l,r);
}
void Dfs(const int p,const int c){
    if(!c(p)) return;
    int e=c;
    if(!it(p).empty()){
        memcpy(dp[++e],dp[c],sizeof(dp[c]));
        for(int f:it(p))
            for(i=k-v[f];i>=0;--i)
                dp[e][i+v[f]]=max(dp[e][i+v[f]],dp[e][i]+w[f]);
    }
    if(l(p)==r(p)){
        ans=0;
        for(i=1;i<=k;++i){
            ans+=pw[i]*dp[e][i];
            if(ans>=Q) ans%=Q;
        }
        s[l(p)]=ans;
        return;
    }
    Dfs(ls(p),e);
    Dfs(rs(p),e);
}
int main(){
    memset(s,-1,sizeof(s));
    scanf("%d%d",&n,&k);
    pw[1]=1;
    for(i=2;i<=k;++i) pw[i]=(pw[i-1]*P)%Q;
    for(i=1;i<=n;++i) scanf("%d%d",w+i,v+i);
    scanf("%d",&q);
    Build(1,0,q);
    for(i=1;i<=q;++i){
        scanf("%d",&o);
        if(o==3){
            a=1;
            for(;;){
                c(a)=1;
                if(l(a)==r(a)) break;
                if(i<=m(a)) a=ls(a);
                else a=rs(a);
            }
            continue;
        }
        if(o==1){
            ++n;
            scanf("%d%d",w+n,v+n);
            lt[n]=i;
        }
        else{
            scanf("%d",&a);
            Add(1,lt[a],i);
            lt[a]=-1;
        }
    }
    for(a=1;a<=n;++a) if(~lt[a]) Add(1,lt[a],q);
    Dfs(1,0);
    for(i=1;i<=q;++i) if(~s[i]) printf("%d\n",s[i]);
    return 0;
}

CF249D Donkey and Stars

题意

坐标系上给出 \(n\) 个点,第 \(i\) 个点为 \((x_i,y_i)\) 且这些点两两不同。求最多有多少个点依次连成的折线上的线段的斜率在 \(\frac ab\)\(\frac cd\) 之间(不包括 \(\boldsymbol{\frac ab}\) \(\boldsymbol{\frac cd}\))。折线必须从坐标原点开始且坐标原点不计入答案。\(1\le n,x_i,y_i\le 10^5,\boldsymbol{0\le}\ a,b,c,d\le 10^5,\frac ab<\frac cd\)

解法

考虑把两个点的斜率用另一种方式表示:\(i\)\(j\) 之间的斜率在 \(\frac ab\)\(\frac cd\) 之间可以表示如下:

定义向量 \(\overrightarrow {e_1}=(b,a),\overrightarrow {e_2}=(d,c)\),同时将 \(\overrightarrow{ij}\) 表示为 \(a\overrightarrow{e_1}+b\overrightarrow{e_2}\)(其中 \(a\)\(b\) 为常数),则从 \(i\) 点到 \(j\) 点能延伸一条折线当且仅当 \(a>0,b>0\)。证明显然。

同时令原点为 \(O\),则 \(\overrightarrow{ij}=\overrightarrow{Oj}-\overrightarrow{Oi}\)。故而可以对于第 \(i\) 个点,处理 \(A_i,B_i\) 满足 \(\overrightarrow{Oi}=A_i\overrightarrow{e_1}+B_i\overrightarrow{e_2}\)。可得 \(B_i=\frac{y_ib-x_ia}{cb-ad},A_i=\frac{x_ic-y_id}{cb-ad}\)。(注意有解对应了 \(\frac cd>\frac ab\),也就是 \(cb>ad\),故而可以只需要维护 \(y_ib-x_ia\)\(x_ic-y_id\) 之间的大小关系)这样可以令 \(dp_i\)\(O\) 点到 \(i\) 点的最长折线长度,则转移方程有 \(dp_i=\max_{A_i-A_j>0\text{ and }B_i-B_j>0}(dp_j)+1\)。直接用树状数组统计二维偏序即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
int n,i,a,b,c,d,x,y; 
struct Vals{
    long long val;
    int idx;
    inline bool operator <(const Vals &p)const{return val<p.val;}
}A[maxn],B[maxn];
struct node{
    int ra,rb;
    inline bool operator <(const node &p)const{
        if(ra!=p.ra) return ra<p.ra;
        return rb<p.rb; 
    }
}N[maxn];
int C[maxn],dp[maxn];
inline void Add(int p,const int &v){
    for(;p<maxn;p+=(p&-p)) C[p]=max(C[p],v);
}
inline int Que(int p){
    int ret=0;
    for(;p;p&=(p-1)) ret=max(ret,C[p]);
    return ret;
}
long long lst;
int main(){
    scanf("%d",&n);
    scanf("%d/%d%d/%d",&a,&b,&c,&d);
    for(i=1;i<=n;++i){
        scanf("%d%d",&x,&y);
        A[i].idx=B[i].idx=i;
        B[i].val=(1LL*y*b)-(1LL*x*a);
        A[i].val=(1LL*x*c)-(1LL*y*d);
    }
    sort(A+1,A+n+1);sort(B+1,B+n+1);
    for(i=1;A[i].val<=0;++i);
    x=1;lst=A[i].val; 
    for(;i<=n;++i){
        while(A[i].val==lst) N[A[i++].idx].ra=x;
        lst=A[i].val;N[A[i].idx].ra=++x;
    }
    for(i=1;B[i].val<=0;++i);
    x=1;lst=B[i].val;
    for(;i<=n;++i){
        while(B[i].val==lst) N[B[i++].idx].rb=x;
        lst=B[i].val;N[B[i].idx].rb=++x;
    }
    sort(N+1,N+n+1);x=1;a=0;
    for(i=1;!N[i].ra;++i);
    for(;i<=n;++i){
        if(N[i].ra!=N[x].ra) for(;x<i;++x) if(N[x].rb) Add(N[x].rb,dp[x]);
        if(N[i].rb) a=max(a,dp[i]=Que(N[i].rb-1)+1);
    }
    printf("%d",a);
    return 0;
}

CF547D Mike and Fish

题意

给定 \(n\) 个整点,第 \(i\) 个整点为 \((x_i,y_i)\),你要给每个点染成红色或蓝色。要求同一水平线或垂直线上两种颜色的数量最多相差 \(1\)。保证答案存在。\(n,x_i,y_i\le 2\times 10^5\)

解法

考虑建一张无向图,将每个点 \((x,y)\) 抽象成一条从“第 \(x\) 行代表的点”到“第 \(y\) 列代表的点”的无向边。然后一种合法方案相当于对这些边定向,使得每个点的入度和出度之差不超过 \(1\)

这个图中会有一些奇数度数的点,可以发现无向图中奇数度数的点的数量一定是偶数。同时可以发现从一个奇数度数的点一定存在一条到另一个奇数度数的点的路径(其一定不会在一个偶数度数的点上无法继续延伸路径)。

于是可以把奇数度数的点用 std::set 等存下来,每次选 std::set 中一个点通过 dfs 和另一个点配对。这样整个图只剩偶数度数的点,同时原来的奇数度数的点一定只有一条边定向。然后跑一遍欧拉回路即可。

不过需要注意数据可能为下面的情况:

114514
114514 1
114513 1
…………
2 1
1 1

这样单是对于奇数点配对的方案,传统存图的时间复杂度就高达 \(O(n^2)\)。一个好方法是维护邻接表中每条边的上一条边,利用形如链表删除的方式删边。注意双向边所连接的两个点都要删去这条边。

另外一个做法是:把同一行中第一个点和第二个点连无向边,第三个点和第四个点连无向边……若这一行的点有奇数个,则不用管最后一个点。同一列同样如此。

可以发现图中不可能存在奇环。然后对整个无向图跑一边二分图染色,每一个点的颜色即为其在方案中对应的颜色。此时由于每条边上的点的颜色不同,故而每行/每列上的两种颜色的点的数量差不超过 \(1\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxd=200000;
const int maxn=400010;
int n,x,y,i,tot=1; 
int h[maxn],ver[maxn],pre[maxn],nxt[maxn];
int deg[maxn],val[maxn];
bool vis[maxn];
set<int> s;
int dfs(int p,bool d,int fe){
    if(deg[p]&1){
        nxt[pre[fe]]=nxt[fe];
        pre[nxt[fe]]=pre[fe];
        if(fe==h[p]) h[p]=nxt[fe];
        return p;
    }
    for(int to,ret,lp=h[p];lp;lp=nxt[lp]){
        if(~val[lp>>1]) continue;
        to=ver[lp];
        ret=dfs(to,val[lp>>1]=!d,p,lp^1);
        if(ret){
            nxt[pre[lp]]=nxt[lp];
            pre[nxt[lp]]=pre[lp];
            if(lp==h[p]) h[p]=nxt[lp];
            if(fe){
                nxt[pre[fe]]=nxt[fe];
                pre[nxt[fe]]=pre[fe];
                if(fe==h[p]) h[p]=nxt[fe];
            }
            return ret;
        }
        val[lp>>1]=-1;
    }
    return 0;
}
void euler(int p,bool d){
    for(int &lp=h[p];lp;lp=nxt[lp]){
        if(~val[lp>>1]) continue;
        euler(ver[lp],val[lp>>1]=!d);
    }
}
int main(){
    memset(val,-1,sizeof(val));
    scanf("%d",&n);
    for(i=1;i<=n;++i){
        scanf("%d%d",&x,&y);
        y+=maxd;
        ver[++tot]=y;
        nxt[tot]=h[x];
        pre[h[x]]=tot;
        h[x]=tot;
        ver[++tot]=x;
        nxt[tot]=h[y];
        pre[h[y]]=tot;
        h[y]=tot;
        ++deg[x];++deg[y];
    }
    for(i=1;i<maxn;++i) if(deg[i]&1) s.insert(i);
    while(!s.empty()){
        deg[x=*s.begin()]=0;
        s.erase(x);y=dfs(x,0,0);
        deg[y]=0;s.erase(y);
    }
    for(i=1;i<maxn;++i) euler(i,0);
    for(i=1;i<=n;++i) putchar(val[i]?'r':'b');
    return 0;
}
点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
const int maxt=800010;
int n,i,x,y,tot;
int h[maxn],ver[maxt],nxt[maxt];
int lx[maxn],ly[maxn],col[maxn];
void Add(int v){
    ver[++tot]=v;nxt[tot]=h[i];h[i]=tot;
    ver[++tot]=i;nxt[tot]=h[v];h[v]=tot;
}
void Color(int p){
    for(int to,lp=h[p];lp;lp=nxt[lp]){
        if(~col[to=ver[lp]]) continue;
        col[to]=!col[p];Color(to);
    }
}
int main(){
    memset(col,-1,sizeof(col));
    scanf("%d",&n);
    for(i=1;i<=n;++i){
        scanf("%d%d",&x,&y);
        if(lx[x]) Add(lx[x]),lx[x]=0;
        else lx[x]=i;
        if(ly[y]) Add(ly[y]),ly[y]=0;
        else ly[y]=i;
    }
    for(i=1;i<=n;++i){
        if(col[i]<0) col[i]=0,Color(i);
        putchar(col[i]?'r':'b');
    }
    return 0;
}

ABC232G Modulo Shortest Path

题意

有一张 \(n\) 个点的有向完全图,\(\forall i,j\),第 \(i\) 个点和第 \(j\) 个点之间的边权为 \((a_i+b_j)\bmod M\)。求 \(1\)\(n\) 的最短路。\(n\le 2\cdot 10^5,a_i,b_i\le M\le 10^9\)

解法

考虑将 \((a_i+b_j)\bmod M\) 拆分成两部分,以此建出边数为 \(O(n)\) 的图。

一个想法是把所有点按 \(b\) 值降序排序,同时新建 \(n\) 个虚拟节点(编号为 \(n+1\sim 2n\));然后对于第 \(i\) 个点,向 \(2n\) 条边权为 \(a_i\) 的边;对于第 \(j\) 个点,从 \(n+j\)\(j\) 连一条边权为 \(b_j\) 的边。同时对于 \(\forall i\in[1,n-1]\),从 \(n+i+1\)\(n+i\) 连一条边权为 \(0\) 的边。这样就可以处理边权为 \(a_i+b_j\) 的情况。

考虑 \(a_i+b_j\ge M\) 的情况。一种方法是二分出最后一个满足 \(a_i+b_j\ge M\) 的点 \(j\),然后连一条从 \(i\)\(n+j\) 且边权为 \(a_i-M\) 的边。但是这样做会导致负权边。GaryH 说可以用 \(\require{cancel}\xcancel{O(n\sqrt n\log n)}\) 的大常数做法 \(\require{cancel}\xcancel{O(n\log^8n)}\)的做法

考虑另外一种方法。可以对于 \(\forall i\in[1,n-1]\),从 \(n+i\)\(n+i+1\) 连一条边权为 \(b_i-b_{i+1}\) 的边。同样对于 \(\forall i\),需要从 \(n+i\)\(i\) 连一条边权为 \(0\) 的边,从 \(i\)\(2n\) 连一条边权为 \(a_i\) 的边。但是处理 \(a_i+b_j\ge M\) 的情况时,可以对于 \(\forall i\),同样二分查找最后一个满足 \(a_i+b_j\ge M\)\(j\),然后从 \(i\)\(n+j\) 连一条边权为 \(a_i+b_j-M\) 的边。这样总边数即为 \(O(n)\) 了。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
const int maxb=400010;
const int maxt=1600010;
#define ll long long
int n,i,u,v,x,M,tot; 
int h[maxb],nxt[maxt],ver[maxt],edg[maxt];
bool vis[maxb];
ll w,dis[maxb];
priority_queue<pair<ll,int> > q;
struct node{
    int a,b,idx;
    inline bool operator <(const node &a)const{return b>a.b;}
}N[maxn];
node *p=N+1;
inline void Add(const int &a,const int &b,const int &c){
    ver[++tot]=b;edg[tot]=c;
    nxt[tot]=h[a];h[a]=tot;
}
int main(){
    memset(dis,0x3f,sizeof(dis));
    scanf("%d%d",&n,&M);
    for(i=1;i<=n;++i,++p){
        scanf("%d",&p->a);
        p->idx=i;
    }
    p=N+1;
    for(i=1;i<=n;++i,++p) scanf("%d",&p->b); 
    sort(N+1,N+n+1);v=n<<1|1;
    for(i=1;i<=n;++i){
        if(N[i].idx==1){
            dis[i]=0;
            q.push(make_pair(0,i));
        }
        else if(N[i].idx==n) x=i;
        Add(n+i+1,n+i,N[i].b-N[i+1].b);
        p=upper_bound(N+1,N+n+1,(node){0,M-N[i].a,0});
        if(p>N+1){
            --p;
            if(p->b+N[i].a>=M) Add(i,p-N+n,N[i].a+p->b-M);
        }
        Add(i+n,i,0);Add(i,v,N[i].a);
    }
    while(!q.empty()){
        u=q.top().second;q.pop();
        if(vis[u]) continue;
        vis[u]=1;w=dis[u];
        for(i=h[u];i;i=nxt[i]){
            v=ver[i];
            if(w+edg[i]<dis[v]){
                dis[v]=w+edg[i];
                q.push(make_pair(-dis[v],v));
            }
        }
    }
    printf("%lld",dis[x]);
    return 0;
}

ABC134F Permutation Oddness

题意

定义一个 \(1 \sim n\) 的排列 \(p\) 的怪异度为 \(\sum_{i=1}^n|p_i-i|\)。求怪异度为 \(k\)\(1 \sim n\) 的排列数,答案对 \(10^9+7\) 取模。\(n\le 50,k\le n^2\)

解法

考虑把 \(1\sim n\) 的排列看成是左部点和右部点编号均为 \(1\sim n\) 的二分图的最大匹配,其中左部点和右部点分别为值和下标。

考虑用 dp 顺序维护左右部点均在 \(1\sim i\) 的子图的数量和部分怪异度。把 \(|p_i-i|\) 拆开可得 \([p_i>i](p_i-i)+[p_i\le i](i-p_i)\)

\(dp_{i,j,k,l}\)\(1\sim i\) 的子图中,左部点有 \(j\) 个未匹配,右部点为 \(k\) 个未被匹配,且目前计入答案的怪异度为 \(l\) 的匹配个数。为了方便,我们只考虑某个点 \(i\) 和下标不大于 \(i\) 的点配对的情况。可以分别计算 \(|p_i-i|\) 的贡献。若某个点 \(i\) 和编号更大的点配对,则会对怪异度有 \(-i\) 的贡献,若某个点 \(i\) 和编号更小的点配对,则会对怪异度有 \(i\) 的贡献。转移有以下几种:

  • 左右部第 \(i+1\) 个点互相匹配。此时 \(dp_{i+1,j,k,l}\leftarrow dp_{i+1,j,k,l}+dp_{i,j,k,l}\)
  • 左部第 \(i+1\) 个点和右部第 \(1\sim i\) 个点匹配,右部第 \(i+1\) 个点和左部第 \(i+2\sim n\) 个点匹配。此时有 \(dp_{i+1,j,k,l}\leftarrow dp_{i+1,j,k,l}+dp_{i,j,k,l}k\)
  • 左部第 \(i+1\) 个点和右部第 \(i+2\sim n\) 个点匹配,右部第 \(i+1\) 个点和左部第 \(1\sim i\) 个点匹配。同样有 \(dp_{i+1,j,k,l}\leftarrow dp_{i+1,j,k,l}+dp_{i,j,k,l}j\)
  • 左右部第 \(i+1\) 个点都和第 \(i+2\sim n\) 个点匹配。此时有 \(dp_{i+1,j,k,l}\leftarrow dp_{i+1,j,k,l}+dp_{i,j-1,k-1,l+2(i+1)}\)
  • 左右部第 \(i+1\) 个点都和第 \(1\sim i\) 个点匹配。此时有 \(dp_{i+1,j,k,l}\leftarrow dp_{i+1,j,k,l}+dp_{i,j+1,k+1,l-2(i+1)}(j+1)(k+1)\)

综上,我们有了全部的的四种转移形式,但是时间和空间复杂度高达 \(O(n^5)\),内存访问很不连续,难以卡常优化。

考虑 dp 的初值,显然有 \(dp_{0,0,0,0}=1\)。同时可以发现每一个 \(dp_{i,j,k}\) 均只会向 \(dp_{i+1,j+1,k+1}\)\(dp_{i+1,j-1,k-1}\)\(dp_{i+1,j,k}\) 进行转移,故而可以发现对于所有的匹配,均有未匹配的左部点和右部点个数相等。故而可以合并 \(j,k\) 两维为一维,时空复杂度就变成了 \(O(n^4)\)

注意:\(l\) 可以为负数。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=55;
const int maxm=2700;
const int max2=5500;
const int md=1000000007;
int n,i,j,k,i2;
int dp[maxn][maxn][max2];
long long t;
signed main(){
    dp[0][0][maxm]=1;
    scanf("%d",&n); 
    for(i=1;i<=n;++i){
        i2=i*(i+1);
        for(j=0;j<=i;++j){
            for(k=-i2;k<=i2;++k){
                t=dp[i-1][j+1][k-(i<<1)+maxm];
                t*=(j+1)*(j+1);
                t+=dp[i-1][j][k+maxm];
                if(t>=md) t%=md;dp[i][j][k+maxm]=t;
            }
        }
        for(j=1;j<=i;++j){
            for(k=-i2;k<=i2;++k){
                t=dp[i-1][j][k+maxm];
                t*=(j<<1);
                t+=dp[i][j][k+maxm];
                t+=dp[i-1][j-1][k+(i<<1)+maxm];
                if(t>=md) t%=md;
                dp[i][j][k+maxm]=t;
            }
        }
    }
    scanf("%d",&k);
    printf("%d",dp[n][0][k+maxm]);
    return 0;
}

P4249 WC2007 石头剪刀布 & CF1264E Beautiful League

题意

有一张 \(n\) 个点的 竞赛图,有一些边已经定向。请给剩下的边定向,使得图中三元环数量最大。只需要求一组合法方案。WC2007:求三元环数量的最大值,\(n\le 100\)。CF1264E:\(n\le 50\)

解法

显然这张竞赛图的三元环数量为 \(\binom n3-\sum_{i=1}^n\binom{d_i}2\)(其中 \(d_i\)\(i\) 点的入度)。任意三个点之间不能构成三元环当且仅当另外两个点存在连向某个点的边。

\(d_i\) 抽象为 \(i\) 点对应的流入的流量,则可以构造一个最小费用最大流模型:

  • 对于定向的边 \(\overrightarrow{ij}\),从源点向 \(j\) 连一条容量为 \(1\) 费用为 \(0\) 的边,表示 \(j\) 入度(流入量)加上了 \(1\)
  • 对于不定向的边 \(\overleftrightarrow{ij}\),从源点向 \(i\) 连一条容量为 \(1\) 费用为 \(0\) 的边,同时从 \(i\)\(j\) 连一条容量为 \(1\) 费用为 \(0\) 的边,表示 \(i\)\(j\) 的入度(流入量)必须有且只有一者加上 \(1\)
  • 同时对于 \(\forall i\),考虑用某个内容表示 \(\binom{d_i}2\)。可以发现 \(y=\binom x2\) 是凸函数,并且需要求 \(\min_{i=1}^n\binom{d_i}2\);同时有 \(\binom 12=0,\forall d_i\ge 2,\binom{d_i}2-\binom{d_i-1}2=\frac{d_i(d_i-1)}2-\frac{(d_i-1)(d_i-2)}2=d_i-1\);故而可以从 \(i\) 点向汇点连 \(n-1\) 条边(\(i\) 点的入度显然不超过 \(n-1\)),这些边的容量为 \(1\),费用分别为 \(0,1,2,\cdots,n-2\)。同时跑一遍最小费用最大流;则 \(i\) 点流入量一定会从费用分别为 \(0,1,\cdots,d_i-1\) 的边流至汇点,带来 \(\sum_{i=0}^{d_i-1}i=\binom{d_i}2\) 的费用;并且最小费用即为 \(\min_{i=1}^n\binom{d_i}2\)。(p.s. 费用为流量的某种凸函数/凹函数且需要求费用的最小/最大值时,可以用类似的方式构造,不过可以把斜率相同的一段看成一条边)

至于构造方案,可以构造一个二维数组 \(W\)\(W_{i,j}=1\) 代表存在 \(i\rightarrow j\) 的边)。对于定向的边 \(\overrightarrow{ij}\),预先把 \(W_{i,j}\) 设为 \(1\);对于不定向的边 \(\overleftrightarrow{ij}\),考虑其在网络流建出的图中的对应性质。若建出的图中存在 \(\overrightarrow{ji}\;(1\le i,j\le n)\) 边未分配流量,则 \(d_j\) 由于 \(\overleftrightarrow{ij}\) 边而增加了 \(1\);故而对应的有向边为 \(\overrightarrow{ij}\),把 \(W_{i,j}\) 设为 \(1\)。时间复杂度为 \(O(\texttt{能过})\)

代码实现上可能有较大出入,但是原理相同。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=110;
const int maxm=40250;
const int INF=1145141919;
int n,i,j,l,r,u,v,t,o,ans,tot=1;
int h[maxn],fl[maxm],cs[maxm],nxt[maxm],ver[maxm];
int d[maxn],c[maxn],q[maxn*maxm];
bool vis[maxn],win[maxn][maxn];
inline void Add(const int &x,const int &y,const int &f,const int &c){
    ver[++tot]=y;fl[tot]=f;cs[tot]=c;nxt[tot]=h[x];h[x]=tot;
    ver[++tot]=x;fl[tot]=0;cs[tot]=-c;nxt[tot]=h[y];h[y]=tot;
}
int dinic(const int &p,int f){
    if(p==t) return f;
    int to,fa=0,nw;vis[p]=1;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!fl[lp]) continue; to=ver[lp];
        if(vis[to]||d[to]!=d[p]+cs[lp]) continue;
        nw=dinic(to,min(f,fl[lp]));
        if(!nw) continue;
        f-=nw;fa+=nw;ans+=nw*cs[lp];
        fl[lp]-=nw;fl[lp^1]+=nw;
        if(!f) break;
    }
    if(!fa) d[p]=INF;
    vis[p]=0;return fa;
}
int main(){
    scanf("%d",&n);t=n+1;
    for(i=1;i<=n;++i){
        for(j=1;j<=n;++j){
            scanf("%d",&o);
            Add(i,t,1,j-1);
            if(i==j||!o) continue;
            if(o==1){
                Add(0,i,1,0);
                win[i][j]=1;
            }
            else if(i<j){
                Add(0,i,1,0);
                Add(i,j,1,0);
            }
        }
    }
    for(;;){
        for(i=0;i<=t;++i){
            d[i]=INF;
            c[i]=h[i];
        }
        d[0]=l=r=0;
        while(l<=r){
            u=q[l++];vis[u]=0;
            for(i=h[u];i;i=nxt[i]){
                if(!fl[i]) continue;
                v=ver[i];
                if(d[v]>d[u]+cs[i]){
                    d[v]=d[u]+cs[i];
                    if(!vis[v]){
                        vis[v]=1;
                        q[++r]=v;
                    }
                }
            }
        }
        if(d[t]==INF) break;
        dinic(0,INF);
    }
    printf("%d\n",n*(n-1)*(n-2)/6-ans);
    for(i=1;i<=n;++i){
        for(j=h[i];j;j=nxt[j]){
            if(!fl[j]) continue;
            win[i][ver[j]]=1;
        }
        for(j=1;j<=n;++j) printf("%d ",win[i][j]);
        putchar('\n');
    }
    return 0;
}

CF1591E Frequency Queries

题意

有一棵 \(n\) 个点的有根树,每个点有一个权值 \(a_i\),且给出每个点的父节点 \(p_i\)

\(q\) 个询问如 v l k 所示。你需要进行如下操作来得到询问的答案:

  1. 写下根节点到点 \(v\) 路径上的所有点权(包括根节点和 \(v\) 点)。
  2. 去掉其中出现次数小于 \(l\) 的数。
  3. 按出现次数将剩下的数升序排序。
  4. 答案为去重后的第 \(k\) 个数。如果不足 \(k\) 个数,答案为 -1

如果有出现次数相同的数,你可以将它们按任意顺序排序。

本题有多组数据,在输入的第一行以 \(t\) 给出。

数据范围:\(1 \leq t,n,q,\sum n,\sum q \leq 10^6,1 \leq a_i,p_i,v,l,k \leq n\)

解法

给出一种只需要平衡树的做法。但是码长 3.2 k

把所有值按照出现次数建树,在 dfs 的过程中找到需要的节点/新建节点,然后改变出现次数,最后插回平衡树中。查询就直接在出现次数不小于 \(l\) 的部分查找第 \(k\) 个值即可。

这里使用 FHQ Treap(Splay 也可以),支持中断/拼接区间。

细节见代码。理论上它会常数巨大但是实际上不那么大

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000010;
int t,n,i,v,q;
int h[maxn],nxt[maxn],col[maxn];
int lq[maxn],hq[maxn],nq[maxn],aq[maxn],kq[maxn];
mt19937 Rand(time(0));
struct node{
    int cnt,val,ls,rs,fa,siz;
    unsigned key;
}tr[maxn<<1];
int tot,root;
int idx[maxn];
#define cnt(p) tr[p].cnt
#define siz(p) tr[p].siz
#define val(p) tr[p].val
#define ls(p) tr[p].ls
#define rs(p) tr[p].rs
#define fa(p) tr[p].fa
#define key(p) tr[p].key
inline void Init(){tot=root=0;}
inline int New(const int &v){
    cnt(++tot)=siz(tot)=1;val(tot)=v;
    idx[v]=tot;key(tot)=Rand();
    fa(tot)=ls(tot)=rs(tot)=0;
    return tot;
}
inline void Pushup(const int &p){
    siz(p)=1;
    if(ls(p)){
        siz(p)+=siz(ls(p));
        fa(ls(p))=p;
    }
    if(rs(p)){
        siz(p)+=siz(rs(p));
        fa(rs(p))=p;
    }
}
void vSplit(const int p,const int &s,int &x,int &y){
    if(!p){
        x=y=0;
        return;
    }
    fa(ls(p))=fa(rs(p))=0; 
    if(cnt(p)<=s){
        x=p;
        vSplit(rs(p),s,rs(p),y);
    }
    else{
        y=p;
        vSplit(ls(p),s,x,ls(p));
    }
    Pushup(p);
}
void sSplit(const int p,const int s,int &x,int &y){
    if(!p){
        x=y=0;
        return;
    }
    fa(ls(p))=fa(rs(p))=0; 
    if(siz(ls(p))<s){
        x=p;
        sSplit(rs(p),s-siz(ls(p))-1,rs(p),y);
    }
    else{
        y=p;
        sSplit(ls(p),s,x,ls(p));
    }
    Pushup(p);
}
int Merge(const int x,const int y){
    if(!(x&&y)) return x|y;
    if(key(x)<key(y)){
        fa(rs(x))=0;
        rs(x)=Merge(rs(x),y);
        Pushup(x);return x;
    }
    else{
        fa(ls(y))=0;
        ls(y)=Merge(x,ls(y));
        Pushup(y);return y;
    }
}
inline int GetRnk(int p){
    int ret=siz(ls(p));
    while(p){
        if(p==rs(fa(p))) ret+=siz(ls(fa(p)))+1; 
        p=fa(p);
    }
    return ret;
}
inline void Insert(const int &p){
    int x,y,t,z,w;
    if(idx[p]){
        sSplit(root,GetRnk(idx[p]),x,y);
        t=y;
        if(ls(t)){
            while(ls(t)){
                z=t;
                --siz(t);
                t=ls(t);
            }
            fa(t)=0;
            ls(z)=rs(t);
            fa(rs(t))=z;
        }
        else{
            fa(rs(y))=0;
            y=rs(y); 
        }
        rs(t)=0;
        siz(t)=1;
        ++cnt(t);
        vSplit(y,cnt(t),z,w);
        root=Merge(x,Merge(z,Merge(t,w)));
    }
    else root=Merge(New(p),root);
}
inline void Delete(const int &p){
    int x,y,t,z,w;
    sSplit(root,GetRnk(idx[p]),x,y);
    t=y;
    if(ls(t)){
        while(ls(t)){
            z=t;
            --siz(t);
            t=ls(t);
        }
        fa(t)=0;
        ls(z)=rs(t);
        fa(rs(t))=z;
    }
    else{
        fa(rs(y))=0;
        y=rs(y); 
    }
    rs(t)=0;
    siz(t)=1;
    if(--cnt(t)){
        vSplit(x,cnt(t),z,w);
        x=Merge(Merge(z,t),w);
    }
    else idx[p]=0;
    root=Merge(x,y);
}
void dfs(const int p){
    Insert(col[p]);
    int lp,_x,_y,_z,_p;
    for(lp=hq[p];lp;lp=nq[lp]){
        vSplit(root,lq[lp]-1,_x,_y);
        if(siz(_y)>=kq[lp]){
            sSplit(_y,kq[lp]-1,_y,_z);
            _p=_z;
            while(ls(_p)) _p=ls(_p);
            aq[lp]=val(_p);
            _y=Merge(_y,_z);
        }
        root=Merge(_x,_y);
    }
    for(lp=h[p];lp;lp=nxt[lp]) dfs(lp);
    Delete(col[p]);
}
int main(){
    scanf("%d",&t);
    while(t--){
        Init();
        scanf("%d%d",&n,&q);
        for(i=1;i<=n;++i) scanf("%d",col+i);
        for(i=2;i<=n;++i){
            scanf("%d",&v);
            nxt[i]=h[v];
            h[v]=i;
        }
        for(i=1;i<=q;++i){
            scanf("%d%d%d",&v,lq+i,kq+i);
            nq[i]=hq[v];hq[v]=i;aq[i]=-1;
        }
        dfs(1);
        for(i=1;i<=q;++i){
            printf("%d ",aq[i]);
            nq[i]=kq[i]=lq[i]=0;
        }
        for(i=1;i<=n;++i) nxt[i]=h[i]=hq[i]=idx[i]=0;
        putchar('\n');
    }
    return 0;
}

CF1614E Divan and a Cottage

题意

给你一间房子,将房子内的温度称为 \(P\),将房子外的温度称为 \(T\),其中, \(P\) 受制于 \(T\)
若前一天的室温\(P\)气温\(T\),今天的室温\(P_{new}\)

  • $ P_{new} = P + 1 $ , if $ P < T $ ,
  • $ P_{new} = P - 1 $ , if $ P > T $ ,
  • $ P_{new} = P $ , if $ P = T $ .

然后依次给你 \(n\) 天的 \(T\),每天有 \(k\) 个询问,询问:若第一天屋内室温\(x\),则到今天,室温 \(P\) 将会是多少?

题目强制在线。\(n,\sum k\le 2\times 10^5;T,x\le 10^9\)

解法

参考了 Tyyyyy 的题解。(% 游队)

考虑维护每个室温 \(i\) 在每天之后的室温值 \(p_i\)。初值有 \(p_i=i\),然后 \(p\) 始终单调不降,证明考虑每个 \(T\) 均不会破坏当前的单调性。

然后可以动态开点线段树 + 线段树二分维护每个 \(p\),对每个节点维护对应最大/最小值。可以分下面三种情况考虑:

  • 如果某个节点的最大值小于当前 \(T\),则对这个节点整体加 \(1\)
  • 如果某个节点的最小值大于当前 \(T\),则对这个节点整体减 \(1\)
  • 如果某个节点只有 \(T\) 一个值,则直接返回。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxl=32;
const int maxn=400010;
const int md=1000000001;
int n,i,t,k,v,a,l,r,m,pt,tot;
struct seg{int ls,rs,mn,mx,add;}tr[maxn*maxl];
#define ls(p) tr[p].ls
#define rs(p) tr[p].rs
#define mn(p) tr[p].mn
#define mx(p) tr[p].mx
#define add(p) tr[p].add
inline int New(int l,int r){
    mn(++tot)=l; 
    mx(tot)=r;
    return tot;
}
inline void Add(int p,int d){
    mn(p)+=d; 
    mx(p)+=d; 
    add(p)+=d;
}
inline void Pushdown(int p){
    if(!add(p)) return;
    Add(ls(p),add(p)); 
    Add(rs(p),add(p));
    add(p)=0;
}
inline void Pushup(int p){
    mn(p)=min(mn(ls(p)),mn(rs(p)));
    mx(p)=max(mx(ls(p)),mx(rs(p)));
}
void Change(int p,int l,int r){
    if(mx(p)<t){Add(p,1);return;}
    if(mn(p)>t){Add(p,-1);return;}
    if(mx(p)==t&&mn(p)==t) return;
    int m=(l+r)>>1;
    if(!ls(p)) ls(p)=New(l,m);
    if(!rs(p)) rs(p)=New(m+1,r);
    Pushdown(p);
    Change(ls(p),l,m);
    Change(rs(p),m+1,r);
    Pushup(p); 
}
int main(){
    New(1,md);
    scanf("%d",&n);
    for(i=1;i<=n;++i){
        scanf("%d",&t);
        Change(1,1,md);
        scanf("%d",&k);
        while(k--){
            scanf("%d",&v); 
            v-=((v+=a)>=md)*md; 
            pt=l=1; r=md;
            while(l<r){
                m=(l+r)>>1;
                if(!ls(pt)) ls(pt)=New(l,m);
                if(!rs(pt)) rs(pt)=New(m+1,r);
                Pushdown(pt);
                if(v<=m) r=m,pt=ls(pt);
                else l=m+1,pt=rs(pt);
            }
            printf("%d\n",a=mx(pt));
        }
    }
    return 0;
}

CF1625D Binary Spiders

题意

有一个长为 \(n\) 的数组 \(a\)。现在需要找出 \(a\) 的一个最大的子集 \(S\),满足 \(\forall x,y\in S\)\(x\oplus y\ge k\)。求任意一个合法的 \(S\)\(n\le 3\times 10^5;k,a_i<2^{30}\)

解法

考虑 \(\min_{x,y\in S}x\oplus y\)。此时 \(x\)\(y\) 一定是有序状态的 \(S\) 内相邻两个数。证明考虑某个数 \(z\) 满足 \(x<y<z\)\(x\oplus y>x\oplus z\) 时,一定存在某个数位 \(k\) 满足 \(x\)\(y\) 的第 \(k\) 位不同,\(x\)\(z\) 的第 \(k\) 位相同;而 \(y,z\) 的更高位均相同。此时 \(y\) 的第 \(k\) 位一定为 \(0\)\(x,z\) 的第 \(k\) 位一定为 \(1\);而如果 \(x\) 有某个更高位和 \(y,z\) 不同,则取 \(y\oplus z\) 一定更优秀于 \(x\oplus y\);否则一定有 \(x>y\) 和之前矛盾。

综上可以在将 \(a\) 排序后每选某个数值只考虑与其相邻的数的贡献。设 \(dp_i\) 为以 \(a_i\) 为最大值的 \(S\) 的最大大小,转移显然有 \(dp_i=\max_{j<i,a_i\oplus a_j\ge k}dp_j\)。显然可以将所有 \(a_i\) 放在 Trie 上,每个节点维护子树内的 \(\max\) 值,显然取 \(\oplus a_i\ge k\) 的值一定只会在某些子树内。和 P7470 [NOI Online 2021 提高组] 岛屿探险 的做法很相似。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxl=30;
const int maxn=300010;
int n,k,i,j,s,v,b,pt,tot=1;
int a[maxn],pe[maxn],dp[maxn],ans[maxn];
int trie[maxn*maxl][2],mx[maxn*maxl];
inline bool cmp(const int &x,const int &y){return a[x]<a[y];}
int main(){
    scanf("%d%d",&n,&k);
    for(i=1;i<=n;++i) scanf("%d",a+i),pe[i]=i;
    sort(pe+1,pe+n+1,cmp); 
    for(i=1;i<=n;++i){
        v=a[pe[i]]; s=0; pt=1;
        for(j=maxl-1;~j;--j){
            b=(v>>j)&1;
            if((k>>j)&1) pt=trie[pt][!b];
            else{
                s=max(s,mx[trie[pt][!b]]);
                pt=trie[pt][b];
            }
        }
        dp[pe[i]]=s=max(s,mx[pt])+1;
        mx[1]=max(mx[1],s); pt=1;
        for(j=maxl-1;~j;--j){
            b=(v>>j)&1;
            if(!trie[pt][b]) trie[pt][b]=++tot;
            pt=trie[pt][b]; mx[pt]=max(mx[pt],s);
        } 
    } 
    if(mx[1]==1){
        printf("-1");
        return 0;
    }
    printf("%d\n",j=mx[1]);
    for(i=n;i;--i){
        b=pe[i];
        if(dp[b]==j&&
          ((v^a[b])>=k||j==mx[1])){
            v=a[b];
            ans[j--]=b;
        }
    }
    for(i=1;ans[i];++i) printf("%d ",ans[i]);
    return 0;
}

P6031 CF1278F Cards 加强版

解法

模拟赛里面出现了原题,考场上试图复现部分分思路,然后【数据删除】掉了。

考虑将和式转化成 \(\sum_{i=0}^k S(i,k)n^{\underline i}\left(\frac 1m\right)^i\) 的形式(具体操作可以看 这篇文章,其中 \(S(i,k)\) 为第二类斯特林数)。然后考虑 \(S(i,k)\) 的组合意义为将 \(k\) 个有标号球放入 \(i\)无标号盒 内且不允许有空盒的方案数,可以使用容斥得出对应的方案数为 \(\frac 1{i!}\sum_{j=0}^i(-1)^j\binom ji(i-j)^k=\sum_{j=0}^i\frac{(-1)^j(i-j)^k}{j!(i-j)!}=\sum_{j=0}^i\frac{j^k(-1)^{i-j}}{j!(i-j)!}\)。(具体过程可以看 这篇文章)将这个式子代入上面的和式可得

\[\begin{aligned}\sum_{i=0}^kn^{\underline i}\left(\frac 1m\right)^i\left(\sum_{j=0}^i\frac{j^k(-1)^{i-j}}{j!(i-j)!}\right)&=\sum_{i=0}^k\left(-\frac 1m\right)^i\left(\sum_{j=0}^i\frac{j^k(-1)^jn!}{(n-i)!j!(i-j)!}\right)\\&=\sum_{i=0}^k\left(-\frac 1m\right)^i\left(\sum_{j=0}^i\frac{j^k(-1)^jn!(n-j)!}{j!(n-j)!(n-i)!(i-j)!}\right)\\&=\sum_{i=0}^k\left(-\frac 1m\right)^i\left(\sum_{j=0}^i\binom nj\binom{n-j}{i-j}j^k(-1)^j\right)\\&=\sum_{j=0}^k\binom njj^k(-1)^j\left(\sum_{i=j}^k\left(-\frac 1m\right)^i\binom{n-j}{i-j}\right)\\&=\sum_{j=0}^k\binom njj^k(-1)^j\left(\sum_{i=0}^{k-j}\left(-\frac 1m\right)^{i+j}\binom{n-j}i\right)\end{aligned} \]

\(\binom ni\) 等项可以递推计算,然后考虑 \(\binom ij\) 可以拆成 \(\binom{i-1}j+\binom{i-1}{j-1}\),则后面的和式可以按照下述方法拆分:

\(S_j=\sum_{i=0}^{k-j}\left(-\frac 1m\right)^{i+j}\binom{n-j}i\),则有:

\[\begin{aligned}S_j=\sum_{i=0}^{k-j}\left(-\frac 1m\right)^{i+j}\binom{n-j}i&=\left(\sum_{i=0}^{k-j}\left(-\frac 1m\right)^{i+j}\binom{n-(j+1)}i\right)+\left(\sum_{i=1}^{k-j}\left(-\frac 1m\right)^{i+j}\binom{n-(j+1)}{i-1}\right)\\&=\left(-\frac 1m\right)^k\binom{n-(j+1)}{k-j}+(-m)\left(\sum_{i=0}^{k-(j+1)}\left(-\frac 1m\right)^{i+(j+1)}\binom{n-(j+1)}i\right)+S_{j+1}\\&=\left(-\frac 1m\right)^k\binom{n-(j+1)}{k-j}+(1-m)S_{j+1}\end{aligned} \]

在推这样的式子的时候,如果某个地方推错了,则需要仔细看具体在哪个地方上出现了问题,搞清楚代码里的哪一部分对应了式子的哪一部分。虽然式子很恶心,但是一定要有耐心。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=10000010;
const int md=998244353;
int n,m,k,i,j,b,c,s,t;
int v[maxn],p[maxn];
int pw[maxn],inv[maxn];
inline int Pow(int d,int z){
	int r=1;
	do{
		if(z&1) r=(1LL*r*d)%md;
		d=(1LL*d*d)%md;
	}while(z>>=1);
	return r;
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	pw[1]=inv[1]=1; 
	b=Pow(m,md-2);
	for(i=2;i<maxn;++i){
		if(!v[i]){
			v[i]=p[++t]=i;
			pw[i]=Pow(i,k); 
			inv[i]=Pow(i,md-2);
		}
		else{
			j=v[i]; pw[i]=(1LL*pw[j]*pw[i/j])%md;
			inv[i]=(1LL*inv[j]*inv[i/j])%md;
		}
		for(j=1;j<=t;++j){
			if(v[i]<p[j]||i*p[j]>=maxn) break;
			v[i*p[j]]=p[j];
		}
	}
	t=0; c=1;
	for(i=0;i<=k;++i){
		p[i]=(1LL*c*pw[i])%md; 
		if(i&1) p[i]=md-p[i];
		c=((1LL*(n-i)*inv[i+1])%md*c)%md;
	}
	c=s=Pow(md-b,k);
	for(i=k;~i;--i){
		t=(1LL*p[i]*s+t)%md;
		c=((1LL*c*(n-i))%md*inv[k-i+1])%md;
		s=(1LL*(md+1-m)*s+c)%md;
	}
	printf("%d",t);
	return 0;
}
posted @ 2022-10-04 16:18  Fran-Cen  阅读(30)  评论(0编辑  收藏  举报