补题:2020牛客暑期多校训练营(第一场)

 

Contest Link

Solved:4/10

Upsolved:10/10

 


 

A. B-Suffix Array (牛客 5666A

两种做法:一种是利用性质后转为求SA,另一种是Hash+二分。

根据论文 Parameterized Suffix Arrays for Binary Strings,题目中对于后缀的B-function排序,可以通过将其进行一点类似的转化后直接用后缀数组来求。

转化是这样进行的:将字符串$s_1...s_n$转化为数组$b_1...b_n$,其中$b_i=min_{j>i,s[i]=s[j]}\{j-i\}$,若不存在$j$则$b_i=n+1$(只要是一个正常取不到的大数就行)。同时令$b_{n+1}=n+2$,这样使得$sa[1]=n$。倒序输出$sa$数组即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=100005;

int sa[N],rk[N];
//sa[i]: 第i小的后缀的开头位置
//rk[i]: 后缀s[i..n]的rank 
int tmp[N<<1],top[N];

void quicksort(int n,int m)
{
    for(int i=1;i<=m;i++)
        top[i]=0;
    for(int i=1;i<=n;i++)
        top[rk[i]]++;
    for(int i=1;i<=m;i++)
        top[i]=top[i-1]+top[i];
    for(int i=n;i>=1;i--)
        sa[top[rk[tmp[i]]]--]=tmp[i];
}

void getsa(int *s,int n,int lim)
{
    for(int i=1;i<=n;i++)
        rk[i]=s[i],tmp[i]=i;
    for(int i=n+1;i<=2*n;i++)
        tmp[i]=0;
    quicksort(n,lim);
    
    for(int i=1;i<n;i<<=1)
    {
        int cnt=0;
        for(int j=n-i+1;j<=n;j++)
            tmp[++cnt]=j;
        for(int j=1;j<=n;j++)
            if(sa[j]>i)
                tmp[++cnt]=sa[j]-i;
        
        quicksort(n,max(cnt,lim));
        
        for(int j=1;j<=n;j++)
            swap(rk[j],tmp[j]);
        
        rk[sa[1]]=cnt=1;
        for(int j=2;j<=n;j++)
        {
            if(tmp[sa[j]]!=tmp[sa[j-1]] || tmp[sa[j]+i]!=tmp[sa[j-1]+i])
                cnt++;
            rk[sa[j]]=cnt;
        }
        
        if(cnt==n)
            break;
    }
}

int n;
char s[N];

int b[N];

int main()
{
    while(~scanf("%d",&n))
    {
        scanf("%s",s+1);
        
        int pos[2]={n+1,n+1};
        for(int i=n;i>=1;i--)
        {
            b[i]=(pos[s[i]-'a']>n?n+1:pos[s[i]-'a']-i);
            pos[s[i]-'a']=i;
        }
        
        b[n+1]=n+2;
        getsa(b,n+1,n+2);
        
        for(int i=n+1;i>=1;i--)
            if(sa[i]<=n)
                printf("%d%c",sa[i],i==1?'\n':' ');
    }
    return 0;
}
View Code

Hash就比较暴力了。不过都比较卡常。

首先我们可以对于$s_1...s_n$先求出题目中B-function的$b_1...b_n$。如果我们比较两个后缀,其实只会在这个B function上将 'a','b'两个字符第一次出现位置 的$b_i$变为$0$,其余的$b_i$保持不变。那么我们在排序时,可以二分出两个B-suffix中第一个$b_i$不等的位置,只比较这个位置上的值即可,判断前缀是否相等用hash就能完成。

其实还有另一种实现办法。如果我们认为第一个字母一定为'a',那么可以仅对'a'出现的位置进行hash,而'b'出现的位置则也相应确定。此时只需要对'a'、'b'分别预处理hash,每次二分时根据首字母选用对应的hash。

 

B. Infinite Tree (牛客 5666B

首先可以发现,真正可能选为根节点的地方,仅为$i!$或$LCA(i!,j!)$。于是建虚树。

可以先建一棵$m=10$的树看一看LCA的条件。

能够发现,$i!$与$(i-1)!$分叉的位置是,$(i-1)!$所有大于等于$maxdiv(i)$的质因子之积(质因子可重复)。虽然判断条件都为这个,但是不同写法的实现难度差距很大,自己写炸之后学习了别人的写法,利用了树上深度来判断弹栈的终止。

之后就是简单的树上dfs了。若向儿子走,那么$\Delta ans=(dep[son]-dep[x])\cdot (\sum w_i-\sum_{i\in subtree(son)}w_i)$。

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
const int N=100005;

int n;
ll w[2*N];
int mindiv[N];

int t[N];
inline int lowbit(int x)
{
    return x&(-x);
}
inline void add(int k,int x)
{
    for(int i=k;i<=n;i+=lowbit(i))
        t[i]+=x;
}
inline int query(int k)
{
    int ans=0;
    for(int i=k;i;i-=lowbit(i))
        ans+=t[i];
    return ans;
}

int tot,dep[N*2];
int top,st[N],pos[N];
vector<int> v[N*2];

void build(int n)
{
    tot=n,st[top=1]=1;
    for(int i=1;i<=n;i++)
        t[i]=0;
    
    for(int i=2,j;i<=n;i++)
    {
        dep[i]=dep[i-1]+1;
        for(j=i;j!=mindiv[j];j/=mindiv[j])
            dep[i]++;
        
        pos[i]=query(n)-query(j-1);
        for(j=i;j!=1;j/=mindiv[j])
            add(mindiv[j],1);
    }
    
    for(int i=2;i<=n;i++)
    {
        while(top>1 && dep[st[top-1]]>=pos[i])
        {
            v[st[top-1]].push_back(st[top]);
            top--;
        }
        
        if(dep[st[top]]!=pos[i])
        {
            dep[++tot]=pos[i];
            v[tot].push_back(st[top]);
            st[top]=tot;
        }
        st[++top]=i;
    }
    while(top>1)
    {
        v[st[top-1]].push_back(st[top]);
        top--;
    }
}

void getsz(int x)
{
    for(int i=0;i<v[x].size();i++)
    {
        int y=v[x][i];
        getsz(y);
        w[x]+=w[y];
    }
}

void dfs(int x,ll val,ll &ans)
{
    ans=min(val,ans);
    for(int i=0;i<v[x].size();i++)
    {
        int y=v[x][i],len=dep[y]-dep[x];
        dfs(y,val+(w[1]-2*w[y])*len,ans);
    }
}

int main()
{
    for(int i=1;i<N;i++)
        mindiv[i]=i;
    for(int i=2;i*i<N;i++)
        for(int j=i;j<N;j+=i)
            mindiv[j]=min(mindiv[j],i);
    
    while(~scanf("%d",&n))
    {
        build(n);
        
        for(int i=1;i<=n;i++)
            scanf("%lld",&w[i]);
        for(int i=n+1;i<=tot;i++)
            w[i]=0;
        
        ll ans=0;
        for(int i=1;i<=n;i++)
            ans+=1LL*w[i]*dep[i];
        
        getsz(1);
        dfs(1,ans,ans);
        printf("%lld\n",ans);
        
        for(int i=1;i<=tot;i++)
            v[i].clear();
    }
    return 0;
}
View Code

 

C. Domino (牛客 5666C

又是一个论文题,链接见:Distances in Domino Flip Graphs

感觉没啥意思,没有很多推广的样子...那么就变成锻炼阅读英语论文了。

具体的算法参考Page 7的Figure 5,对于所有格子周围的点计算$h_T(v)$(共$(n+1)\times (m+1)$个点)。计算方法就是,从$(0,0)$出发,不穿过任何Domino到达$(x,y)$经过的正向边数$-$反向边数(即论文Page 7中的$h_T(v)=\sum_{i=1}^n o(e_i)$)。(假装)能够证明通过任意路径到达$(x,y)$得出的$h_T$值相等。最后,两个Domino覆盖$T,T'$间的距离为$\frac{1}{4}\sum |h_T(v)-h_{T'}(v)|$。

所以代码中真正要做的工作就是把同一块Domino粘一起来判断是否穿过,然后bfs即可。貌似也有不用bfs的实现。

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef pair<int,int> pii;
const int N=1005;
const int dx[2]={1,0},dy[2]={0,1};

inline char getch()
{
    char ch=getchar();
    while(ch!='|' && ch!='-')
        ch=getchar();
    return ch;
}

int n,m;
char map[N][N],ord[N][N];

queue<pii> Q;
bool vis[N][N];

inline bool inboard(int x,int y)
{
    return (x>=0 && x<=n && y>=0 && y<=m);
}

void calc(int *dist)
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            map[i][j]=getch(),ord[i][j]=0;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=m;j++)
            vis[i][j]=false;
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(map[i][j]=='-' && ord[i][j]==0)
                ord[i][j]=1,ord[i][j+1]=2;
            if(map[i][j]=='|' && ord[i][j]==0)
                ord[i][j]=1,ord[i+1][j]=2;
        }
    
    vis[0][0]=true,Q.push(pii(0,0));
    while(!Q.empty())
    {
        int x=Q.front().first,y=Q.front().second;
        Q.pop();
        
        for(int i=0;i<2;i++)
        {
            int nx=x+dx[i],ny=y+dy[i];
            if(!inboard(nx,ny) || vis[nx][ny])
                continue;
            
            if(i==0 && y<m && map[x+1][y]=='-' && map[x+1][y+1]=='-' &&
                ord[x+1][y]==1 && ord[x+1][y+1]==2) //down
                continue;
            if(i==1 && x<n && map[x][y+1]=='|' && map[x+1][y+1]=='|' &&
                ord[x][y+1]==1 && ord[x+1][y+1]==2) //right
                continue;
            
            vis[nx][ny]=true;
            Q.push(pii(nx,ny));
            dist[nx*m+ny]=dist[x*m+y]+((x+y)%2==0?1:-1)*(i==0?1:-1);
        }
    }
}

