Codeforces #305 Div 1 简要题解

A. Mike and Frog

题目链接

http://codeforces.com/contest/547/problem/A

题目大意

Xaniar初始的值为 h1 ,Abol初始的值为 h2 ,每一秒钟,两人各自的值会产生如下变化

h1=(x1h1+y1)modm

h2=(x2h2+y2)modm

问最少多少秒后,两人的值各变为 a1,a2 。若无解输出-1

思路

tutorial里的神奇做法虽然非常丽洁,但是不太好懂,下面是我想出的另外一种解法

首先我们求出 t1 h1 变成 a1 t2 a1 又变回 a1 t3 h2 变成 a2 t4 a2 又变回 a2

下面考虑 t1,t2,t3,t4 均存在的情况,那么问题可以变成求方程组

t1+kt2=t3+lt4

k,l 最小的一组正整数解

移项得

kt2lt4=t3t1

这个式子可以通过扩展欧几里得得到一组解

t2kt4l=gcd(t2,t4) ,若 gcd(t3t1,gcd(t2,t4)) 则无解。若存在解,则可以得到一组可行解 k=t3t1gcd(t2,t4)k,l=t3t1gcd(t2,t4)l

对于不定方程 axby=c 而言,若一组可行解为 (x0,y0) ,则可以推出通解为 (x0+tb,y0+ta),tZ ,因为 ax0by0=ax0+tabby0tab=a(x0+tb)b(y0+ta)

这样我们就能找出一组正整数解 (k,l) 了,然后我们直接暴力得到最小的一组正整数解 (k,l) 即可得到答案。

而对于其他情况(如不存在 t2 ),需要分别进行特判才能得到正确的解

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

using namespace std;

typedef long long int LL;

LL MOD,h1,h2,a1,a2,x1,x2,y1,y2;
LL t1,t2,t3,t4;

LL extGCD(LL a,LL b,LL &x,LL &y)
{
    if(!b)
    {
        x=1,y=0;
        return a;
    }
    LL gcd=extGCD(b,a%b,x,y);
    LL t=x;
    x=y;
    y=t-(a/b)*y; //!!!
    return gcd;
}

int main()
{
    scanf("%I64d",&MOD);
    scanf("%I64d%I64d",&h1,&a1);
    scanf("%I64d%I64d",&x1,&y1);
    scanf("%I64d%I64d",&h2,&a2);
    scanf("%I64d%I64d",&x2,&y2);
    while(h1!=a1&&t1<MOD+20)
    {
        t1++;
        h1=(h1*x1+y1)%MOD;
    }
    if(h1!=a1)
    {
        printf("-1\n");
        return 0;
    }
    while(h2!=a2&&t3<MOD+20) //!!!!
    {
        t3++;
        h2=(h2*x2+y2)%MOD;
    }
    if(h2!=a2)
    {
        printf("-1\n");
        return 0;
    }
    if(t1==t3)
    {
        printf("%I64d\n",t1);
        return 0;
    }
    while((h1!=a1&&t2<MOD+20)||(h1==a1&&!t2))
    {
        t2++;
        h1=(h1*x1+y1)%MOD;
    }

    while((h2!=a2&&t4<MOD+20)||(h2==a2&&!t4))
    {
        t4++;
        h2=(h2*x2+y2)%MOD;
    }
    if((t1+t2)==(t3+t4))
    {
        printf("%I64d\n",t1+t2);
        return 0;
    }
    if(h1!=a1&&h2==a2)
    {
        if(t1>=t3&&(t1-t3)%t4==0)
        {
            printf("%I64d\n",t1);
            return 0;
        }
    }
    if(h2!=a2&&h1==a1)
    {
        if(t3>=t1&&(t3-t1)%t2==0)
        {
            printf("%I64d\n",t3);
            return 0;
        }
    }
    if(h1!=a1)
    {
        printf("-1\n");
        return 0;
    }
    if(h2!=a2)
    {
        printf("-1\n");
        return 0;
    }
    LL k,l;
    LL gcd=extGCD(t2,t4,k,l);
    LL a=t2/gcd,b=t4/gcd;
    k=k*(t3-t1)/gcd; //!!!
    l=l*(t3-t1)/gcd; //!!!
    if((t3-t1)%gcd)
    {
        printf("-1\n");
        return 0;
    }
    l=-l;
    if(k<0)
    {
        LL tmp=(-k)/b+1;
        k+=tmp*b;
        l+=tmp*a;
    }
    if(l<0)
    {
        LL tmp=(-l)/a+1;
        k+=tmp*b;
        l+=tmp*a;
    }
    LL ans=k*t2+t1;
    while(1)
    {
        if(l-a>=0&&k-b>=0)
        {
            l-=a;
            k-=b;
            ans=min(ans,k*t2+t1);
        }
        else break;
    }
    printf("%I64d\n",ans);
    return 0;
}

