HGOI20180815 (NOIP 提高组模拟赛 day2)

Day 2

rank 11 100+35+30=165 

 

本题是一道数论题,求ax+by=c的正整数对(x,y) x>=0并且y>=0

先说下gcd: 求a,b公约数gcd(a,b)

如gcd(4,6)=  2

辗转相除法 gcd(a,b)=gcd(b,a%b)

证明一下,令a=kb+r,那么r=a%b;

设d为(a,b)的一个任意公约数d,所以d|a且d|b

因为r=a-kb因为d|a且d|b,所以d|r注意到我们的d是任意选取的,

那么最大公约数是属于这个公因数集合里的所以gcd(a,b)=gcd(b,a%b)

再说下ex_gcd(a,b,&x,&y)求ax+by=gcd(a,b)的一个整数解 x,y

算法如下:

b=0时gcd(a,b)=gcd(a,0)=a;所以ax=a,所以x=1,y=任意数(这里赋值为0)

ax1+by1=gcd(a,b)=gcd(b,a%b)=bx2+(a%b)y2

引理: a-[a/b]*b=a%b

令a=br+k,左边=br+k-[(br+k)/b]*b=br+k-br=k=a%b=右边

由引理得:ax1+by1=bx2+(a-[a/b]*b)y2=ay2+b(x2-[a/b*b]y2)

由恒等式定理得: x1=y2,y1=x2-[a/b*b]y2

所以求解(x1,y1)只要求出(x2,y2)就可以了

ex_gcd程序如下:

ll ex_gcd(ll a,ll b,ll &x,ll &y)
{
   if (b==0) { x=1;y=1; return a;}
   ll r=ex_gcd(b,a%b,x,y);
   ll t=x;x=y;y=t-a/b*y;
   return r;
}

但是这只是求出一组解但我们要求多组解甚至解的个数

令一组解(x0,y0)是初始解 (x1,y1)是要求的解

ax0+by0=gcd(a,b)=ax1+by1

a(x0-x1)=b(y1-y0)两边同时除以gcd(a,b)

设gcd(a,b)=g;a'=a/g;b'b/g

那么就可以写成 a'(x0-x1)=b'(y1-y0) 因为g为a,b最大公约数

那么 a'和b'互质

那么b'|(x0-x1);a'|(y1-y0)设x0-x1=kb'那么y1-y0=ka'

所以x1=x0-kb';y1=y0+ka' k为整数

由此可见方程ax+by=gcd(a,b)如果有解那么一定有无线组解

(x0,y0)==>(x0-kb',y0+ka')

对于一般2元1次不等式ax+by=c若c%gcd(a,b)!=0那么一定无解否则一定有多组解

回到题目让我们求出ax+by=c,通过上面那句话就可以轻易判断有无解,现在考虑解的个数

先把x0 y0调整到正数

