7.15 考试

T1:给一个方程 ax+by=c ,求它的正整数解的数量,其中 a,b,c 可以为负。

仿佛没有学扩展欧几里得一样,写了半天却有很多情况没有考虑到,才50分;发现了自己思维的不周密啊...

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
int T;
ll a,b,c;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll abss(ll x){return x<0?-x:x;}
void exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b){x=1; y=0; return;}
    exgcd(b,a%b,x,y);
    ll t=x; x=y; y=t-a/b*y;
}
int main()
{
    freopen("fuction.in","r",stdin);
    freopen("fuction.out","w",stdout);
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld%lld",&a,&b,&c);
        if((a>0&&b>0&&c>0)||(a<0&&b<0&&c<0))
        {
            if(a<0)a=-a,b=-b,c=-c;
            ll g=gcd(a,b),x,y;
            if(c%g){printf("0\n"); continue;}
            a/=g; b/=g; c/=g;
            exgcd(a,b,x,y);
            x*=c; y*=c;
            if(x>0)
            {
                ll k=x/b;
                x-=k*b; y+=k*a;
                if(y<0){printf("0\n"); continue;}
            }
            else
            {
                ll k=abss(x)/b+1;
                x+=k*b; y-=k*a;
                if(y<0){printf("0\n"); continue;}
            }
            ll ans;
            if(y%a==0)ans=y/a;
            else ans=y/a+1;
            if(ans>65535)printf("ZenMeZheMeDuo\n");
            else printf("%d\n",ans);
        }
        else
        {
            if((a>0&&b>0&&c<0)||(a<0&&b<0&&c>0)){printf("0\n"); continue;}
            ll aa=abss(a),bb=abss(b),cc=abss(c);
            ll g=gcd(aa,bb);
            if(cc%g==0)printf("ZenMeZheMeDuo\n");
            else printf("0\n");
        }
    }
    return 0;
}

然后观摩满分的程序,模仿它的众多细节,其中还有些自己目前尚不甚明晰的地方;

大概就是要注意先把 a,b,c 都变成正数,让 exgcd 处理正数,然后再把它取负回来;

负数的 gcd 那一块也不好弄,干脆不要写需要用 gcd 的扩展欧几里得,而直接在递归的底部赋值 x = c/a;

先把0的情况特判掉,这个还挺好判的;

还有个小发现,就是 a,b 异号的话有无穷多的解,因为原本的 x+b , y-a 因为异号都变成了正的,所以有无数个正整数解;

最后要注意 y 不合法的情况不仅是 y<=0,要注意 y 的最小正整数解是多少,所以还是从解的角度出发比较不容易错;

要怎样才能在考场上就把所有的细节正确地想出来呢?果然还是理解不透彻吧;

总之,感觉通过这一道考察基础的题,对扩展欧几里得的理解又加深了。