B. Mike and Feet

题目链接

http://codeforces.com/contest/547/problem/B

题目大意

对于 1xn ,询问长度为 n 的序列a里, ai...ai+x1 里最小元素的最大值

思路

首先我们求出两个数组 L[],R[] L[i],R[i] 分别代表在 ai 左边最近的比 ai 小的元素的下标,在 ai 右边最近的比 ai 小的元素的下标,这可以通过两次 O(n) 做单调栈得到。那么 (L[i],R[i]) 区间里最小的元素就是 a[i] ,我们枚举 a[i] ,来更新 x=R[i]L[i]1 的答案,但是这样求得的不一定是最大值。假设 ans[i] 表示 x=i 时的答案,很显然 ans[1]>=ans[2]>=ans[3]...>=ans[n] ,因此我们可以从 n 1扫,用 ans[i+1] 的值去更新 ans[i] 的值,就能得到每个询问的答案的最大值了

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1100000

using namespace std;

int n,a[MAXN],ans[MAXN];
int L[MAXN],R[MAXN];
int stack[MAXN],top=0;

int main()
{
    scanf("%d",&n);
    fill(L,L+MAXN,0);
    fill(R,R+MAXN,n+1); //!!!!
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        while(top&&a[stack[top]]>=a[i]) top--;
        if(top) L[i]=stack[top];
        stack[++top]=i;
    }
    top=0;
    for(int i=n;i>=1;i--)
    {
        while(top&&a[stack[top]]>=a[i]) top--;
        if(top) R[i]=stack[top];
        stack[++top]=i;
    }
    for(int i=1;i<=n;i++)
    {
        int len=R[i]-L[i]-1; //!!!!
        ans[len]=max(ans[len],a[i]);
    }
    for(int i=n;i>=1;i--)
        ans[i]=max(ans[i],ans[i+1]);
    for(int i=1;i<=n;i++)
        printf("%d ",ans[i]);
    printf("\n");
    return 0;
}

C. Mike and Foam

题目链接

http://codeforces.com/contest/547/problem/C

题目大意

给你 n 个数a1...an,并维护一个集合 S q次操作,每次可能从 a 序列里拿出一个数插入进集合,也可能从集合中删除一个数,每次操作完成后询问当前的集合里有多少对(ai,aj),使得 gcd(ai,aj)=1

思路

我们可以维护当前的集合 S 的对应答案ans,每次往集合里插入或者删除元素可以看成是相同的操作,只不过是前者对答案的贡献为正,后者为负而已。那么问题就变为,给你一个数字 x ,问当前的集合里有多少个数字与x互质。

不妨对 x 分解质因数,x=pt11pt22pt33...,由于2*3*5*7*11*13*17> 2105 ,因此最多能分解出7个不同的质因数。定义 |pi| 为集合 S 里是pi倍数的数字个数。则当前的集合里与 x 互质的数字个数为

|1||p1||p2|...+|p1p2|+|p1p3|...|p1p2p3|...

这是一个非常简单的容斥,不必过多赘述。因此我们只需要用一个set来维护当前的集合 S ,并用个数组维护当前的集合S里是某些数字的倍数的数字个数,然后对于每次插入或删除 x ,先对x分解质因数,然后做容斥来更新答案即可

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <set>

#define MAXN 510000

using namespace std;

typedef long long int LL;

set<int>bst;
set<int>::iterator it;
LL ans=0;
LL a[MAXN];
LL divisors[20];
int top=0;
LL cnt[1100000]; //cnt[i]=在bst集合里的是数字i的倍数的数字个数
LL stack[1<<8];
int tot=0; //共有tot种可能的质因数组合乘积

void DFS(int pos,int selected,int flag,LL val) //考虑是否选择第pos个质因数,已经选择了selected个质因数,给答案的贡献为flag*xx,要找出是val的倍数的数字个数
{
    if(pos>top)
    {
        if(!selected) return;
        ans+=cnt[val]*flag;
        stack[++tot]=val;
        return;
    }
    DFS(pos+1,selected,flag,val); //不选第pos个质因数
    DFS(pos+1,selected+1,-flag,val*divisors[pos]); //选第pos个质因数
}

void modify(LL x,LL flag)
{
    top=tot=0;
    LL tmp=x;
    for(LL p=2;p*p<=x;p++)
    {
        if(x%p==0)
        {
            divisors[++top]=p;
            while(x%p==0) x/=p;
        }
    }
    if(x&&x!=1) divisors[++top]=x;
    DFS(1,0,flag,1); //!!!!!
}

