HGOI8.15集训题解

题解

不好意思,今天大凶(虽然洛谷上是小吉)


第一题——吃蛋糕(cake)

【题目描述】

  • 给出方程 ax+by=c a x + b y = c ,求出非负解的个数( a,b,c1014 a , b , c ≤ 10 14 )。

  • 这个一看就知道是欧几里得拓展求出一组解,然后求出x最小的非负解和y最小的非负解。
  • 假设 ax+by=c a x + b y = c 的一组解为 x0,y0 x 0 , y 0 ,而原方程可以看做 axc(mod a x ≡ c ( m o d b) b ) ,那么最终的解可以写成 x=x0+bt,y=y0at(b=b/gcd(a,b),a=a/gcd(a,b)) x = x 0 + b ′ t , y = y 0 − a ′ t ( b ′ = b / g c d ( a , b ) , a ′ = a / g c d ( a , b ) )
  • 然后求出两组解,解的个数就是(最大的x-最小的x)/解之间的差。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;
inline int read(){
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
void fff(){
    freopen("cake.in","r",stdin);
    freopen("cake.out","w",stdout);
}
LL a,b,c,ans;
void ex_gcd(LL &x,LL &y,LL &p,LL a,LL b){
    if(b==0){
        x=1;y=0;p=a;
        return;
    }
    ex_gcd(x,y,p,b,a%b);
    LL t=x;
    x=y;
    y=t-a/b*y;
}
LL gcd(LL a,LL b){
    return (b==0)?a:gcd(b,a%b);
}
LL fm(LL x,LL y, LL MOD){
    if(x<=0) x*=-1ll,y*=-1ll;
    LL s=0;
    while (x){
        if(x&1) s=(s+y)%MOD;
        y=(y+y)%MOD;
        x>>=1ll;
    }
    return s;
}
int main(){
//  fff();
    int T;T=read();
    while (T--){
        scanf("%lld%lld%lld",&a,&b,&c);
        ans=0;
        LL x,y,p;
        ex_gcd(x,y,p,a,b);
        if(c%p!=0){
            printf("0\n");
            continue;
        }
        a/=p;b/=p;c/=p;
        x=fm(x,c,b);y=fm(y,c,a);
        x=(x%b+b)%b;
        y=(y%a+a)%a;
        LL tx=(c-b*y)/a;
        printf("%lld\n",(tx-x)/b+1);
    }
}

第二题——01串(zero)

【题目描述】

  • 给出 a,c,k,n,m a , c , k , n , m 。求出在 [0,m1] [ 0 , m − 1 ] 之间有多少个初始z满足:
    z=((az+c)/k)mod z = ( ( a ∗ z + c ) / k ) m o d m m
    if(z<m/2)return 0 0 else return r e t u r n 1 1
  • 使得重复上述多次之后满足给定的01字符串。

  • 讲道理暴力能拿50分。
  • 标程给的是hash+倍增。
  • hash用于判断最终生成的子串是否和结果串相等。
  • hash还对于倍增的链接有很大的提速效果orz
  • 令上面生成的式子的结果为f[z],那么很明显 f f 是单向映射的,那我对于每一个z所产生的最终结果就是唯一确定的,那么我只要造出这m个结果然后判断就可以了哈哈哈哈哈哈
  • 造的时候可以利用倍增,上一层造好之后,只要将上次的以i作为原始z的那一串和这一串的结尾 to[i] t o [ i ] f f <script type="math/tex" id="MathJax-Element-2593">f</script>作为开头的链接起来就可以了。
  • orz我知道我写的很乱你还是直接看代码吧….

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define LL long long
using namespace std;
inline int read(){
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
void fff(){
    freopen("zero.in","r",stdin);
    freopen("zero.out","w",stdout);
}
const int N=1000100;
const int mo1=1e9+7;
const int bas=233;
int A,c,k,m,n;
vector <int> a[N];
int HASH;
int to[N][2],Pow1[N],Pow2[N],now[N];
int Hash[N][2],nowhash[N];
int ans;
char s[N];
void Ch(int &now,int C){
    now=1ll*now*Pow1[C]%mo1;
}
void Add(int &now,int A){
    now=now+A;
    if(now>=mo1)now-=mo1;
}
bool check(int a,int b){
    return a==b;
}
int main(){
//  fff();
    A=read(),c=read(),k=read(),m=read(),n=read();
    scanf("%s",s);
    Pow1[0]=1;
    for(int i=1;i<=n;i++){
        Pow1[i]=1ll*Pow1[i-1]*bas%mo1;
    }
    int M=m>>1;
    for (int i=0;i<m;i++){
        int z=i;
        now[i]=i;
        z=((1ll*A*z+c)/k)%m;
        to[i][0]=z;
        Hash[i][0]=(z>=M)+1;
    }
    if(n&1){
        for (int i=0;i<m;i++){
            nowhash[i]=Hash[now[i]][0];
            now[i]=to[now[i]][0];
        }
    }
    for (int i=0;i<n;i++) HASH=(1ll*HASH*bas+s[i]-'0'+1)%mo1;
    for (int x=1;x<=17;x++){
        for (int i=0;i<m;i++){
            to[i][x&1]=to[to[i][(x-1)&1]][(x-1)&1];
            Hash[i][x&1]=Hash[i][(x-1)&1];
            Ch(Hash[i][x&1],1<<(x-1));
            Add(Hash[i][x&1],Hash[to[i][(x-1)&1]][(x-1)&1]);
        }
        if(n&(1<<x)){
            for (int i=0;i<m;i++){
                Ch(nowhash[i],1<<x);
                Add(nowhash[i],Hash[now[i]][x&1]);
                now[i]=to[now[i]][x&1];
            }
        }
    }
    for (int i=0;i<m;i++){
        if(check(nowhash[i],HASH)) ans++;
    }
    cout<<ans;
}

第三题——没有上司的舞会(dance)(注意:这个不是洛谷的原题orz

【题目描述】

  • 给出一幅有向无环图求出最长反链。

  • 首先需要知道Dilworth定理,最长反链=最小链覆盖(但我不会证明

  • 那么就变成了一幅图求最小链覆盖,如果把每一个节点先看做一条链,那么每把两个点链接起来,就减少了一个一条链(保证点之间必须有边),那么如果将一个点一分为二,一个是入度,一个是出度,就变成了求最大二分图匹配了。

  • OK,看出板子了,现学匈牙利,直接抄起键盘就是干!

这里写图片描述


#include<bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define LL long long
using namespace std;
inline int read(){
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
void fff(){
    freopen("dance.in","r",stdin);
    freopen("dance.out","w",stdout);
}
const int N=10010;
int n,m;
bitset<N> isfa[N];
int que[N];
int ind[N],top[N],cnt;
vector<int>v[N],vb[N];
struct edge{
    int u,v,cap;
};
struct Dinic{
    int n,s,t,dis[N],cur[N],que[N];
    vector<edge>e;vector<int> v[N];
    void Init(int n){
        this->n=n;e.clear();
    }
    void AddEdge(int x,int y,int flw){
        e.push_back((edge){x,y,flw});
        v[x].push_back(e.size()-1);
        e.push_back((edge){y,x,0});
        v[y].push_back(e.size()-1);
    }
    int bfs(){
        memset(dis,0x3f,sizeof(dis));
        int l=1,r=1;que[1]=s;dis[s]=0;
        while(l<=r){
            int p=que[l++],to,i;
            for (int t=0;t<(int)v[p].size();t++)
                if(e[i=v[p][t]].cap&&dis[to=e[i].v]>1e9)
                    dis[to]=dis[p]+1,que[++r]=to;
        }
        return dis[t]<1e9;
    }
    int dfs(int p,int a){
        if(p==t||!a) return a;
        int sf=0,flw;
        for (int &i=cur[p],to;i<(int)v[p].size();i++){
            edge &E=e[v[p][i]]; 
            if(dis[to=E.v]==dis[p]+1&&(flw=dfs(to,min(a,E.cap)))){
                E.cap-=flw;e[v[p][i]^1].cap+=flw;
                a-=flw;sf+=flw;
                if(!a) break;
            }
        }
        return sf;
    }
    int dinic(int s,int t){
        this->s=s;this->t=t;
        int flw=0;
        while (bfs()){
            memset(cur,0,sizeof(cur));
            flw+=dfs(s,1e9);
        }
        return flw;
    }
}sol;
int main(){
//  fff();
    n=read(),m=read();
    for (int i=1;i<=m;i++){
        int x,y;
        x=read(),y=read();
        v[x].push_back(y);ind[y]++;
        vb[y].push_back(x);
    }
    int l=1,r=0;
    for (int i=1;i<=n;i++) if(!ind[i]) que[++r]=i;
    while (l<=r){
        int now=que[l++];top[++cnt]=now;
        for(int i=0;i<(int)v[now].size();i++)
            if(!--ind[v[now][i]]) que[++r]=v[now][i];
    }
    for(int i=1;i<=n;i++){
        int p=top[i];isfa[p][p]=1;
        sol.AddEdge(0,i*2,1);
        sol.AddEdge(i*2+1,1,1);
        for (int j=0;j<(int)vb[p].size();j++)
            isfa[p]|=isfa[vb[p][j]];
        for (int j=1;j<=n;j++)
            if(isfa[p][j]&&p!=j)
                sol.AddEdge(j*2,p*2+1,1);
    }
    printf("%d\n",n-sol.dinic(0,1));
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值