Codeforces Round 897 (Div. 2)

\(A. green_gold_dog, array and permutation\)

让大的数减小的数就可以制造更多的不同。

PII a[N];
int ans[N];
void solve(){
    int n=read();
    for(int i=1;i<=n;i++){
        a[i]=make_pair(read(),i);
    }
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++){
        ans[a[i].second]=(n-i+1);
    }
    for(int i=1;i<=n;i++){
        cout<<ans[i]<<" ";
    }
    cout<<'\n';
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(B. XOR Palindromes\)

首先答案显然对称。
找到最小的变化次数然后依次加 \(2\) 因为每次可以多变一对,直到加到答案对称的位置。
如果是奇数显然可以多变一个中间的数字,填补变一对的的空缺。

void solve(){
    int n=read();
    string s;
    cin>>s;
    s=' '+s;
    int minn=0;
    for(int i=1;i<=n&&i<=n-i+1;i++){
        if(s[i]!=s[n-i+1])minn++;
    }
    if(n%2==0){
        for(int i=0;i<=n;i++){
            if(minn<=i&&i<=n-minn&&i%2==minn%2){
                cout<<1;
            }else cout<<0;
        }
    }else{
        for(int i=0;i<=n;i++){
            if(minn<=i&&i<=n-minn){
                cout<<1;
            }else cout<<0;
        }
    }
    cout<<'\n';
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(C. Salyg1n and the MEX Game\)

每次加入 \(Mex\) 即可,感觉很显然啊,这样可以一直逼近 \(0\) ,直到不能再 \(move\)
如果想要更大的 \(Mex\) 将来不及补缺,不能做到。

int a[N];
void solve(){
    int n=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    int cnt=0,op=a[n]+1;
    for(int i=1;i<=n;i++){
        if(a[i]==cnt)cnt++;
        else {
            op=cnt;
            break;
        }
    }
    while(1){
        cout<<op<<endl;
        int x;
        cin>>x;
        if(x==-1)break;
        op=x;
    }
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(D. Cyclic Operations\)

对位置和点权建边,跑强连通。
如果一个点位于单链上,分析可知一定可以完成赋值的,因为总存在一个点不需要对其他点负责,而且也可以多次对一个点覆盖。
如果一个点位于强连通分量上,显然要大小等于 \(k\) ,感觉这个没什么好说的。

int cnt,low[N],num[N],dfn,sccno[N],sta[N],top;
int siz[N];
vector<int>G[N];
void dfs(int u){
    sta[top++]=u;
    low[u]=num[u]=++dfn;
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i];
        if(!num[v]){
            dfs(v);
            low[u]=min(low[v],low[u]);
        }else if(!sccno[v]){
            low[u]=min(low[u],num[v]);
        }
    }
    if(low[u]==num[u]){
        cnt++;
        while(1){
            int v=sta[--top];
            sccno[v]=cnt;
            if(u==v)break;
        }
    }
}
void Tarjan(int n){
    cnt=top=dfn=0;
    for(int i=1;i<=n;i++){
        if(!num[i]){
            dfs(i);
        }
    }
}
void solve(){
    int n=read(),k=read(),ans=1;
    for(int i=0;i<=n+100;i++){
        G[i].clear();
        num[i]=0;
        sccno[i]=0;
        siz[i]=0;
        low[i]=0;
    }
    for(int i=1;i<=n;i++){
        int x=read();
        G[i].push_back(x);
    }
    if(k==1){
        int ok=1;
        for(int i=1;i<=n;i++){
            if(G[i][0]!=i)ok=0;
        }
        puts(ok>0?"YES":"NO");
        return ;
    }
    Tarjan(n);
    for(int i=1;i<=n;i++){
        siz[sccno[i]]++;
    }
    for(int i=1;i<=n;i++){
        if((siz[sccno[i]]==1&&G[i][0]==i)||(siz[sccno[i]]!=1&&siz[sccno[i]]!=k))ans=0;
    }
    puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(E1. Salyg1n and Array (simple version)\)

对于前面的整块显然是直接询问,对于剩下的数字需与最后一个整块合并,每次询问因为会反转,依次问这个区间就可以得到这个区间的异或和。分析我们的询问次数。前半段最多花费 \(\frac{n}{k}\) 次询问,后半段需要 \(k\) 次询问,和不会超过 \(100\) 次。

int ask(int x){
    cout<<"? "<<x<<endl;
    return read();
}
void solve(){
    int n=read(),k=read(),ans=0;
    for(int i=1;i+k-1<=n;){
        ans^=ask(i);
        if(i+k*2-1<=n)i+=k;
        else i++;
    }
    cout<<"! "<<ans<<endl;
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(E2. Salyg1n and Array (hard version)\)

观察到偶数,说明 \((m-k)|2\) 。对于最后的 \((n\%k)+k\) 的部分可以通过三次询问得到异或和。假设这段的长度为 \(m\) 。那么将其分成长度为 \(\frac{m-k}{2}\)\(\frac{m-k}{2}\)\(2k-m\)\(\frac{m-k}{2}\) , \(\frac{m-k}{2}\) 五部分。由于询问之后会反转,依次每三段做一次询问,长度都为 \(k\) ,且每段最后都被询问奇数次。询问前半段最多花费 \(\frac{n}{k}\) 次询问,后半段需要 \(3\) 次询问,和不会超过 \(52\) 次。

int ask(int x){
    cout<<"? "<<x<<endl;
    return read();
}
void solve(){
    int n=read(),k=read(),ans=0;
    for(int i=1;i+k-1<=n;i+=k){
        ans^=ask(i);
    }
    if(n%k){
        ans^=ask(n-k+1-((n%k)>>1));
	    ans^=ask(n-k+1);
    }
    cout<<"! "<<ans<<endl;
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(F. Most Different Tree\)

如果我们已经有一颗小子树不与原图 \(G\) 中任意一子树相同,那么这个子树向上扩展也不与 \(G\) 中任意子树相同。那么这时候我们的目标就变成了找一个最小的未在 \(G\) 中出现的树。
用树哈希的方式记录原图的出现的树,然后暴力枚举子树大小,寻找没有出现过的树。
那么进一步研究,可以得出枚举到的可行子树大小必然是一个很小的数字。因为 \(i\) 个点构成的不同构二叉树个数是卡特兰数

vector<int>G[N],siz,a,b,c;
vector<vector<int> >nxt;
int num,id[N],n,t,cnt,ans,res;
map<vector<int>,int>mp;
int get(vector<int> &a){
	if(mp.find(a)!=mp.end())    //若存在
        return mp[a];      //返回已有值
	int s=1;   
    for(auto x:a)
        s+=siz[x];  //记录子树大小
	siz.push_back(s);   
    nxt.push_back(a);   //记录后继节点
	int id=mp.size();   //赋值hash
    mp[a]=id;
	return id;
}
void cal(int sur,int lst){
    if(!sur){   //若sur为0
		int k=get(b);   //对空vec取hash
		c.push_back(k); //将hash值记录
		if(k>cnt)ans=res,num=k;	    //如果这个hash值是已记录之外的就是符合要求 记录数据
		return ;
	}
	for(int i=lst;i<a.size();i++){  //递归枚举所有已知子树
		if(siz[a[i]]<=sur){ //如果该子树大小小于目前枚举的值
			b.push_back(a[i]);  //塞入临时vec
			cal(sur-siz[a[i]],i);   //递归
			if(ans)return ; //如果得到合法就return
			b.pop_back();   //删掉刚入队的元素
		}
	}
}
void dfs(int u,int fa){
	vector<int> aa; //新建vec
    aa.clear();
	for(auto v:G[u]){
		if(v==fa)continue;
		dfs(v,u);   //向下递归
		aa.push_back(id[v]);    //将子节点的编号存入vec
	}
	sort(aa.begin(),aa.end());  //对vec排序
	id[u]=get(aa);  //记录hash
    cnt=max(cnt,id[u]); //一共有多少个hash值
	return ;
}
void pr(int x,int y){
    for(auto v:nxt[y]){
        int u=++ans;
        cout<<u<<" "<<x<<"\n";
        pr(u,v);
    }
}
void solve(){
    n=read();
    for(int i=1;i<n;i++){
        int u=read(),v=read();
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1,0);   //对给定的子树作hash
    for(res=1;res<=n;res++){
        cal(res-1,0);   //暴力处理res个节点的时候 所有可能情况
        if(ans){    //如果可以
            ans=1;
            for(int j=n;j>res;j--,ans++){   //把子树之外的树枝随便构造
                cout<<ans<<" "<<ans+1<<'\n';
            }
            pr(ans,num);    //递归输出子树
            return ;
        }
        a.insert(a.end(),c.begin(),c.end());
        c.clear();
        b.clear(); 
		sort(a.begin(),a.end());
    }
    for(int i=1;i<=n;i++){  //若找不到 输出子树
		for(auto j:G[i]){
			if(i>j)cout<<i<<" "<<j<<"\n";
		}
	}
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}
posted @ 2023-09-12 00:43  EdGrass  阅读(183)  评论(0)    收藏  举报