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

根据难度排序。
可能是最容易看懂得题解?
个人总结向。
如果有什么讲的不清楚的欢迎留言私信交流~

文章目录

  • F. Infinite String Comparision(字符串+结论)
  • J. Easy Integration(欧拉积分)
  • H. Minimum-cost Flow(最小费用最大流)
  • I. 1 or 2(带花树)

F. Infinite String Comparision(字符串+结论)

题意: 给出两个无限循环串的循环节,比较两个串的大小。

思路:
假设一个字符串 S 有循环节(不需要是完整循环节) p 和 q ,并且满足 p + q ≤ S + g c d ( p , q ) p+q≤S+gcd(p,q) p+qS+gcd(p,q),那么 g c d ( p , q ) gcd(p,q) gcd(p,q) 也是一个循环节。

证明点这里

题解中说到只要复制到长度为(n+m)即可,也就是说可以记住这个结论。

代码:

#include
#define mp make_pair
#define pb push_back
#define ll long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
#define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;

const int manx=1e6+5;
const int N=1e3+5;
const int mod=1e9+7;

int main()
{
    string a,b;
    while(cin>>a>>b){
        string s1=a+b,s2=b+a;
        if(s1<s2) cout<<"<"<<endl;
        else if(s1==s2) cout<<"="<<endl;
        else cout<<">"<<endl;
    }
    return 0;
}

J. Easy Integration(欧拉积分)

题意: ∫ 0 1 ( x − x 2 ) n d x ∫_{0}^{1}(x−x^{2})^{n}dx 01(xx2)ndx 输出分数 p / q p/q p/q p ∗ q − 1 p∗q^{-1}%998244353 pq1

思路:

欧拉积分:
伽玛函数: Γ ( n + 1 ) = n ! Γ(n+1) = n! Γ(n+1)=n!
贝塔函数: B ( p , q ) = ∫ 0 1 x p − 1 ∗ ( 1 − x ) q − 1 d x = Γ ( p ) ∗ Γ ( q ) / Γ ( q + p ) B(p,q)=∫_{0}^{1}x^{p-1}*(1-x)^{q-1}dx=Γ(p)*Γ(q)/Γ(q+p) B(p,q)=01xp1(1x)q1dx=Γ(p)Γ(q)/Γ(q+p)

过程: ∫ 0 1 x n ∗ ( 1 − x ) n d x = B ( n + 1 , n + 1 ) = n ! ∗ n ! / ( 2 ∗ n + 1 ) ! ∫_{0}^{1}x^{n}*(1-x)^{n}dx=B(n+1,n+1)=n!*n!/(2*n+1)! 01xn(1x)ndx=B(n+1,n+1)=n!n!/(2n+1)!

代码:

#include
#define mp make_pair
#define pb push_back
#define ll long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
#define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;

const int manx=2e6+5;
const int N=1e3+5;
const int mod=998244353;