改后(AC)代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
ll T,a,b,c;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
void exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b){x=c/a; y=0; return;}//不得到 gcd 的解,而直接令 x=c/a? 
    exgcd(b,a%b,y,x);
    y-=a/b*x;
}
int main()
{
    freopen("fuction.in","r",stdin);
    freopen("fuction.out","w",stdout);
    scanf("%lld",&T);
    while(T--)
    {
        scanf("%lld%lld%lld",&a,&b,&c);
        if(c<0)a=-a,b=-b,c=-c;
        if(a==0&&b==0)
        {
            if(c==0)printf("ZenMeZheMeDuo\n");
            else printf("0\n");
            continue;
        }
        if(a==0)
        {
//            if(c%b==0&&c/b>0)printf("1\n");
            if(c%b==0&&c/b>0)printf("ZenMeZheMeDuo\n");//!!!  //可取任意个0 
            else printf("0\n");
            continue;
        }
        if(b==0)
        {
//            if(c%a==0&&c/a>0)printf("1\n");
            if(c%a==0&&c/a>0)printf("ZenMeZheMeDuo\n");//!!!
            else printf("0\n");
            continue;
        }
        bool f1=0,f2=0;
        if(a<0)f1=1,a=-a;
        if(b<0)f2=1,b=-b;
        ll g=gcd(a,b),x,y;
//        a/=g; b/=g; c/=g;//和平常 exgcd 写法不同? 
        exgcd(a,b,x,y);//
//        x*=c; y*=c; a*=g; b*=g; c*=g;
        if(a*x+b*y!=c){printf("0\n"); continue;}//等价于 c%g!=0 ,无解? 
        if(f1)a=-a,x=-x;
        if(f2)b=-b,y=-y;
        if(a*b<0)
        {
//            if(c%g)printf("0\n");
//            else //可有可无 
            printf("ZenMeZheMeDuo\n");//发现能增加任意成比例倍 
            continue;
        }
        if(a<0)a=-a,b=-b,c=-c;//此时 a,b 同号 
        a/=g; b/=g; c/=g;//
        
//        if(x<0)  //原来写法的残渣(为什么不行?(少了个特判?)) 
//        {
//            ll k=(-x)/b+1;
//            x+=k*b; y-=k*a;
//        }
//        else
//        {
//            ll k=x/b;
//            x-=k*b; y+=k*a;
//        }
//        if(y<=0){printf("0\n"); continue;} //
//        ll ans;
//        if(y%a==0)ans=y/a;
//        else ans=y/a+1;

        ll ans;
        x=(x%b+b)%b;
        if(x==0)x=b;
        y=(c-a*x)/b;
        ll mny=(y%a+a)%a;
        if(mny==0)mny=a;
        if(mny>y)ans=0;//不仅是 y<=0 
        else ans=(y-mny)/a+1;
        if(ans>65535)printf("ZenMeZheMeDuo\n");
        else printf("%lld\n",ans);
    }
    return 0;
}

T2:树上染色,把 m 个点染成黑色,其它点是白色,收益是白点两两之间距离和+黑点两两之间距离和,求最大收益。

以前做过的题,有个很妙的性质是一旦确定子树内部有多少黑点,就知道子树内部有多少白点,以及子树外的白点、黑点个数;

所以就可以依据这个算出子树根上这条边的贡献,于是问题成了个简单的树形DP,考场AC。

考场(AC)代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
int const maxn=2005;
int n,m,hd[maxn],ct,siz[maxn];
ll f[maxn][maxn];
struct N{
    int to,nxt,w;
    N(int t=0,int n=0,int w=0):to(t),nxt(n),w(w) {}
}ed[maxn<<1];
void add(int x,int y,int z){ed[++ct]=N(y,hd[x],z); hd[x]=ct;}
ll num(int siz,int x){return x*(m-x)+max(0,(siz-x)*(n-m-siz+x));}
void dp(int x,int fa)
{
    siz[x]=1;
    for(int i=hd[x],u;i;i=ed[i].nxt)
    {
        if((u=ed[i].to)==fa)continue;
        dp(u,x);
        siz[x]+=siz[u];
        for(int j=min(siz[x],m);j>=0;j--)//x中黑点数 
            for(int k=max(0,j-(siz[x]-siz[u]));k<=siz[u]&&k<=j;k++)//u中黑点数 
                f[x][j]=max(f[x][j],f[x][j-k]+f[u][k]+ed[i].w*num(siz[u],k));
//                printf("f[%d][%d]=%d f[%d][%d]=%d num(%d,%d)=%lld\n",
//                    u,k,f[u][k],x,j,f[x][j],siz[u],k,num(siz[u],k));
    }
}
int main()
{
    freopen("coloration.in","r",stdin);
    freopen("coloration.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1,x,y,z;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z); add(y,x,z);
    }
    int x=1; siz[x]=1; 
    for(int i=hd[x],u;i;i=ed[i].nxt)
    {
        dp(u=ed[i].to,x);
        siz[x]+=siz[u];
        for(int j=min(siz[x],m);j>=0;j--)//x中黑点数 
            for(int k=max(0,j-(siz[x]-siz[u]));k<=siz[u]&&k<=j;k++)//u中黑点数 
                f[x][j]=max(f[x][j],f[x][j-k]+f[u][k]+ed[i].w*num(siz[u],k));
//                printf("f[%d][%d]=%d f[%d][%d]=%d num(%d,%d)=%lld\n",
//                    u,k,f[u][k],x,j,f[x][j],siz[u],k,num(siz[u],k));
    }
    printf("%lld\n",f[x][m]);
    return 0;
}