int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
        scanf("%I64d",&a[i]);
    while(q--)
    {
        int x;
        scanf("%d",&x);
        //cout<<"size:"<<bst.size()<<endl;
        if(a[x]==1)
        {
            if(bst.count(x))
            {
                bst.erase(x);
                ans-=bst.size();
                printf("%I64d\n",ans);
            }
            else
            {
                ans+=bst.size();
                bst.insert(x);
                printf("%I64d\n",ans);
            }
        }
        else
        {
            if(bst.count(x))
            {
                bst.erase(x);
                modify(a[x],-1);
                for(int i=1;i<=tot;i++) cnt[stack[i]]--;
                ans-=bst.size();
                ans--;
                printf("%I64d\n",ans);

            }
            else
            {
                ans+=bst.size();
                modify(a[x],1);
                printf("%I64d\n",ans);
                for(int i=1;i<=tot;i++) cnt[stack[i]]++;
                bst.insert(x);
            }
        }
    }
    return 0;
}

D. Mike and Fish

题目链接

http://codeforces.com/contest/547/problem/D

题目大意

给你 n 个点的坐标,每个点的坐标均为整数,要将所有点染成红蓝两色,并且每一列、每一行上红点的个数与蓝点的个数相差不能超过1,求一组合法染色方案

思路

将每个行标和每个列标均看成是图中的一个结点,对于每个点(xi,yi),从 xi yi 连一条无向边,那么问题转变为,在无向图中给每条边进行红蓝染色,使得每个结点连出的边中红边和蓝边个数相差不超过1。

显然我们按照行标-列标连边建立的是一个二分图,那么图中奇数度数的点的个数一定是偶数(每次加入一条边,会为所有点的度数和增加2,那么无论如何,所有点的度数和都是偶数,偶数度数点的度数和为偶数,那么奇数度数点的度数和也是偶数,故奇数度数点的个数为偶数),因此我们可以把度数为奇数的点,两两之间依次连无向边,这样每个点的度数就能都变成偶数了。

那么对于每个联通块,我们以一个奇数度数点为起点(若没有,则以偶数度数点为起点),做欧拉回路,显然是存在欧拉回路的,我们对这条路径进行红蓝边交叉染色。

考虑一般情况,之前这个联通块里没有加任何边,则在欧拉回路中,每个点显然出边个数与入边个数相同(即从每个点离开的次数和进入每个点的次数是相同的),而且欧拉回路长度是偶数(无向图 G 是二部图的充分必要条件为G中所有回路的长度均为偶数),显然每个点出边均为蓝色,入边均为红色(或者出边均为红色,入边均为蓝色),那么满足了题目给出的要求。

而若之前这个联通块里加了边,则加边后欧拉回路长度依然是偶数,而每个点新连的边最多只有一条,那么每个点连的红边个数和蓝边个数相差一,也满足了题目给出的要求。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <set>

#define MAXE 401000
#define MAXV 401000

using namespace std;

int n,degree[MAXV];

struct edge
{
    int v,id;
    edge(){}
    edge(int _v,int _id):v(_v),id(_id){}
}edges[MAXE*2];

bool operator==(edge a,edge b)
{
    return a.v==b.v&&a.id==b.id;
}

bool operator<(edge a,edge b)
{
    return a.id<b.id;
}

bool operator>(edge a,edge b)
{
    return a.id>b.id;
}

set<edge>G[MAXV];

int stack[MAXE*2],top=0;
bool vis[MAXV];

void AddEdge(int U,int V,int ID)
{
    G[U].insert(edge(V,ID));
    G[V].insert(edge(U,ID));
}

void DFS(int u)
{
    vis[u]=true;
    while(!G[u].empty())
    {
        int v=G[u].begin()->v;
        int id=G[u].begin()->id;
        G[u].erase(edge(v,id));
        G[v].erase(edge(u,id));
        DFS(v);
        stack[++top]=id;
    }
}

int oddpoints[MAXV],tot=0;
char ans[MAXV];

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        y+=200000; //!!!!!
        degree[x]++,degree[y]++;
        AddEdge(x,y,i);
        AddEdge(y,x,i);
    }
    for(int i=1;i<=400000;i++)
        if(degree[i]&1)
            oddpoints[++tot]=i;
    for(int i=1;i<=tot;i+=2)
    {
        AddEdge(oddpoints[i],oddpoints[i+1],0);
        AddEdge(oddpoints[i+1],oddpoints[i],0);
    }
    for(int i=1;i<=tot;i++)
    {
        if(vis[oddpoints[i]]) continue;
        DFS(oddpoints[i]);
        bool flag=false;
        while(top)
        {
            flag^=1;
            if(stack[top])
                ans[stack[top]]=flag?'r':'b';
            top--;
        }
    }
    for(int i=1;i<=400000;i++)
    {
        if(vis[i]) continue;
        DFS(i);
        bool flag=false;
        while(top)
        {
            flag^=1;
            if(stack[top])
                ans[stack[top]]=flag?'r':'b';
            top--;
        }
    }
    printf("%s\n",ans+1);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值