B. Coffee Chicken

大致题意:

  • S(1)="COFFEE";
  • S(2)="CHICKEN";
  • S(n)=S(n-2):S(n-1)----即第n-2个字符串作为前缀,第n-1个字符串作为后缀.
    T组询问,求第n个字符串中第k个位置的字符.(1<=T<=1000,1<=n<=500,k<=min{ |S(n)|,1e12} )

分析:

  • 我们可以处理出长度<=1e12的字符串一共57个
  • 我们可以直接对长度进行dfs分治求pos位置.需要注意的是n>56时,前缀字符串跟n的奇偶性有关.
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

ll a[502];
string s[3];

void dfs( ll x,ll k,ll r )
{
    if( x<=2 )
    {
        for( int i=k-1;i<min(r,(ll)s[x].size());i++ ) cout<<s[x][i];
        return ;
    }

    if( r<=(ll)a[x-2] ) dfs(x-2,k,r);
    else if( k<=a[x-2] && r>a[x-2] )
    {
        dfs(x-2,k,a[x-2]);
        dfs(x-1,1,r-a[x-2]);
    }
    else
    {
        k-=a[x-2];
        dfs(x-1,k,r-a[x-2]);
    }
}

int main()
{
    s[1]="COFFEE";s[2]="CHICKEN";
    a[1]=6,a[2]=7;
    for( int i=3;i<=57;i++ ) a[i]=a[i-1]+a[i-2];
    int t;
    scanf("%d",&t);
    while( t-- )
    {
        ll n,k;
        scanf("%lld%lld",&n,&k);
        if( n>56 ) 
        {
            if( n%2==0 ) n=56;
            else n=57;
        }
        dfs(n,k,k+9);
        puts("");
    }

}

C. Gifted Composer

大致题意:七个音符{do re mi fa sol la si},有一个空序列,n次操作,每次操作在序列头部或尾部插入一个音符,问每次操作有多少种循环节.----循环节定义 能划分成k个重复的部分 后面允许跟着一个不完整的部分(完整部分的初始部分)
图片说明
分析:对字符串进行哈希处理,记录每次操作对应字符串S的区间[L,R],枚举循环节长度为i,二分找到符合字符串的最后出现的操作pos,那么长度i的循环节的贡献[i,pos],利用差分统计答案即可.

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=2e6+10;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;

using namespace std;
char s[10],t[maxn],ss[10];
int L[maxn],R[maxn];
int a[maxn];

struct Hash{
    ll p[maxn],hash[maxn],base=131;
    ll getHash(int l,int r )
    {
        ll ans=(hash[r]-hash[l-1]*p[r-l+1])%mod;
        return (ans+mod)%mod;
    }
    void init( char *buf )
    {
        int n=strlen(buf);
        p[0]=1;
        for( int i=1;i<=n;i++ ) p[i]=p[i-1]*base%mod;
        for( int i=1;i<=n;i++ )
        {
            hash[i]=( hash[i-1]*base%mod+(buf[i-1]-'a'+1) )%mod;
        }
    }
}hh;

int check( int q,int x )
{
    return ( hh.getHash(L[q],R[q]-x)==hh.getHash(L[q]+x,R[q]) );
} 


int main()
{
    int n,head=1e6+1,tail=1e6+2;
    scanf("%d",&n);
    for( int i=1;i<=n;i++ )
    {
        scanf("%s%s",ss,s);
        char c=s[0];
        if( c=='s' && s[1]=='i' ) c='z';
        if( ss[0]=='a' ) t[tail++]=c;
        else t[head--]=c;
        L[i]=head+1,R[i]=tail-1;
    } 
    for( int i=1;i<=n;i++ ) L[i]-=head,R[i]-=head;
    t[tail]=0;
    hh.init(t+head+1);
    for( int i=1;i<=n;i++ )
    {
        int l=i,r=n;
        while( l<=r )
        {
            int mid=(l+r)>>1;
            if( check(mid,i) ) l=mid+1;
            else r=mid-1;
        }
        a[i]++,a[r+1]--;
    } 
    for( int i=1;i<=n;i++ )
    {
        a[i]+=a[i-1];
        printf("%d\n",a[i]);
    }
    return 0;
} 

D. Han Xin and His Troops

大致题意:给定n个( x=b( mod a ) )同余方程,求最小的x满足所有方程.(n<=100,m<=1e18,0<b<a<1e5 )
分析:两种解法.

  • 1.套用CRT板子-----注意爆longlong 要用__int128
#include<iostream>
#include<cstdio>
using namespace std;



const int maxn=1e5+10;
typedef __int128 ll;


ll m[maxn];
ll a[maxn];
ll m1;

