1. 程式人生 > >AYITOJ ROUND #1題解

AYITOJ ROUND #1題解

自我感覺這一套題的難度梯度還是很科學的。我的預期是絕大部分人做出來簡單題,少量人ac中等題。E題之前出過類似的題目,G題是一個明顯的二分,如果這兩道題都沒思路,可以肝B題。做題情況還是不容樂觀的。

分析原因可能還是大家見題比較少,尤其是新題。不能只做專題,可以經常做一下牛客網的比賽或者codeforces鍛鍊一下思維。

藉助本次比賽修復了網站很多bug還是很好的。

就在我寫這篇部落格的時候居然有人用搜索過了E?並且耗時極少。一道構造題就這樣變成了搜尋。。。

還好有人用構造過了E。

 

難度分佈:

簡單題:DF
中等題:BEG
難題:AC

 

A.VN的五殺

思路:

經分析得只有敵方周圍的最多5*8個點為有效點,我們可以在這最多40個點的範圍內進行搜尋。
可以將這些點分為5個集合,我們先得出一個攻擊序列,然後每次找一個集合中的兩個點,入點和出點。
可以發現入點可以取集合中距離上一個出點最近的點,並且vn在每一個集合中停留的時間一定恰好為5。
這樣我們只需要在每個集合中搜索一個出點即可。

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 1005
#define inf 0x3f3f3f3f3f3f3f3f
int dir[8][2]= {1,0,0,1,-1,0,0,-1,1,-1,-1,1,1,1,-1,-1};
int all[300][5],path[5];
ll stx,sty;
struct node
{
    ll x,y;
} p[6],e;
vector<node> vec[5];
ll ans;
ll distance(ll x1,ll y1,ll x2,ll y2)
{
    return abs(x1-x2)+abs(y1-y2);
}
void dfs(int step,ll lasty,ll lastx,ll s)
{
    if(s>ans) return;
    if(step==5)
    {
        ans=s;
        return;
    }
    //printf(">>%d %lld %lld %lld\n",step,lasty,lastx,s);
    int n=path[step];
    ll minn=inf;
    for(int i=0; i<vec[n].size(); i++)
    {
        e.y=vec[n][i].y;
        e.x=vec[n][i].x;
        if(distance(e.x,e.y,lastx,lasty)<minn)
            minn=distance(e.x,e.y,lastx,lasty);
    }
    for(int i=0; i<vec[n].size(); i++)
    {
        e=vec[n][i];
        dfs(step+1,e.y,e.x,s+minn+5);
    }
}
int main()
{
    scanf("%lld%lld",&stx,&sty);
    for(int i=0; i<5; i++)
        scanf("%lld%lld",&p[i].x,&p[i].y);
    for(int i=0; i<5; i++)
    {
        for(int j=0; j<8; j++) //找五個點周圍的點
        {
            ll ty=p[i].y+dir[j][0];
            ll tx=p[i].x+dir[j][1];
            int flag=0;
            for(int k=0; k<5; k++) //判斷是否重合
            {
                if(ty==p[k].y && tx==p[k].x)
                {
                    flag=1;
                    break;
                }
            }
            e.y=ty;
            e.x=tx;
            if(!flag) vec[i].push_back(e);
        }
    }
    ans=inf;
    for(int i=0; i<5; i++) path[i]=i;
    do
    {
        dfs(0,sty,stx,0);
    }while(next_permutation(path,path+5));
    printf("%lld\n",ans);
    return 0;
}

 

B.化簡方程式

思路:

可以定義一個結構體表示含x的標準項。重定義運算子。用棧先處理出乘法運算,加法運算可以直接用一個數組記錄x對應次冪下的係數,減法運算可以轉化為加法。

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 105
#define inf 0x3f3f3f3f
struct node
{
    int xi,mi;
    node(){}
    node(int xx,int mm)
    {
        xi=xx;
        mi=mm;
    }
} p;
bool cmp(node a,node b)
{
    return a.mi>b.mi;
}
int xx[10005];
char str[MAXN];
node add(node a,node b)
{
    return node(a.xi+b.xi,a.mi);
}
node mul(node a,node b)
{
    return node(a.xi*b.xi,a.mi+b.mi);
}
stack<node> st;
stack<char> fu;
int main()
{
    scanf("%s",str);
    for(int i=0; str[i]; i++)
    {
        int d=0;
        int flag=0;
        int j=i;
        if(str[i]=='-')
        {
            flag=1;
            j++;
        }
        for(j; isdigit(str[j]); j++)
        {
            d=d*10+str[j]-'0';
        }
        if(d==0) p.xi=1;
        else p.xi=d;
        if(str[j]!='x') p.mi=0;
        else
        {
            j++;
            if(str[j]!='^') p.mi=1;
            else
            {
                d=0;
                for(j=j+1; isdigit(str[j]); j++)
                {
                    d=d*10+str[j]-'0';
                }
                p.mi=d;
            }
        }
        if(flag) p.xi=-p.xi;
        //printf("%dx^%d %d\n",p.xi,p.mi,j);
        st.push(p);
        if(!fu.empty() && fu.top()=='*')
        {
            node a=st.top(); st.pop();
            node b=st.top(); st.pop();
            fu.pop();
            st.push(mul(a,b));
        }
        if(str[j]=='-') j--,fu.push('+');
        else if(str[j]=='+') fu.push('+');
        else if(str[j]=='*') fu.push('*');
        i=j;
    }
    memset(xx,0,sizeof xx);
    int maxx=0;
    while(!st.empty())
    {
        node top=st.top(); st.pop();
        xx[top.mi]+=top.xi;
        maxx=max(maxx,top.mi);
    }
    int out=0;
    for(int i=maxx;i>=1;i--)
    {
        if(xx[i]==0) continue;
        out=1;
        if(xx[i]<0)
        {
            if(xx[i]==-1) printf("-");
            else printf("%d",xx[i]);
        }
        else
        {
            if(i<maxx) printf("+");
            if(xx[i]>1) printf("%d",xx[i]);
        }
        printf("x");
        if(i>1) printf("^%d",i);
    }
    if(xx[0]!=0)
    {
        if(maxx==0 || xx[0]<0) printf("%d",xx[0]),out=1;
        else printf("+%d",xx[0]),out=1;
    }
    if(!out) printf("0");
    printf("\n");
    return 0;
}

 

C.取數遊戲

思路:

可以用dpa[i]表示當前數為i時,輪到Alice取數時,Alice最終能取到的最大值。
dpb[i]表示當前數為i時,輪到Bob取數時,Alice最終能取到的最小值。
轉移方程:
dpa[i]=max( dpb[i/k]+k , max( dpb[p] | i-m<=p<=i-1 )+p , dpb[i] ); k為i的素因子
dpb[i]=min( dpa[i/k] , min( dpa[p] | i-m<=p<=i-1 ), dpa[i]);
操作一我們可以對當前數進行分解,直接暴力列舉質因子會超時。
操作二我們可以用兩個單調佇列維護[i-m,i-1]區間內的最小dpa和最大dpb。
更新dpa,維護que2的時候需要考慮下標的影響,下標的影響為負。

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 100005
#define inf 0x3f3f3f3f
int n,m,dpa[MAXN],dpb[MAXN],que1[MAXN],tmp1[MAXN],que2[MAXN],tmp2[MAXN];
int prime[MAXN];
bool is_prime[MAXN];
//返回n以內素數的個數
int sieve(int n)
{
    int p = 0;
    for(int i = 0; i<= n; i++) is_prime[i] = true;
    is_prime[0] = is_prime[1] = false;
    for(int i = 2; i <= n; i++)
    {
        if(is_prime[i])
        {
            prime[p++] = i;
            for(int j = 2 * i; j <= n; j += i) is_prime[j] = false;
        }
    }
    return p;
}
int main()
{
    scanf("%d%d",&n,&m);
    dpa[0]=0;
    dpb[0]=0;
    int p=sieve(n);
    int head1=1,tail1=0,head2=1,tail2=0;
    for(int i=1; i<=n; i++)
    {
        int last;
        dpb[i]=inf;
        dpa[i]=0;
        int d=i;
        for(int j=0; j<p && prime[j]<=d && prime[j]*prime[j]<=i; j++)
        {
            int num=prime[j];
            if(d%num==0)
            {
                dpb[i]=min(dpb[i],dpa[i/num]);
                dpa[i]=max(dpa[i],dpb[i/num]+num);
                while(d%num==0) d/=num;
            }
        }
        if(d>1)//壓縮時間
        {
            dpb[i]=min(dpb[i],dpa[i/d]);
            dpa[i]=max(dpa[i],dpb[i/d]+d);
        }
        while(head1<=tail1 && tmp1[head1]<i-m) head1++;
        while(head1<=tail1 && que1[tail1]>=dpa[i-1]) tail1--;
        que1[++tail1]=dpa[i-1];
        tmp1[tail1]=i-1;
        dpb[i]=min(dpb[i],que1[head1]);

        while(head2<=tail2 && tmp2[head2]<i-m) head2++;
        while(head2<=tail2 && que2[tail2]<=dpb[i-1]-i+1) tail2--;
        que2[++tail2]=dpb[i-1]-i+1;
        tmp2[tail2]=i-1;
        dpa[i]=max(dpa[i],que2[head2]+i);
    }
    printf("%d\n",dpa[n]);
    return 0;
}

 