x最小时且大于0,y最大,x0-kb'>=0不停的增加k最多可以是kmax= x0/b';xmin=x0-(x0/b')*b'=x0%b'

x最大时y最小且大于0,y0+ka'>=0不停的减去k最多可以是kmin=-y0/a'同理ymin=y0%a'

通过ymin可以求出xmax(带入即可)

求出xmin和xmax后再xmin和xmax之间每隔b‘就有一组解所以就是一个等差数列,个数是(尾项-首相)/公差+1=(xmax-xmin)/b'+1

 

# include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll ex_gcd(ll a,ll b,ll &x,ll &y)
{
   if (b==0) { x=1;y=1; return a;}
   ll r=ex_gcd(b,a%b,x,y);
   ll t=x;x=y;y=t-a/b*y;
   return r;
}
inline ll read()
{
    ll 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;
}
int main()
{
    freopen("cake.in","r",stdin);
    freopen("cake.out","w",stdout);
    int T; scanf("%d",&T);
    while (T--) {
        ll a=read(),b=read(),x,y,c=read();
        ll g=ex_gcd(a,b,x,y);
        if (c%g!=0) {
            printf("0\n");
            continue;
        }  
        ll g1=a/g,g2=b/g;ll k=c/g;
        x=x*(k%g2); y=y*(k%g1); //后面反正要%g1的现在先%防止爆炸 
        ll xmin=(x%g2+g2)%g2,ymin=(y%g1+g1)%g1;
        ll xmax=(c-b*ymin)/a; //通过ymin求xmax 
        if (xmax-xmin<0) {
            printf("0\n"); continue;
        }
        ll ans=((xmax-xmin)/g2)+1;//等差数列个数 
        printf("%lld\n",ans);
    } 
    return 0;
}

 

30pts:

# include <bits/stdc++.h>
using namespace std;
char s[1005];
bool fun(int z,int a,int c,int k,int m,int n)
{
    for (int i=1;i<=n;i++) {
         z=((a*z+c)/k)%m;
         if ((z<m/2)&&(s[i]!='0')) return false;
         else if ((z>=m/2)&&(s[i]!='1'))return false;
    }
    return true;
}
int main()
{
    freopen("zero.in","r",stdin);
    freopen("zero.out","w",stdout);
    int a,c,k,m,n;
    scanf("%d%d%d%d%d",&a,&c,&k,&m,&n);
    scanf("%s",s+1);
    int ans=0;
    for(int z=0;z<=m;z++) {
        if (fun(z,a,c,k,m,n)) ans++;
    }
    printf("%d\n",ans);
    return 0;
}

100pts:倍增+hash

题解原文:

倍增HASH,用倍增记录每个值跳2^i次后会到哪个值,构成的串的HASH值,

然后根据n的二进制直接算答案就行了,倍增的时候要滚存。

#include <bits/stdc++.h>

using namespace std;

const int mo1 = 1000000007;
const int mo2 = 1000000009;
const int base1 = 233;
const int base2 = 23333;
const int M = 1100010;
const int N = 100010;

vector<int>a[M];
pair<int, int>HASH;
int to[M][2], Pow1[N], Pow2[N], now[M];
pair<int, int>Hash[M][2], nowhash[M];
int ans;
char s[N];

inline void Ch(pair<int, int> &now, int C)
{
    now.first = 1ll * now.first * Pow1[C] % mo1;
}

inline void Add(pair<int, int> &now, pair<int, int> A)
{
    now.first = now.first + A.first;
    if(now.first >= mo1)now.first -= mo1;
}

inline int check(pair<int, int> a, pair<int, int> b)
{
    return ((a.first == b.first) && (a.second == b.second));
}

int main()
{
    freopen("zero.in", "r", stdin);
    freopen("zero.out", "w", stdout);
    int A, c, k, m, n;
    Pow1[0] = Pow2[0] = 1;
    scanf("%d%d%d%d%d", &A, &c, &k, &m ,&n);
    for(int i = 1;i <= n;i++)
    {
        Pow1[i] = 1ll * Pow1[i - 1] * base1 % 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].first = (z >= M) + 1;
    }
    scanf("%s", s);
    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.first = (1ll * HASH.first * base1 + 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++; 
    printf("%d\n", ans);
}

 

 

 

题意:

       给一个DAG,选择尽量多的点使彼此之间不存在祖先-后代关系。

5%:

       暴力枚举每一个点是否被选中,时间复杂度:O(2^n)

20%:

       在上一个做法的基础上加上一些剪枝。时间复杂度:O(2^n)。

       此算法亦可通过n==200的测试点,且只需要16ms(luogu上)。

树的部分分:

       容易发现选择全部叶节点即可。时间复杂度:O(n)

“每个会员要么没有上司,要么没有下属”的:

       容易发现此时的DAG是一个二分图。使用经典的二分图最大独立集算法(点数-最大匹配数)即可。时间复杂度:O(m*sqrt(n))。

“全是直接下属”的:

    我也不知道怎么做,这个部分分只是为了让***的错误算法多拿一些分。

100%:

容易发现此题是一道DAG最大独立集裸题(参见 CTSC2008祭祀),而且不用输出方案。(但我真的不是出的原题,纯属巧合)

由于时间原因,在此复制@白苏小公子喵 的题解:

在有向无环图中,我们定义:

链:图上一些点的集合,对于链上任意两个点x、y,满足x能到达y或者y能到达x。

反链:图上一些点的的集合,对于反链上任意两个点x、y,满足x不能到达y并且y不能到达x。

所以就是很显然的求最长反链长度了~

有以下Dilworth定理:

最长反链长度=最小链覆盖(选取最少的链覆盖所有的点)->证明详见最长反链与最小链覆盖

以及其对偶定理:最长链长度=最小反链覆盖

所以就又转化成了求最小路径(链)覆盖了,来看怎么求:

选择建一个二分图,两边各有n个点,原来的点node分别对应两个图中的node1、node2。如果原图中存在边 x->y,那么就在二分图上建立边 x1->y2。

跑一遍匈牙利,则有 原图最小路径覆盖=原点数n-二分图最大匹配

(当然也可以用网络流,则有 原图最小路径覆盖=原点数n-最大流)

为什么呢?考虑每在二分图上连一条边,就相当于将两条路径连成一条,那么最小链覆盖数就减少了1(少用一条链覆盖所有点了)。我们将一个点拆成两个,跑二分图最大匹配,避免了路径相交的问题,保证所选出来的每一条一定为一条链。

#include<bits/stdc++.h>
#define maxn 10010
#define int64 long long
#define FO(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
using namespace std;
bitset<maxn> isfa[maxn];
int n,m,que[maxn],cnt;
int ind[maxn],top[maxn];
vector<int> v[maxn],vb[maxn];
struct edge{
    int u,v,cap;
};
struct Dinic{
    int n,s,t,dis[maxn],cur[maxn],que[maxn];
    vector<edge>e;vector<int>v[maxn];
    void Init(int n){
        this->n=n;e.clear();
        for(int i=0;i<n;i++)v[i].clear();
    }
    void AddEdge(int x,int y,int flw){
        e.push_back((edge){x,y,flw});
        e.push_back((edge){y,x,0});
        v[x].push_back(e.size()-2);
        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(){
    FO(dance); 
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y);
        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));
    return 0;
}

 

posted @ 2018-08-15 14:42  ljc20020730  阅读(165)  评论(0编辑  收藏  举报