ll f[manx],n;
ll qp(ll a, ll b){
    ll ans=1;
    while(b){
        if(b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans%mod;
}
ll inv(ll x){
    return qp(x,mod-2);
}
int main()
{
    io; 
    f[1]=1;
    for(int i=2;i<=2000005;i++) f[i]=f[i-1]*i%mod;
    while(cin>>n){
        ll ans=f[n]*f[n]%mod*inv(f[2*n+1])%mod;
        cout<<ans<<endl;
    }
    return 0;
}

H. Minimum-cost Flow(最小费用最大流)

题意: 给定网络的边和费用,每次询问给定所有边的容量u和要求的流量v,问把一单位流量从1 流向 n 最少所需要的费用,如果不能满流输出NaN。

思路:
很明显每条边的容量都是相同的,所以我们可以初始化为1,这样做的好处是 m a x f l o w ∗ ( u / v ) maxflow * (u/v) maxflow(u/v) 就是当前询问的最大流,在增广路径的时候用 map 记录每一条路径的流量与花费(map会自动排序)。
如果 m a x f l o w ∗ ( u / v ) < 1 maxflow * (u/v) < 1 maxflow(u/v)<1 的话,则输出NaN 。
否则的话,我们不断地加入每一条路径,并且累加费用,直到流量不小于 v 时跳出循环,最后总的费用输出时分子分母同时除以分子分母的GCD即可。

代码:

#include
#define mp make_pair
#define pb push_back
#define ll long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
#define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
 
const int manx=2e3+5;
const int N=1e3+5;
const int mod=1e9+7;
 
bool vis[manx];
ll n,m,s,t,x,y,z,f,maxflow,mincost,cnt;
ll dis[manx],pre[manx],last[manx],flow[manx],head[manx];
map<ll,ll>mps;
struct node{
    ll v,next,flow,dis;
}a[manx];
 
inline void add(ll u,ll v,ll flow,ll dis){
    a[++cnt].next=head[u],a[cnt].v=v,a[cnt].flow=flow,a[cnt].dis=dis,head[u]=cnt;
}
 
bool spfa(ll s,ll t)
{
    for(int i=1;i<=n;i++) vis[i]=0,dis[i]=flow[i]=inf;
    queue<ll>q;
    q.push(s); vis[s]=1; dis[s]=0; pre[t]=-1;
    while(!q.empty()){
        ll now=q.front();
        q.pop();
        vis[now]=0;
        for(int i=head[now];i!=-1;i=a[i].next){
            if(a[i].flow>0&&dis[a[i].v]>dis[now]+a[i].dis){
                dis[a[i].v]=dis[now]+a[i].dis;
                pre[a[i].v]=now;
                last[a[i].v]=i;
                flow[a[i].v]=min(flow[now],a[i].flow);
                if(!vis[a[i].v]){
                    vis[a[i].v]=1;
                    q.push(a[i].v);
                }
            }
        }
    }
    return pre[t]!=-1;
}
inline void donic()  
{
    while(spfa(s,t)){
        ll now=t;
        maxflow+=flow[t];
        mincost+=flow[t]*dis[t];
        mps[flow[t]*dis[t]]+=flow[t];
        while(now!=s){
            a[last[now]].flow-=flow[t];
            a[last[now]^1].flow+=flow[t];
            now=pre[now];
        }
    }
}
inline void init(){
    for(int i=1;i<=n;i++) head[i]=-1,last[i]=0;
    cnt=-1; maxflow=mincost=0; s=1,t=n;
    mps.clear();
}
int main()
{
    while(scanf("%lld%lld",&n,&m)!=EOF){
        init();
        for(int i=1;i<=m;i++){
            scanf("%lld%lld%lld",&x,&y,&f);
            add(x,y,1,f); add(y,x,0,-f);
        }
        donic();
        ll q; cin>>q;
        while(q--){
            ll u,v; scanf("%lld%lld",&u,&v);
            if(maxflow*u<v) printf("NaN\n");
            else{
                x=0,y=v;
                for(auto sb: mps){
                    if(v>sb.se*u) v-=sb.se*u,x+=sb.fi*u;
                    else {
                        x+=sb.fi/sb.se*v; break;
                    }
                }
                z=__gcd(x,y);
                printf("%lld/%lld\n",x/z,y/z);
            }
        }
    }
    return 0;
}

I. 1 or 2(带花树)

题意: 给出一张图和每个点的期望度数,要求你删掉一些边使得每个点的度数都等于期望度数,问是否可行。

思路:
在这之前只知道匈牙利算法,现在了解到了一般图最大匹配的解决算法。
以知道这个算法为前提,难点在于想到拆点拆边。(当时我就不知道)
对于每个点要求的度数 d [ i ] d[i] d[i] ,我们将点 i 拆成 d [ i ] d[i] d[i]
对于每条边 e ( u , v ) e(u,v) e(u,v),我们将 ( u , v ) (u,v) (u,v)拆成,x,y,x与分别与 u 拆成的 d [ x ] d[x] d[x] 个点连边,y点同理,最后再将 ( x , y ) (x,y) (xy)连一条边(这样做是可以防止 u 或者 v 要求的度数为0的情况,此时直接在 x 与 y 之间连一条边就可以解决)
例如,题目中第三组样例:
3 2
1 1 2
1 3
2 3
第1个点拆成1个点,编号为1。
第2个点拆成1个点,编号为2。
第3个点拆成2个点,编号分别为3,4
第1条边拆成两个点,编号为5,6:(5,6)(5,1)(6,3)(6,4)连边。
第2条边拆成两个点,编号为7,8:(7,8)(7,2)(8,3)(8,4)连边。
注意在这题里是无向边。

代码:

#include
#define mp make_pair
#define pb push_back
#define ll long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
#define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;

const int manx=2e3+5;
const int N=1e3+5;
const int mod=1e9+7;

deque<ll>q;
bool g[N][N],inque[N],inblossom[N];
ll match[N],pre[N],base[N];
inline ll findancestor(ll u,ll v){  //找公共祖先
    bool inpath[N]={false};
    while(1){
        u=base[u];
        inpath[u]=true;
        if(match[u]==-1)break;
        u=pre[match[u]];
    }
    while(1){
        v=base[v];
        if(inpath[v])return v;
        v=pre[match[v]];
    }
}
inline void reset(ll u,ll anc){ //压缩花
    while(u!=anc)
    {
        ll v=match[u];
        inblossom[base[u]]=1;
        inblossom[base[v]]=1;
        v=pre[v];
        if(base[v]!=anc)pre[v]=match[u];
        u=v;
    }
}
inline void contract(ll u,ll v,ll n){
    ll anc=findancestor(u,v);
    memset(inblossom,0,sizeof(inblossom));
    reset(u,anc);
    reset(v,anc);
    if(base[u]!=anc)pre[u]=v;
    if(base[v]!=anc)pre[v]=u;
    for(int i=1;i<=n;i++)
        if(inblossom[base[i]]){
            base[i]=anc;
            if(!inque[i]) q.push_back(i),inque[i]=1;
        }
}
inline bool dfs(ll S,ll n)
{
    for(int i=0;i<=n;i++) pre[i]=-1,inque[i]=0,base[i]=i;
    q.clear(); q.push_back(S); inque[S]=1;
    while(!q.empty()){
        ll u=q.front(); q.pop_front();
        for(int v=1;v<=n;v++){
            if(g[u][v]&&base[v]!=base[u]&&match[u]!=v){
                if(v==S||(match[v]!=-1&&pre[match[v]]!=-1)) contract(u,v,n);
                else if(pre[v]==-1){
                    pre[v]=u;
                    if(match[v]!=-1) q.push_back(match[v]),inque[match[v]]=1;
                    else{
                        u=v;
                        while(u!=-1){
                            v=pre[u];
                            ll w=match[v];
                            match[u]=v;
                            match[v]=u;
                            u=w;
                        }
                        return true;
                    }
                }
            }
        }
    }
    return false;
}
inline int solve(ll n){  
    memset(match,-1,sizeof(match));
    ll ans=0;
    for(int i=1; i<=n; i++)
        if(match[i]==-1&&dfs(i,n))
            ans++;
    return ans;
}
//以上是带花树模板
//g[i][j]存放关系图:i,j是否有边,match[i]存放i所匹配的点
//建图开始初始化g
//最终匹配方案为match
//复杂度O(n^3)
//点是从1到n的
//最终带花树solve给出的是匹配对数
ll d[60];
ll x[210],y[210];
ll mps[55][210];
inline bool build(ll n,ll m)
{
    ll num=0;
    //拆点,给i点的第j个度一个标号,方便接下来的建图
    for(int i=1;i<=n;i++)
        for(int j=1;j<=d[i];j++)
            mps[i][j]=++num; 
    //拆边,把边被拆成的两个点分别与两头的每个度相连
    for(int i=1;i<=m;i++){ 
        for(int j=1;j<=d[x[i]];j++) g[mps[x[i]][j]][num+1]=g[num+1][mps[x[i]][j]]=1;
        for(int j=1;j<=d[y[i]];j++) g[mps[y[i]][j]][num+2]=g[num+2][mps[y[i]][j]]=1;
        g[num+1][num+2]=g[num+2][num+1]=1;
        num+=2;
    }
    if(solve(num)*2==num) return 1;
    return 0;
}
int main()
{
    ll n,m;
    while(scanf("%lld%lld",&n,&m)!=EOF){
        memset(g,0,sizeof(g));
        for(int i=1;i<=n;i++) scanf("%lld",&d[i]);
        for(int i=1;i<=m;i++) scanf("%lld%lld",&x[i],&y[i]);
        if(build(n,m)) puts("Yes");
        else puts("No");
    }
    return 0;
}

你可能感兴趣的:(acm,牛客暑假多校,2020牛客暑假多校,acm,acm竞赛,算法)