欢迎来到蒟蒻mqd的博客

Ozon Tech Challenge 2020 (Div.1 + Div.2)

Ozon Tech Challenge 2020 (Div.1 + Div.2)

题目链接:https://codeforces.com/contest/1305

A. Kuroni and the Gifts

怎么做都能AC。

B. Kuroni and Simple Strings

题解:

要么0次,要么1次。从最外层开始去除,一定是最优的。因为最左边的"("可以匹配右边所有的")",而最右边的")"可以匹配左边的"("。也就是两边的括号能匹配的更多,所以需要优先去除。暴力就能过。

代码:

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
 
typedef long long ll;
 
const int INF=1e9+7;
const int N=5050;
int n,f[N],len;
char s[N];
bool dfs(set<int> &a){
    int l=len+1,r=0;
    for(int i=1;i<len;i++){
        if (f[i]==0 && s[i]=='('){
            l=i;
            f[i]=1;
            break;
        }
    }
    for(int i=len-1;i>=1;i--){
        if (f[i]==0 && s[i]==')'){
            r=i;
            f[i]=1;
            break;
        }
    }
    if (l>r)return 0;
    a.insert(l);
    a.insert(r);
    return 1;
}
void work(){
    scanf("%s",s+1);
    s[0]='a';
    len=strlen(s);
    vector<set<int>> ans;
    set<int>a;
    while(dfs(a));
    if (a.size()==0){printf("0\n");return ;}
    printf("1\n");
    printf("%d\n",(int)a.size());
    for(auto x:a){
        printf("%d ",x);
    }
}
int main(){
#ifndef ONLINE_JUDGE
    freopen("aa.in","r",stdin);
#endif
    work();
 
}

C. Kuroni and Impossible Calculation

题解

千万不要把他当做数论去想。关键就在模数很小只有1000。当\(n>m\)时,那肯定存在\((a[i]-a[j])\%m==0\) 。要是能想到这里,这道题就没了。

代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1050;
    int a[N];
    int main(){
        int n,mo,ans=1;
        scanf("%d %d",&n,&mo);
        if (n>mo){cout<<0<<endl;return 0;}
        for(int i=1;i<=n;i++){
            scanf("%d",a+i);
            for(int  j=1;j<i;j++){
                ans*=abs(a[j]-a[i])%mo;
                ans%=mo;
            }
        }
        cout<<ans<<endl;
    }

D. Kuroni and the Celebration

题解

我用的方法感觉比较玄学,一直WA来WA去。先随机选一个数为根,然后每次询问两个子节点,如果\(lca\)为子节点,那么实际的根肯定在子树里面。如果儿子都不是\(lca\),那么父亲就是根。这样做,有一个小问题,就是如果儿子是奇数的话,就会单出来一个,这样询问次数就可能就会超过\(\lfloor\frac{n}{2}\rfloor\)。那么就选择\(size[x]\approx[\frac{n}{2}],且son[x]\%2==0的点为根(size[x]为x子树的大小,son[x]表示x的儿子的个数)\)

还有就是每次先询问\(size\)更大的儿子。反正这些方法都比较玄学,要考虑会被什么数据卡,可麻烦了。

正解其实是类似拓扑排序,每次找两个度为1的点记为\(x,y\),然后询问,如果$lca(x,y)==x \(,那么\)x\(就是根,如果\)x,y$都不是根那就删除这两点,同时更新与之想连的点的度。这样能保证每次删除两个点。最后要么在中途找到,要么只剩下一个点。也就做完啦,感觉很好写。

代码一 玄学dfs

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
 
typedef long long ll;
 