void read(ll &x )
{
    x=0;int f=1;char s=getchar();
    for( ;!isdigit(s);s=='-' && (f=-1),s=getchar());
    for( ;isdigit(s);x=x*10+s-48,s=getchar());
    x*=f;
}


inline void print( ll x )
{
    if( x<0 )
    {
        x=-x;
        putchar('-');
    }
    if( x>9 ) print(x/10);
    putchar( x%10+'0' );
}

struct node{

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

    ll solve( ll *a,ll *m,ll n )
    {
        ll c=a[0],l=m[0];
        ll d,x,y;
        for(int i=1;i<n;i++ )
        {
            d=exgcd( l,m[i],x,y );
            if( ( a[i]-c)%d )  return -1;   
            x=( a[i]-c )/d*x%( m[i]/d );
            c+=l*x;
            l=l/d*m[i];
            c%=l;
        }
        return c >= 0 ? c : c+l;
    }

}CRT;

int main()
{
    ll n;
    read(n);read(m1);
    for( int i=0;i<n;i++ )
    {
        read(m[i]);
        read(a[i]);
    }
    ll ans=CRT.solve(a,m,n);
    if( ans==-1 ) printf("he was definitely lying\n");
    else if( ans>m1 ) printf("he was probably lying\n");
    else print(ans);
}
  • 2.出题人推荐的方法.利用LCM(p1,p2...p-1)=y, 在有解的情况下满足pi同余式,ans+=y,可以证明加的次数不超过pi次.并且该方法不会爆long long. ( 有解判定,n个模数是否互质 ).-------该方法只适用于模数较小情况.
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;


ll a[105],b[105];
ll n,m;

void solve()
{
    ll ans=b[1];
    ll tmp=a[1];
    for( int i=1;i<=n;i++ )
    {
        for( int j=1;j<=i;j++ )
        {
            int gd=__gcd(a[i],a[j]);
            if( b[i]%gd!=b[j]%gd )
            {
                puts("he was definitely lying");
                return;
            } 
        }
    }


    for( int i=2;i<=n;i++ )
    {
        for( int j=0;j<a[i] && ans<=m && ans%a[i]!=b[i] ;j++ ) ans+=tmp;
        if( ans>m ) 
        {
            puts("he was probably lying");
            return;
        }
        tmp=tmp/__gcd(tmp,a[i])*a[i];
    }
    printf("%lld\n",ans);
}


int main()
{
    scanf("%lld%lld",&n,&m);

    for( int i=1;i<=n;i++ )    scanf("%lld%lld",&a[i],&b[i]); 
    solve();

}

F. Popping Balloons

大致题意:给定n*n的矩阵,有n个气球分布在矩阵中,我们横着打三枪,竖着打三枪,打枪 横/竖 间隔为r.求最多能打掉多少气球.
分析:该题目有三种方法.

  • 1.出题人解法.-----暂无标程

  • 暴力枚举横向打法.我们考虑打x1,x1+r,x1+2 * r 三枪,用g(i)表示i列气球个数和.横向打枪的打中气球(x,y),会影响列向气球个数.那么我们可以用一个muliset维护列向气球个数.对于横向每一个方案,对于影响的列向气球个数进行log(n)取出修改插入.我们可以计算每个气球的操作次数为9log(n) .那么该方案的复杂度为O(9n*log(n) )

  • 2.解法二

  • 我们处理出所有横向打法的方案贡献并排序.最终答案肯定出现在最大的几个方案中,那么我们可以暴力计算最大的100个方案,进行更新即可。(实测只需要暴力计算最大的4..5个方案)

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;

int n,r,fx[maxn*3],fy[maxn*3],res;

struct node{
    int x,y;
}a[maxn],w[maxn];

bool cmp( node xx,node yy )
{
    return xx.x>yy.x;
}
int main()
{
    cin>>n>>r;
    memset(fx,0,sizeof(fx));
    for( int i=0;i<n;i++ )
    {
        cin>>a[i].x>>a[i].y;
        fx[a[i].x]++;
    }
    for( int i=0;i<maxn;i++ )
    {
        w[i].x=fx[i]+fx[i+r]+fx[i+2*r];
        w[i].y=i;
    }
    sort(w,w+maxn,cmp);
    for( int i=0;i<=4;i++ )
    {
        memset(fy,0,sizeof(fy));
        for( int j=0;j<n;j++ )
        {
            if( a[j].x!=w[i].y && a[j].x!=w[i].y+r && a[j].x!=w[i].y+2*r )
            {
                fy[a[j].y]++;
            }
        }  
        int my=0;
        for( int j=0;j<maxn;j++ ) my=max( my,fy[j]+fy[j+r]+fy[j+2*r] );
        res=max(res,my+w[i].x);
    }
    cout<<res<<endl;
}

