Codeforces #305 Div 1 简要题解

来源:互联网 发布:淘宝红包图片 编辑:程序博客网 时间:2024/04/30 10:54

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里的神奇做法虽然非常丽洁,但是不太好懂,下面是我想出的另外一种解法

首先我们求出t1h1变成a1t2a1又变回a1t3h2变成a2t4a2又变回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],因此我们可以从n1扫,用ans[i+1]的值去更新ans[i]的值,就能得到每个询问的答案的最大值了

代码

#include <iostream>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <algorithm>#define MAXN 1100000using 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,并维护一个集合Sq次操作,每次可能从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 510000using 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),从xiyi连一条无向边,那么问题转变为,在无向图中给每条边进行红蓝染色,使得每个结点连出的边中红边和蓝边个数相差不超过1。

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

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

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

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

代码

#include <iostream>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <algorithm>#include <set>#define MAXE 401000#define MAXV 401000using 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;}
0 0