const int INF=1e9+7;
const int N=5050;
int n,size[N],d[N],Tot,f[N];
struct Edge{int x,y,s;};
bool cmp(int x,int y){return size[x]>size[y];}
struct Graph{
    int tot=0,last[N];
    Edge edges[N<<1];
    void addEdge(int x,int y){
        edges[++tot]={x,y,last[x]};
        last[x]=tot;
    }
    void dfs1(int x,int pre){
        size[x]=1;
        for(int i=last[x];i;i=edges[i].s){
            Edge &e=edges[i];
            if (e.y==pre)continue;
            dfs1(e.y,x);
            size[x]+=size[e.y];
        }
    }
    void dfs(int x,int pre){
        static int a[N];
        int lca,num=0;
        for(int i=last[x];i;i=edges[i].s){
            Edge &e=edges[i];
            if (e.y==pre)continue;
            a[++num]=e.y;
        }
        a[num+1]=x;
        sort(a+1,a+num+1,cmp);
        for(int i=1;i<=num;i+=2){
            //fflush(stdout);
            printf("? %d %d\n",a[i],a[i+1]);
            Tot++;
            if (Tot>n/2)cout<<a[1000000];
            fflush(stdout); 
            scanf("%d",&lca);
            //fflush(stdout); 
            if (lca!=x){
                dfs(lca,x);
                return;
            }
        }
        printf("! %d\n",x);
        return;
    }
}G;
void work(){
    scanf("%d",&n);
    for(int i=1;i<=n-1;i++){
        int x,y;
        scanf("%d %d",&x,&y);
        d[x]++; d[y]++;
        G.addEdge(x,y);
        G.addEdge(y,x);
    }
    int mx=0,xx=0,yy=1;
    G.dfs1(1,0);
    for(int i=1;i<=n;i++){
        if (d[i]%2==0) yy=i;
        if (size[i]<=(n+1)/2 && d[i]%2==0 && size[i]>mx){
            mx=size[i];
            xx=i;
        }
    }
    if (xx==0)xx=yy;
    G.dfs1(xx,0);
    G.dfs(xx,0);
}
int main(){
#ifndef ONLINE_JUDGE
    //freopen("aa.in","r",stdin);
#endif
    work();
 
}

代码二 拓扑序

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1050;
    int n;
    struct Edge{int x,y,s;};
    struct Graph{
        int d[N],last[N],tot=0;
        Edge edges[N<<1];
        void addEdge(int x,int y){
            edges[++tot]=Edge{x,y,last[x]};
            last[x]=tot;
            d[x]++; d[y]++;
        }
        void addNode(queue<int> &Q, int x){
            for(int i=last[x];i;i=edges[i].s){
                Edge &e=edges[i];
                d[e.y]-=2;
                if (d[e.y]==2){Q.push(e.y);d[e.y]=-1;}
            }
        }
        void solve(){
            queue<int>Q;
            for(int i=1;i<=n;i++){
                if (d[i]==2){d[i]=-1;Q.push(i);}
            }
            while(!Q.empty()){
                int x=Q.front(); Q.pop();
                addNode(Q,x);
                if (Q.empty()){
                    printf("! %d\n",x);
                    fflush(stdout);
                    return;
                }
                int lca,y=Q.front(); Q.pop();
                addNode(Q,y);
                printf("? %d %d\n",x,y);
                fflush(stdout);
                scanf("%d",&lca);
                if (lca==x || lca==y){
                    printf("! %d\n",lca);
                    return;
                }
            }
        }
    }G;
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n-1;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            G.addEdge(x,y);
            G.addEdge(y,x);
        }
        G.solve();
    }

E. Kuroni and the Score Distribution

题解

我们先想怎样可以使得m很快被满足。因为序列单增,所以第\(i\)位,最多有\(\frac{i-1}{2}\)个数满足条件。即\(a[1]+a[i-1]=a[2]+a[i-2]=...=a[\frac{i-1}{2}]+a[i-\frac{i-1}{2}]\)

这个大概就是一个等差数列,于是就想着从\(1\)开始构建公差为\(1\)的等差数列。

那么如果超过了呢。假如到\(i\)位还需要\(x(x<\frac {i-1}{2})\)个三元组。那么只要令\(a[i]=a[i-1]+a[i-2*x]\)即可。