T3:给定一张 n*m 的网格图,从左上(1,1)到右下(n,m),有一些格子是障碍格子,从起点格子的中心出发,向右上/右下/左上/左下发射一条光线,光线碰到障碍平面会反射,碰到障碍角会弹回来,碰到边界或边角同理,现在给出障碍坐标、起点坐标和初始方向,问最多能穿过多少个格子。(n,m,k <= 100000)

又是一脸不可做的T3...只会模拟啊...一格一格走什么的...

然而没时间写了,于是有了下面这个没过样例的爆0模拟:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
int const maxn=1e5+5;
int n,m,k,sx,sy,xx[4]={-1,-1,1,1},yy[4]={1,-1,1,-1};
int h[4]={2,3,0,1},s[4]={1,0,3,2},op[4]={3,2,1,0},tp[1005][1005];
ll ans;
bool vis[1005][1005],b[1005][1005];
bool ck(int x,int y){return (x<1||y<1||x>n||y>m);}
bool dfs(int x,int y,int t)
{
    if(vis[x][y]&&(tp[x][y]==t||tp[x][y]==op[t]))return 0;
    if(!vis[x][y])ans++;
    vis[x][y]=1; 
    int nx=x+xx[t],ny=y+yy[t];
    int tx=x+xx[t],ty=y+yy[t];
    if(b[nx][ny])
    {
        nx=x,ny=y;
        if((b[tx][ny]&&b[nx][ty])||(b[tx][ny]&&ck(nx,ty))
         ||(ck(tx,ny)&&b[nx][ty])||(ck(tx,ny)&&ck(nx,ty))
         ||(!b[tx]&&!b[ty]))return 1;
        else if(b[tx][ny])return dfs(tx,ny,h[t]);
        else return dfs(nx,ty,s[t]);
    }
    else
    {
        if(((nx>n||nx<1)&&(ty>m||ty<1))||((tx>n||tx<1)&&(ny>m||ny<1)))return 1;
        else if(nx>n||nx<1)return dfs(nx,ty,h[t]);
        else if(ny>m||ny<1)return dfs(tx,ny,s[t]);
        else return dfs(nx,ny,t);
    }
}
int main()
{
    freopen("ray.in","r",stdin);
    freopen("ray.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1,x,y;i<=k;i++){scanf("%d%d",&x,&y); b[x][y]=1;}
    char ch[5]; bool f=0;
    scanf("%d%d",&sx,&sy);
    cin>>ch; int fx;
    if(ch[0]=='N'&&ch[1]=='E')fx=0;
    if(ch[0]=='N'&&ch[1]=='W')fx=1;
    if(ch[0]=='S'&&ch[1]=='E')fx=2;
    if(ch[0]=='S'&&ch[1]=='W')fx=3;
    f=dfs(sx,sy,fx);
    if(f)dfs(sx+xx[op[fx]],sy+yy[op[fx]],op[fx]);
    printf("%d\n",ans);
    return 0;
}

(模拟都好难写啊...)

其实正解就是模拟,因为我们可以发现,能使光线发生改变的格子是 n 级别的,边界是 2*n , k 是 n;

所以模拟有保证,但也不能朴素地一格一格模拟;

因为光线在发生方向改变之前都不会改变路径,所以模拟的单位不是一格,而是一次方向改变;

讲题的同学(dalao)给出了“切比雪夫距离”的定义,大概就是横竖的格子斜着来看,横坐标变成 x+y,纵坐标变成 x-y;(???)

那么一条斜着的光线就成了横平竖直的啦!可以把新坐标放进 vector 里,到时二分查找撞上的会是哪一个障碍;

然而这个好难写啊,使用 vector 本来已经是简化了代码,但是好难写啊!

观摩那位同学(dalao)的代码,真是太优美了!对着它看了一晚上,才总算勉强理解,抄了一遍;

首先,我的习惯本来是 n 行 m 列,坐标的第一个是从上往下数行,第二个是从左往右数列;

可是那位同学(dalao)却和数学课上的平面直角坐标系一样从左往右数坐标的第一个,从下往上数坐标的第二个,为了读代码(TJ)方便,我也在这里暂时改变一下习惯;

把新坐标的横、纵坐标分别用两个 vector 存起来,再附带一个原横坐标,找的时候会方便一点;

方向用两个变量存,变方向的时候 *-1 就好了,很方便;

没想到因为对 vector 的自带函数不熟悉,读 TJ 都费了好大的劲...

有令人惊恐(且十分精妙)的细节,写了许多注释,这里就不再写了;(理解了一晚上)

真无法想象在考场上写出这样的代码,需要多加回顾啊...

改后(AC)代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
int const maxn=1e5+5;
int n,m,k,sx,sy,sdx,sdy,x,y,dx,dy;
bool f;
long long ans;
struct N{
    int x,y;
    N(int x=0,int y=0):x(x),y(y) {}
    bool operator < (const N &b) const
    {
        if(x==b.x)return y<b.y;
        else return x<b.x;
    }
};
vector<N>v[3];
void add(int x,int y)
{
    v[0].push_back(N(x-y,x));//
    v[1].push_back(N(x+y,x));//
}
void work()
{
    bool fl=(dx!=dy);//fl=1横走 
    N p=fl?N(x+y,x):N(x-y,x);
    vector<N>::iterator it=upper_bound(v[fl].begin(),v[fl].end(),p);//第一个大于p.x的 (精确到了原横坐标!!!)
    for(;it->x!=p.x;it--);//新坐标相等(且原横坐标第一个大于的) 
    //必须有这句,因为可能原本的y是相同x中最大的,于是就找到了x大于的地方 
    //上面的情况仅在dx<0时存在(退一步从障碍处出发时退到边缘),所以下面再处理一下到下一个障碍位置 
    if(dx<0)//横坐标-- 
        for(;it->y>=x;it--);//原横坐标第一个小于x的  
    ans+=abs(it->y-x)-1;//横看的经过格子数 (it是撞上的障碍,而且从障碍处出发,算下来一共-1)
    x=it->y; y=fl?(it->x-x):(x-it->x);//下一步横坐标是it带的原横坐标 //从新横坐标中得到当前的纵坐标 
//    bool u=binary_search(v[0].begin(),v[0].end(),N((x-dx)-y,x-dx)),
//         q=binary_search(v[0].begin(),v[0].end(),N(x-(y-dy),x));    //二者皆可 
    bool u=binary_search(v[1].begin(),v[1].end(),N((x-dx)+y,x-dx)),
         q=binary_search(v[1].begin(),v[1].end(),N(x+(y-dy),x));    //二者皆可 
    if(u==q)f=1,dx*=-1,dy*=-1;
    else if(u)x-=dx,dy*=-1;//退一步从障碍处出发 
    else if(q)y-=dy,dx*=-1;
}
int main()
{
    freopen("ray.in","r",stdin);
    freopen("ray.out","w",stdout);
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;i++)add(0,i),add(n+1,i);
    for(int i=0;i<=n+1;i++)add(i,0),add(i,m+1);//注意要包围起来!(网格图) 
    for(int i=1,x,y;i<=k;i++){scanf("%d%d",&x,&y); add(x,y);}
    sort(v[0].begin(),v[0].end());
    sort(v[1].begin(),v[1].end());
    char ch[5];
    scanf("%d%d",&x,&y); cin>>ch;
    dx=(ch[0]=='N')?-1:1;
    dy=(ch[1]=='W')?-1:1;
    work();
    sx=x; sy=y; sdx=dx; sdy=dy;//换起点,为了使它最后在起点处能停一下 
    ans=0;
    while(1)
    {
        work();
        if(x==sx&&y==sy&&dx==sdx&&dy==sdy)break;
    }
    if(f)ans>>=1;//原路返回则/2 
    printf("%lld\n",ans);
    return 0;
}

总结:细节复杂(其实也没有)的T1,思细极恐(就是大模拟)的T3,要怎样才能在考场上写出来...

考虑问题还需要更周密一点,写模拟也要讲究艺术啊。

猜你喜欢

转载自www.cnblogs.com/Zinn/p/9315145.html