ZR 集训 Day18 – 人类智慧题合集

ooliver 发布于 14 小时前 137 次阅读 OI


AI 摘要

人类智慧题,没有套路,只有灵光一现。差分交换、随机染色、压缩通信……看似无从下手,实则妙不可言。这份题单,带你领略构造、Ad-hoc与随机的魔力,准备好烧脑了吗?

什么是人类智慧题?

为了方便通过关键词搜索到这篇文章,在此解释一下我对人类智慧题的定义:构造(交互、通信)、Ad-hoc、随机。

题单

CF1110E Magic Stones

牛炸了的 Ad-hoc。

首先特判 $a_1,t_1$ 与 $a_n,t_n$ 是否相等。

先看一组变化:

$$
[a_1,a_2,a_3] \rightarrow [a_1,a_1+a_3-a_2,a_3]
$$

尝试差分,得到:

$$
[a_2-a_1,a_3-a_2] \rightarrow [a_3-a_2,a_2-a_1]
$$

会发现以上操作对于差分数组而言就是交换相邻项,所以直接判断差分数组构成的集合是否相等即可。

代码:

C++
#include<bits/stdc++.h>
using namespace std;
 
const int N=2e5+5;
int n;
int a[N],t[N];
multiset<int> s1,s2;
 
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        if(i>1) s1.insert(a[i]-a[i-1]);
    }
    for(int i=1;i<=n;i++){
        cin>>t[i];
        if(i>1) s2.insert(t[i]-t[i-1]);
    }
    if(a[1]==t[1]&&a[n]==t[n]&&s1==s2) cout<<"Yes";
    else cout<<"No";
    return 0;
}

CF1270G Subset with Zero Sum

看到这个特殊的数据范围:$i-n \le a_i \le i-1$,考虑变化成:$1 \le i-a_i \le n$。

如果连边:$i \rightarrow i-a_i$,之后找到一个环 $S$,则有:

$$
\begin{aligned}
\sum_{i\in S} i&=\sum_{i\in S} next_i \\
\sum_{i\in S} i&=\sum_{i\in S} i-a_i \\
\sum_{i\in S} a_i &= 0
\end{aligned}
$$

此时输出找到的环的下标即可。

代码:

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

const int N=1e6+5;
int T,n,now,idx;
int a[N],nxt[N],tag[N],ans[N];

void solve(){
    now=1,idx=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",a+i);
        nxt[i]=i-a[i],tag[i]=0;
    }
    while(!tag[now]) tag[now]=1,now=nxt[now];
    ans[++idx]=now,now=nxt[now];
    while(now!=ans[1]) ans[++idx]=now,now=nxt[now];
    cout<<idx<<"\n";
    for(int i=1;i<=idx;i++) cout<<ans[i]<<" ";
    cout<<"\n";
}

int main(){
    scanf("%d",&T);
    while(T--) solve();
    return 0;
}

CF1672E notepad.exe

观察限制,显然询问次数是 $(n+\log V)$ 次的。

暴力的想法肯定是枚举一个高度,对于当前高度二分找到最小的宽度。

不难发现,当高度递增,为了使答案最优,我们肯定希望宽度递减。

这是我们先二分找出当高度为 $1$ 时最小的宽度 $w_1$,

之后枚举高度 $h$,最优情况下可以省去 $(h-1)$ 个空格,也就是最优的宽度应为 $\frac{w_1-h+1}{h}$,向上取整一下就是 $\lfloor \frac{w_1}{h} \rfloor$。又因为想要更新答案,宽度至少为 $\lfloor \frac{w_1}{h} \rfloor$,发现此时宽度的取值是一定的,那就直接猜这个值,如果符合条件就更新答案。

代码:

C++
#include<bits/stdc++.h>
using namespace std;
 
int n,x,w1,ans;
 
int main(){
    cin>>n;
    int l=1,r=4002000;
    while(l<r){
        int mid=(l+r)>>1;
        cout<<"? "<<mid<<endl;
        cin>>x;
        if(x==1) r=mid;
        else l=mid+1;
    }
    ans=w1=l;
    for(int h=2;h<=n;h++){
        cout<<"? "<<w1/h<<endl;
        cin>>x;
        if(x!=0) ans=min(ans,w1/h*x);
    }
    cout<<"! "<<ans<<endl;
    return 0;
}

HDOJ6664 Andy and Maze

随机算法。

直接做的话,要求 $k$ 个点不能重复,这个状态很难记录,所以我们考虑随机染色,找颜色不同的路径。

这样答案路径被统计的概率就应该是 $\frac{k!}{k^k}$,重复差不多 $300$ 次正确率就非常高了。