int ds[N*N],dt[N*N]; //height

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        calc(ds);
        calc(dt);
        
        int ans=0;
        for(int i=0;i<=n;i++)
            for(int j=0;j<=m;j++)
                ans+=abs(ds[i*m+j]-dt[i*m+j]);
        
        printf("%d\n",ans/4);
    }
    return 0;
}
View Code

 

D. Quadratic Form (牛客 5666D

这题考察的是线性规划的求解。线性规划的标准形式如下(其中$X\in R^n$):

\[\left\{\begin{array}{**lr**} min\ f(X)\\c_i(X)\leq 0, & i=1,2,...\\h_j(X)=0, & j=1,2,...\end{array}\right.\]

那么将这道题目的条件套进去,就能够写出:

\[\left\{\begin{array}{**lr**} min\ -b^TX\\X^TAX-1\leq 0\end{array}\right.\]

此时,拉格朗日函数为$L(X,\mu)=-b^TX+\mu(X^TAX-1)$,设最优解为$X^*,\mu^*$。

考虑代入KTT条件:

\[\left\{\begin{array}{**lr**} \nabla_X L(X^*,\mu^*)=-b^T+\mu^*{X^*}^T(A+A^T)=0 & (1)\\ \nabla_{\mu}L(X^*,\mu^*)={X^*}^TAX^*-1=0 & (2)\\ \mu^*\geq 0 &(3)\\ \mu^*({X^*}^TAX^*-1)\leq 0 & (4)\end{array}\right.\]

有了$(2)$,就可以直接无视$(4)$了。$(3)$对于求解没有太多帮助。

由$(1),(2)$,再加上$A$对称,能够一同推出:$-b^TX^*-\mu^*{X^*}^T(A+A^T)X^*=-b^TX^*+\mu^*\cdot 2=0$,从而得到:

\[\begin{array}{**lr**} \mu^*=\frac{1}{2}b^TX^* & (5)\end{array}\]

而又由$(1)$,能够推出:

\[\begin{array}{**lr**} {X^*}^T=\frac{1}{\mu^*}b^T(A+A^T)^{-1}\end{array}\]

整理一下,再转置,就是:

\[\begin{array}{**lr**} X^*=\frac{A^{-1}b}{2\mu^*} &(6)\end{array}\]

综合$(1),(6)$,得到:

\[b^TX^*=b^T\frac{A^{-1}b}{2\mu^*}=b^T\frac{A^{-1}b}{b^TX^*}\]

即有$(b^TX^*)^2=b^TA^{-1}b$。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

//如果为了求逆,N需要开到原来的两倍 
const int N=405;
const int MOD=998244353;

inline int quickpow(int x,int k)
{
    int res=1;
    while(k)
    {
        if(k&1)
            res=1LL*res*x%MOD;
        x=1LL*x*x%MOD;
        k>>=1;
    }
    return res;
}

inline int rev(int x)
{
    return quickpow(x,MOD-2);
}

struct Determinant
{
    int a[N][N];
    
    Determinant()
    {
        memset(a,0,sizeof(a));
    }
    
    int value(int n)
    {
        int ans=1;
        for(int i=0;i<n;i++)
            for(int j=i+1;j<n;j++)
                while(a[j][i])
                {
                    int mul=a[i][i]/a[j][i];
                    for(int k=i;k<n;k++)
                    {
                        a[i][k]=(a[i][k]-1LL*a[j][k]*mul%MOD+MOD)%MOD;
                        swap(a[i][k],a[j][k]);
                    }
                    ans=-ans;
                }
        
        for(int i=0;i<n;i++)
            ans=1LL*ans*a[i][i]%MOD;
        return (ans+MOD)%MOD;
    }
    
    int reverse(int n)
    {
        for(int i=0;i<n;i++)
            for(int j=n;j<2*n;j++)
                a[i][j]=(j-n==i?1:0);
        
        for(int i=0;i<n;i++)
        {
            int pos=i;
            if(pos<n && !a[pos][i])
                pos++;
            
            if(pos==n)
                return 0;
            int mul=rev(a[pos][i]);
            for(int j=0;j<2*n;j++)
            {
                swap(a[i][j],a[pos][j]);
                a[i][j]=1LL*a[i][j]*mul%MOD;
            }
            
            for(int j=0;j<n;j++)
                if(j!=i)
                {
                    mul=a[j][i];
                    for(int k=i;k<2*n;k++)
                        a[j][k]=(a[j][k]-1LL*mul*a[i][k]%MOD+MOD)%MOD; 
                }
        }
        
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                a[i][j]=a[i][j+n];
        return 1;
    }
};

int n;
Determinant A;
int b[N],tmp[N];

int main()
{
    while(~scanf("%d",&n))
    {
        memset(tmp,0,sizeof(tmp));
        
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                scanf("%d",&A.a[i][j]);
        for(int i=0;i<n;i++)
            scanf("%d",&b[i]);
        
        A.reverse(n);
        
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                tmp[i]=(tmp[i]+1LL*b[j]*A.a[j][i])%MOD;
        
        int ans=0;
        for(int i=0;i<n;i++)
            ans=(ans+1LL*b[i]*tmp[i])%MOD;
        printf("%d\n",ans);
    }
    return 0;
}
View Code

 

E. Counting Spanning Trees (牛客 5666E

论文题,原论文见:https://arxiv.org/pdf/0706.2918.pdf

首先,满足题目中条件的二分图被称为Ferrers图。记二分图$G$中的两个点集为$U=\{u_1,...,u_n\},V=\{v_1,...,v_m\}$,记度数分别为$\lambda_1,...,\lambda_n$和$\lambda'_1,...,\lambda'_m$,此图为Ferrers图的严格的条件为:

1. 若$(u_i,v_j)$边存在则有$(u_p,v_q)$边均存在($1\leq p\leq i,1\leq q\leq j$),换句话说也就是$\lambda_i$单调减且与$v_1,...,v_{\lambda_i}$均有边。

2. 无孤立点。

此二分图中的度数$\tau (G)=\prod_{i=2}^n \lambda_i\prod_{j=2}^m \lambda'_j$(不知道有没有感性的理解)。

回到本题中,就需要先将$a_i$按从大到小排序,然后对二分图的另一侧算度数,之后去掉两侧最大的度数后将度数相乘即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
const int N=100005;

int n,m,mod;
int a[N],ord[N],b[N];

int main()
{
    while(~scanf("%d%d%d",&n,&m,&mod))
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        
        sort(a+1,a+n+1);
        
        for(int i=1;i<=m;i++)
            b[i]=n-(lower_bound(a+1,a+n+1,i)-a)+1;
        
        ll ans=1;
        for(int i=1;i<n;i++)
            ans=ans*a[i]%mod;
        for(int i=2;i<=m;i++)
            ans=ans*b[i]%mod;
        printf("%lld\n",ans);
    }
    return 0;
}
View Code

 

F. Infinite String Comparision (牛客 5666F

签到题。把最长的串复制一遍,把另一个串也补全到这个长度,比较即可。

#include <cstdio>
#include <cstring>
using namespace std;

const int N=200005;

int solve(char *s,int n,char *t,int m)
{
    int lim=n*2;
    for(int i=n+1;i<=lim;i++)
        s[i]=s[(i-1)%n+1];
    for(int i=m+1;i<=lim;i++)
        t[i]=t[(i-1)%m+1];
    for(int i=1;i<=lim;i++)
        if(s[i]!=t[i])
            return s[i]<t[i]?-1:1;
    return 0;
}

int n,m,ans;
char s[N],t[N];

int main()
{
    while(~scanf("%s%s",s+1,t+1))
    {
        n=strlen(s+1),m=strlen(t+1);
        if(n>m)
            ans=solve(s,n,t,m);
        else
            ans=-solve(t,m,s,n);
        
        if(ans==0)
            printf("=\n");
        if(ans==-1)
            printf("<\n");
        if(ans==1)
            printf(">\n");
    }
    return 0;
}
View Code

 

G. 八仙过海,各显神通 (牛客 5666G

看到题目中定义的乘,就应该考虑是否满足交换律、结合律。其实是满足的,因为可以每个向量都可以写成矩阵的形式。

\[\begin{pmatrix} a_0 & a_1 & a_2\\ a_1 & a_2 & a_0\\ a_2 & a_0 & a_1\end{pmatrix}\]

队友lyy在现场推了个神仙结论,就是题目中规定的向量乘有$P^2-1$的公共循环节(对于任意向量)。于是就可以将每个向量的幂次降到__int128的范围内了,可以使用快速幂。

据他所说,他是受到了51nod 1195的启发,认为公共循环节一定在$P^3$级别或以内,然后对于$P$较小的素数打表发现的规律。对于合数,他觉得应该可以用积性函数推出类似的结论。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
typedef __int128 i128;
typedef unsigned int ui;
const int N=100005;
const int P=998244353;
const ll PP=1LL*P*P-1;

struct Vector
{
    ll a[3];
    Vector(ll x=0,ll y=0,ll z=0)
    {
        a[0]=x,a[1]=y,a[2]=z;
    }
};
inline Vector operator *(const Vector &X,const Vector &Y)
{
    Vector res;
    res.a[0]=(X.a[0]*Y.a[0]+X.a[1]*Y.a[2]+X.a[2]*Y.a[1])%P;
    res.a[1]=(X.a[1]*Y.a[0]+X.a[2]*Y.a[2]+X.a[0]*Y.a[1])%P;
    res.a[2]=(X.a[2]*Y.a[0]+X.a[0]*Y.a[2]+X.a[1]*Y.a[1])%P;
    return res;
}

inline i128 quickpow(i128 x,ll t)
{
    i128 res=1;
    while(t)
    {
        if(t&1)
            res=res*x%PP;
        x=x*x%PP;
        t>>=1;
    }
    return res;
}

inline Vector quickpow(Vector x,ll t)
{
    Vector res(1,0,0);
    while(t)
    {
        if(t&1)
            res=res*x;
        x=x*x;
        t>>=1;
    }
    return res;
}

int n,m,q,z0,a,b;
Vector v[N];

i128 pw2[10];

int main()
{
    scanf("%d%d%d%d%d%d",&n,&m,&q,&z0,&a,&b);
    
    for(int i=0;i<10;i++)
        pw2[i]=quickpow(2,32*i);
    
    ui z=z0;
    for(int i=1;i<=n;i++)
        for(int j=0;j<3;j++)
        {
            z=z*a+b;
            v[i].a[j]=z%P;
        }
    
    while(q--)
    {
        Vector ans(1,0,0);
        for(int i=1;i<=n;i++)
        {
            i128 pw=0;
            for(int j=0;j<m;j++)
            {
                z=z*a+b;
                pw=(pw+1LL*z*pw2[j])%PP;
            }
            
            ans=ans*quickpow(v[i],pw);
        }
        
        printf("%lld %lld %lld\n",ans.a[0],ans.a[1],ans.a[2]);
    }
    return 0;
}
View Code

 

H. Minimum-cost Flow (牛客 5666H

对于每一个询问$u,v$,我们相当于在原图中求流量为$\frac{v}{u}$的最小费用流。我们只要把所有整数流量的最小费用流求出来(每次跑出一个增广路的时候存一下),对于每个询问在所有方案里面二分就行了。

假设所有方案中,第$i$个的总流量为$f_i$、费用为$c_i$(均有$f_i+1=f_{i+1}$,因为原图中每边容量为$1$),那么最终要输出的答案为:

\[c_i\cdot \frac{u}{v}+(\frac{v}{u}-f_i)\cdot \frac{c_{i+1}-c_i}{f_{i+1}-f_i}\cdot \frac{u}{v}=\frac{c_iu+(v-uf_i)(c_{i+1}-c_i)}{v}\]

#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <queue>
using namespace std;

struct Edge
{
    int to,cap,cost,rev;
    Edge(int a,int b,int c,int d)
    {
        to=a,cap=b,cost=c,rev=d;
    }
};
typedef long long ll;
typedef pair<int,int> pii;
const int INF=1<<30;
const int N=205;

int n,m;
vector<Edge> v[N];

inline void Add(int from,int to,int cap,int cost)
{
    v[from].push_back(Edge(to,cap,cost,v[to].size()));
    v[to].push_back(Edge(from,0,-cost,v[from].size()-1));
}

int h[N],dist[N];
int prevv[N],preve[N];

vector<pii> ans;

int min_cost_flow(int s,int t,int f)
{
    int res=0;
    memset(h,0,sizeof(h));
    
    while(f)
    {
        priority_queue<pii,vector<pii>,greater<pii> > Q;
        for(int i=0;i<N;i++)
            dist[i]=INF;
        dist[s]=0;
        Q.push(pii(0,s));
        
        while(!Q.empty())
        {
            pii p=Q.top();
            Q.pop();
            
            int cur=p.second;
            if(dist[cur]<p.first)
                continue;
            
            for(int i=0;i<v[cur].size();i++)
            {
                Edge &e=v[cur][i];
                if(e.cap>0 && dist[e.to]>dist[cur]+e.cost+h[cur]-h[e.to])
                {
                    dist[e.to]=dist[cur]+e.cost+h[cur]-h[e.to];
                    prevv[e.to]=cur;
                    preve[e.to]=i;
                    Q.push(pii(dist[e.to],e.to));
                }
            }
        }
        
        if(dist[t]==INF)
            return -1;
        
        for(int i=0;i<=n;i++)
            h[i]+=dist[i];
        
        int d=f;
        for(int i=t;i!=s;i=prevv[i])
            d=min(d,v[prevv[i]][preve[i]].cap);
        f-=d;
        res+=d*h[t];
        ans.push_back(pii(INF-f,res));
        
        for(int i=t;i!=s;i=prevv[i])
        {
            Edge &e=v[prevv[i]][preve[i]];
            e.cap-=d;
            v[i][e.rev].cap+=d;
        }
    }
    return res;
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        ans.clear();
        for(int i=1;i<=n;i++)
            v[i].clear();
        
        for(int i=1;i<=m;i++)
        {
            int x,y,c;
            scanf("%d%d%d",&x,&y,&c);
            Add(x,y,1,c);
        }
        
        ans.push_back(pii(0,0));
        min_cost_flow(1,n,INF);
        
        int q;
        scanf("%d",&q);
        while(q--)
        {
            ll ui,vi;
            scanf("%lld%lld",&ui,&vi);
            if(vi>ui*ans.back().first)
            {
                printf("NaN\n");
                continue;
            }
            
            int l=0,r=ans.size()-1,mid,res=-1;
            while(l<r)
            {
                mid=(l+r)>>1;
                if(vi>ui*ans[mid].first)
                    l=mid+1,res=mid;
                else
                    r=mid;
            }
            
            ll num=ans[res].second;
            ll dnum=vi;
            num=num*ui+(vi-ui*ans[res].first)*(ans[res+1].second-ans[res].second);
            ll gcd=__gcd(num,dnum);
            printf("%lld/%lld\n",num/gcd,dnum/gcd);
        }
    }
    return 0;
}
View Code

 

I. 1 or 2 (牛客 5666I

当$d_i\leq 2$时,可以利用对称性用最大流求解(并不是十分确定正确性)。但是当$d_i>2$时,就变成了HDU 3551,需要使用带花树(一般图匹配算法)。

将每条边$(x,y)$拆成$(x_1,e),(e,e'),(e',y_1)$三条边(若$d_x=2$,则还需要拆出$(x_2,e)$;$d_y=2$类似),然后跑一遍带花树,判断最大匹配数是否等于$\frac{1}{2}\sum d_i$即可。

举个例子来说明这个方法的正确性。假如输入为:

3 3
2 1 1
1 2
1 3
2 3

上图的红线给出了一种最大匹配的方案。可以看出,在有最大匹配时,每个节点的度数均等于$d_i$;若一条原图中的边在最终方案中出现,则拆出的边中有$2$条被选中(为$(v_j,e_i),(e'_i,v_k)$,其中$v_j,v_k$也可为$v'_j,v'_k$);若一条原图中的边在最终方案中未出现,则拆出的边中有$1$条被选中(为$(e_i,e'_i)$)。

在答案为Yes时,共有$\frac{1}{2}\sum d_i$条原图中的边出现、$m-\frac{1}{2}\sum d_i$条未出现,则在拆边后的图中共有$2\cdot \frac{1}{2}\sum d_i+ 1\cdot (m-\frac{1}{2}\sum d_i)=m+\frac{1}{2}\sum d_i$条边被选中(即最大匹配数)。而拆边后的图中共有$2m+\sum d_i$个节点。故回答为Yes的条件是 $2\times $最大匹配数=拆点数。

一般图匹配还是博大精深的,有不少类似网络流的蛇皮建图,有必要更加深入地学习。

#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=505;

//用并查集维护点是否在奇环内 
int fa[N];

inline int find(int a)
{
    if(fa[a]==a)
        return a;
    return fa[a]=find(fa[a]);
}

int n,m;
vector<int> v[N];

//vis: 染色标记+访问标记
//pre: 增广路的前一个点
//match: 与哪个点匹配
int vis[N],pre[N],match[N];
queue<int> Q;

//cnt: 标记总数
//tag: 在求lca时,给路径打上标记 
int cnt,tag[N];

int lca(int x,int y)
{
    for(++cnt;;swap(x,y))
        if(x)
        {
            x=find(x);
            if(tag[x]==cnt)
                return x;
            tag[x]=cnt,x=pre[match[x]];
        }
}

void blossom(int x,int y,int _lca)
{
    while(find(x)!=_lca)
    {
        pre[x]=y,y=match[x];
        if(vis[y]==2)
            vis[y]=1,Q.push(y);
        if(find(x)==x)
            fa[x]=_lca;
        if(find(y)==y)
            fa[y]=_lca;
        x=pre[y];
    }
}

bool augment(int x,int id)
{
    for(int i=1;i<=id;i++)//not template
        fa[i]=i,vis[i]=pre[i]=0;
    while(!Q.empty())
        Q.pop();
    
    vis[x]=1;
    Q.push(x);
    while(!Q.empty())
    {
        x=Q.front();
        Q.pop();
        
        for(int i=0;i<v[x].size();i++)
        {
            int y=v[x][i],last;
            if(find(x)==find(y) || vis[y]==2)
                continue;
            
            if(!vis[y])
            {
                vis[y]=2,pre[y]=x;
                if(!match[y]) //y成为增广路的终点 
                {
                    for(x=y;x;x=last)
                        last=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;
                    return true;
                }
                
                vis[match[y]]=1;
                Q.push(match[y]);
            }
            else
            {
                int _lca=lca(x,y);
                blossom(x,y,_lca),blossom(y,x,_lca);
            }
        }
    }
    return false;
}

int d[N];
int idx[N][2];

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        cnt=0;
        memset(tag,0,sizeof(tag));
        memset(match,0,sizeof(match));
        
        int id=0,sum=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&d[i]);
            for(int j=1;j<=d[i];j++)
                idx[i][j]=++id;
        }
        sum=id;
        
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            for(int j=1;j<=d[x];j++)
                v[idx[x][j]].push_back(id+1),v[id+1].push_back(idx[x][j]);
            for(int j=1;j<=d[y];j++)
                v[idx[y][j]].push_back(id+2),v[id+2].push_back(idx[y][j]);
            v[id+1].push_back(id+2),v[id+2].push_back(id+1);
            id+=2;
        }
        
        int ans=0;
        for(int i=1;i<=id;i++)
            if(!match[i] && augment(i,id))
                ans++;
        
        if(ans*2==id)
            printf("Yes\n");
        else
            printf("No\n");
        
        for(int i=1;i<=id;i++)
            v[i].clear();
    }
    return 0;
}
View Code

 

J. Easy Integration (牛客 5666J

推导见:某作业帮答案

如果放到现场赛真的会变成签到题吗?深表怀疑。

 

posted @ 2020-08-02 01:33  LiuRunky  阅读(534)  评论(0)    收藏  举报