3.解法三----rank1 杜大佬的方法 复杂度O(n) 暂时没搞懂.......鸽了.....


G.Road Construction

大致题意:给定n个点的坐标,请画一条直线满足(n<=300,xi,yi<=1e9)

  • 将点均分成两部分。
  • 并且直线上不过点
  • 使得所有点到直线的距离的最小值 最大.

分析:我们可以得到结论----满足直线一定是平行某两个点的连线或者是某两点连线的中垂线。(简单证明即可).
n不超过300,然后我们枚举所有的斜率,把所有点按斜率排序,取中间两个点,让这两个点到直线距离相等,这个距离就是当前的最小距离。然后求最小距离最大值。复杂度O(n^3*logn)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=302;

struct node{
    double x,y,b;
    bool operator <  ( const node &t )const{
        return b<t.b;
    }
}p[maxn];

double x[maxn],y[maxn];

double dis( double k,double b,double x,double y )
{
    return fabs(k*x+b-y)/sqrt(k*k+1.0);
}

int main()
{
    int n;
    scanf("%d",&n);
    for( int i=1;i<=n;i++ )
    {
        scanf("%lf%lf",&x[i],&y[i]);
        p[i].x=x[i];
        p[i].y=y[i];
    }
    double ans=0;
    for( int i=1;i<=n;i++ )
    {
        for( int j=i+1;j<=n;j++ )
        {
            double k=(y[i]-y[j])/(x[i]-x[j]); // 平行的情况 
            for( int l=1;l<=n;l++ )
            {
                p[l].b=p[l].y-k*p[l].x;
            }
            sort(p+1,p+1+n);
            double b=(p[n/2].b+p[n/2+1].b)/2;
            ans=max( ans,dis(k,b,p[n/2].x,p[n/2].y) );

            k=-1.0/k;                         // 垂直的情况 
            for( int l=1;l<=n;l++ )
            {
                p[l].b=p[l].y-k*p[l].x;
            }
            sort(p+1,p+1+n);
            b=(p[n/2].b+p[n/2+1].b)/2;
            ans=max( ans,dis(k,b,p[n/2].x,p[n/2].y) );
        }
    }
    printf("%.10lf\n",ans);
    return 0; 
} 

---nth_element可以将复杂度降至O(n^3)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=302;


double x[maxn],y[maxn],b[maxn],ans;
int n;

void solve( double k )
{
    for( int i=0;i<n;i++ ) b[i]=y[i]-k*x[i];
    nth_element(b,b+n/2,b+n);
    double d1=b[n/2];
    double d2=*max_element(b,b+n/2);
    double w=(d1-d2)/sqrt(1+k*k); 
    ans=max(ans,w/2);
}


int main()
{

    scanf("%d",&n);
    for( int i=0;i<n;i++ )    scanf("%lf%lf",&x[i],&y[i]);
    for( int i=0;i<n;i++ )
    {
        for( int j=i+1;j<n;j++ )
        {
            double k=(y[i]-y[j])/(x[i]-x[j]);
            solve(k);
            solve(-1.0/k);
        }
    }
    printf("%.12lf\n",ans);
    return 0; 
} 

H.Stammering Chemists

大致题意:给定6个点,5条边.判断该图形结构.
分析:按点的度判断即可.

#include<bits/stdc++.h>
using namespace std;

// 1 2 3 4

// 2 4       n-hexane 
// 3 2 1     2-methylpentane
// 3 2 1     3-methylpentane
// 4 0 2     2,3-dimethylbutane
// 4 2 0 1   2,2-dimethylbutane

vector<int>g[10];
int ans=0;

void dfs( int x,int f ,int dep )
{
    ans=max(ans,dep);    
    for( auto v:g[x] )
    {
        if( v==f ) continue;
        dfs(v,x,dep+1);
    }
}

int main()
{
    int t;
    cin>>t;
    while( t-- )
    {
        ans=0;
        int vis[5]={0};
        for( int i=1;i<=6;i++ ) g[i].clear();
        for( int i=1;i<6;i++ )
        {
            int u,v;cin>>u>>v;
            g[u].push_back(v);
            g[v].push_back(u); 
        }
        for( int i=1;i<=6;i++ ) vis[ g[i].size() ]++;
        if( vis[1]+vis[2]==6 ) puts("n-hexane");
        else if( vis[1]+vis[3]==6 ) puts("2,3-dimethylbutane");
        else if( vis[1]+vis[2]+vis[4]==6 ) puts("2,2-dimethylbutane");
        else 
        {
            int s;
            for( int i=1;i<=6;i++ ) if( g[i].size()==3 ) s=i;
            dfs(s,0,0);
            if( ans==2 ) puts("3-methylpentane");
            else puts("2-methylpentane");
        }
    }
}