最后就是考虑剩下的不会再出现新的三元组。我们可以直接令\(a[i]=INF(INF表示一个很大的数,如10^8)\),然后之后每个数加上前面等差数列最大的那个值就行。假如前面枚举了五个数\(1,2,3,4,5\),那么之后就可以是\(100000,100006,100012...\)。然后就没了。一般构造题,方法都不只一种,没必要一定要构建一个最极限的方法。

#include<bits/stdc++.h>
using namespace std;
const int N=10050;
int ans[N];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    int Max=(n/2-1)*(n/2)+(n/2)*(n%2);
    if (m>Max){printf("-1\n");return 0;}
    if (m==0){
        for(int i=n+1;i<=2*n;i++)printf("%d%c",i," \n"[i==2*n]);
        return 0;
    }
    int num=0;
    ans[++num]=1;
    ans[++num]=2;
    for(int i=3;i<=n;i++){
        num++;
        int val=(i-1)/2;
        if (val<m){
            m-=val;
            ans[num]=ans[num-1]+ans[num-i+1];
        }else{
            ans[num]=ans[num-1]+ans[num-2*m];
            m=0;
            break;
        }
    }
    ans[num+1]=100000000;
    for(int i=num+2;i<=n;i++){
        ans[i]=ans[i-1]+ans[num]+1;
    }
    for(int i=1;i<=n;i++)printf("%d%c",ans[i]," \n"[i==n]);
    

F. Kuroni and the Punishment

题解

想了半天,首先最容易发现,每个奇数加1,是一种可行方案,\(ans\le n\)

之后想怎么枚举公约数,只要枚举质数就行了,但是显然没办法枚举太多。

然后又想,应该很多点是不会变动的。因为平均每个变动不到一次\((\frac{ans}{n}<1)\)

于是就想随遍抽取两个数,把他们的最大公约数作为所有数的公约数,重复100次。然后WA了,但是总感觉就是一个随机抽取。于是又想啊想。终于明白了。

肯定至少有一半的数变动等于0或1。我们可以求变动为2的数最多为\(k\),则

\(2*k\le n\Rightarrow k\le \frac{n}{2}\)。也就是说一半以上都是变动1或0。那我们随便抽一个数,它变动大于1的概率是\(\frac{1}{2}\),我们抽取20次,一次都抽不到的概率则为\(\frac{1}{2^{20}}\),这个概率几乎是不可能。也就是说抽20次肯定能抽到变动为0或1的数。那我们就把每次抽到的数\(x,x\pm1\)进行质因数分解,再把这些质数当做公约数求一遍,取最小值即可。

其实抽10次就已经差不多了。我试了两次,第一次过了,第二次WA在160个点左右。

最后告诫大家最好不要用\(rand()\),因为它在codeforces上的范围是\([0,32767]\)

具体大家可以看看:https://www.cnblogs.com/RabbitHu/p/10390146.html

代码

    #include<bits/stdc++.h>
    using namespace std;
    #define gcd __gcd
    //#define RAND_MAX 200050
    typedef long long ll;
    const int N=200050;
    set<ll> S;
    ll a[N],ans;
    int n;
    void dfs(ll x){
        ll len=(ll)(sqrt(x));
        for(ll i=2;i<=len;i++){
            if (x%i==0){
                S.insert(i);
                while(x%i==0)x/=i;
            }
        }
        if  (x>1)S.insert(x);
    }
    void check(ll x){
        ll tp=0;
        for(int i=1;i<=n;i++){
            if (a[i]<=x)
                tp+=x-a[i];
            else 
                tp+=min(a[i]%x,x-a[i]%x);
            if (tp>=ans)return;
     
        }
        ans=tp;
    }
    int main(){
        mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
        scanf("%d",&n);
        ll x,y;
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
            if (a[i]&1)ans++;
        }
        for(int i=1;i<20;i++){
            x=rng()%n+1;
            dfs(a[x]);
            dfs(a[x]+1);
            if (a[x]>1)dfs(a[x]-1);
        }
        for(ll x:S)check(x);
        
        printf("%lld ",ans);
    }
posted @ 2020-03-05 15:00  mmqqdd  阅读(201)  评论(0编辑  收藏  举报