D.貪吃魚

思路:

從後往前掃,維護最大值。

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 100005
int n,a[MAXN];
int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
        scanf("%d",a+i);
    int maxx=0,cnt=0;
    for(int i=n-1;i>=0;i--)
    {
        if(a[i]>=maxx) maxx=a[i];
        else cnt++;
    }
    printf("%d\n",n-cnt);
	return 0;
}

 

E.挑戰數獨

思路:

可以先用錯位排列構造出一個全空的數獨的解,然後3行3列的交換即可,可以發現一定會構造出解。

錯位排列的結果可以參考maze1矩陣。

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int maze1[9][9]= {{1,2,3,4,5,6,7,8,9},
    {4,5,6,7,8,9,1,2,3},
    {7,8,9,1,2,3,4,5,6},
    {2,3,4,5,6,7,8,9,1},
    {5,6,7,8,9,1,2,3,4},
    {8,9,1,2,3,4,5,6,7},
    {3,4,5,6,7,8,9,1,2},
    {6,7,8,9,1,2,3,4,5},
    {9,1,2,3,4,5,6,7,8}
};
int maze[9][9];
int x,y,d;
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&y,&x,&d);
        x--;
        y--;
        memcpy(maze,maze1,sizeof maze1);
        int tt=-1;
        for(int i=0; i<3; i++)
        {
            for(int j=0; j<3; j++)
            {
                if(maze[i*3+y%3][j*3+x%3]==d)
                {
                    tt=i*3+j;
                    break;
                }
            }
            if(tt!=-1) break;
        }
        for(int i=y/3*3; i<y/3*3+3; i++)
            for(int j=0; j<9; j++)
                swap(maze[i][j],maze[tt/3*3+i-y/3*3][j]);

        for(int i=x/3*3; i<x/3*3+3; i++)
            for(int j=0; j<9; j++)
                swap(maze[j][i],maze[j][tt%3*3+i-x/3*3]);

        for(int i=0; i<9; i++)
        {
            for(int j=0; j<9; j++)
            {
                if(j) printf(" ");
                printf("%d",maze[i][j]);
            }
            printf("\n");
        }
    }
    return 0;
}

 

F.褻瀆

思路:

判斷是否連續。

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 105
#define inf 0x3f3f3f3f
int n,a[MAXN];
bool vis[MAXN];
int main()
{
    scanf("%d",&n);
    memset(vis,0,sizeof vis);
    int maxx=0;
    for(int i=0;i<n;i++)
    {
        scanf("%d",a+i);
        maxx=max(maxx,a[i]);
        vis[a[i]]=1;
    }
    int flag=0;
    for(int i=1;i<=maxx;i++)
    {
        if(!vis[i])
        {
            flag=1;
            break;
        }
    }
    if(flag) puts("No");
    else puts("Yes");
    return 0;
}

 

G.字母

思路:

對26個字母二分最小間隔。

check時可以用vector存字母出現的位置,然後lower_bound掃一遍即可。

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define MAXN 1000005
int n,k,ans;
char str[MAXN];
vector<int> vec[30];
int check(int len)
{
    for(int i=0;i<26;i++)
    {
        if(vec[i].size()<=k) continue;
        int last=vec[i][0];
        for(int j=1;j<k;j++)
        {
            int it=lower_bound(vec[i].begin(),vec[i].end(),last+len+1)-vec[i].begin();
            last=vec[i][it];
            if(last==inf) break;
            if(j==k-1) return 1;
        }
    }
    return 0;
}
void find(int L,int R)
{
    if(L>=R) return;
    int mid=(L+R)/2;
    if(check(mid))
    {
        ans=mid;
        find(mid+1,R);
    }
    else find(L,mid);
}
int main()
{
	scanf("%d%d",&n,&k);
	scanf("%s",str);
	for(int i=0;str[i];i++)
        vec[str[i]-'a'].push_back(i);
    for(int i=0;i<26;i++)
        vec[i].push_back(inf);
    ans=-1;
    find(0,n+1);
    printf("%d\n",ans);
	return 0;
}