代码:

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

const int N=1e4+5;
int T,n,m,k;
int col[N],dp[N][64],u[N],v[N],w[N];

void solve(){
    int ans=-1,num=300;
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++) cin>>u[i]>>v[i]>>w[i];
    while(num--){
        memset(dp,-0x3f,sizeof dp);
        for(int i=1;i<=n;i++) col[i]=rand()%k,dp[i][1<<col[i]]=0;
        for(int s=1;s<(1<<k);s++) for(int i=1;i<=m;i++){
            if(s&(1<<col[u[i]])) 
                dp[u[i]][s]=max(dp[u[i]][s],dp[v[i]][s^(1<<col[u[i]])]+w[i]);
            if(s&(1<<col[v[i]])) 
                dp[v[i]][s]=max(dp[v[i]][s],dp[u[i]][s^(1<<col[v[i]])]+w[i]);
        }
        for(int i=1;i<=n;i++) ans=max(ans,dp[i][(1<<k)-1]);
    }
    if(ans!=-1) cout<<ans<<"\n";
    else cout<<"impossible\n";
}

int main(){
    cin>>T;
    while(T--) solve();
    return 0;
}

CF1408F Two Different

不难发现,对于 $n=2^k$ 是很好构造的,我们甚至可以做到让所有数字都变得一样。因为相邻两个是可以变成相同数字的,我们可以用类似归并的思想,递归直到只剩两个元素,否则对左右两边一一配对,就完成了合并。

其他情况呢?这里题目要求至多两种不同的数字,根据上面的分析,我们要把 $n$ 拆成两个 $2^k$ 相加的形式,但如果拆不了呢?可以想到,如果这两个 $2^k$ 有重合的部分也是没关系的,因为后面的会把前面的覆盖掉,所以就做完了。

代码:

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

int n,p,q,x;
vector<pair<int,int>> ans;

void solve(int id,int k){
    if(k==0) return;
    if(k==1){
        ans.push_back({id+1,id+2});
        return;
    }
    solve(id,k-1),solve(id+(1<<(k-1)),k-1);
    for(int i=1;i<=(1<<(k-1));i++) ans.push_back({id+i,id+i+(1<<(k-1))});
}

signed main(){
    cin>>n;
    for(int i=0;i<=20;i++) if(n&(1<<i)) q=p,p=i;
    if(q!=0) q++,x=n-(1<<p);
    solve(0,q);
    solve(x,p);
    cout<<ans.size()<<"\n";
    for(auto[x,y]:ans) cout<<x<<" "<<y<<"\n";
    return 0;
}

QOJ141 8 染色

蒟蒻第一次做通信题,核心就是两个优化:

  1. 对于度数 $<8$ 的点是不需要传递信息的,可以直接根据其临边判断出其颜色。
  2. 我们完全可以把两个颜色压缩成一个颜色,等 Bob 拿到后对于相同的颜色跑一遍黑白染色即可。

代码:

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

vector<int> Alice(int n,int m,vector<int> u,vector<int> v,vector<int> c){
    vector<int> x,deg(n,0);
    for(int i=0;i<m;i++) deg[u[i]]++,deg[v[i]]++;
    for(int i=0;i<n;i++) if(deg[i]>=8) x.push_back(c[i]>>2),x.push_back((c[i]>>1)&1);
    return x;
}

vector<int> Bob(int n,int m,vector<int> u,vector<int> v,vector<int> x){
    int idx=0;
    vector<int> c(n,-1),tag(n,-1),deg(n,0),g[200005];
    for(int i=0;i<m;i++) g[u[i]].push_back(v[i]),g[v[i]].push_back(u[i]),deg[u[i]]++,deg[v[i]]++;
    for(int i=0;i<n;i++) if(deg[i]>=8) c[i]=(x[idx]<<1)+x[idx+1],idx+=2;
    function<void(int)> dfs=[&](int u){
        for(int v:g[u]) if(c[u]==c[v]&&tag[v]==-1){
            tag[v]=tag[u]^1;
            dfs(v);
        }
    };
    for(int i=0;i<n;i++) if(tag[i]==-1&&c[i]!=-1) tag[i]=0,dfs(i);
    for(int i=0;i<n;i++) if(tag[i]!=-1) c[i]=(c[i]<<1)+tag[i];
    for(int i=0;i<n;i++){
        if(c[i]!=-1) continue;
        vector<bool> t(8);
        for(int v:g[i]) if(c[v]!=-1) t[c[v]]=1;
        for(int j=0;j<8;j++) if(!t[j]) c[i]=j; 
    }
    return c;
}