Codeforces Round #812 (Div. 2) A-E

A. Traveling Salesman Problem

题意:空间中 在坐标轴上给几个点,问最后,从原点出发经过所有点,再返回原点所经过的最远距离

分析:

找到 最大的 x ,最小的 x ,最大的 y ,最小的 y ,然后根据 曼哈顿距离,计算一个大正方形的边长就可以了

为什么不能先把所有点初始化成无穷大?

因为如果只有一个点, (1,0) 这时候 最大的 x 和最小的 x 都是 1,但实际上最小的 x 应该是 0 

复制代码
void solve()
{
//    cin>>n>>m;
    cin>>n;
    int mxx = 0,mxy = 0,mnx = 0,mny = 0;
    fo(i,1,n) {
        int x,y;cin>>x>>y;
        mxx = max(mxx,x);
        mxy = max(mxy,y);
        mnx = min(mnx,x);
        mny = min(mny,y);
    }
    cout<<(mxx - mnx) * 2 + (mxy - mny) * 2<<endl;
}
复制代码

 

B. Optimal Reduction

题意:一段长度为 n 的序列 a ,每次能使一段区间内的数 -1 ,问要最终使整个区间的所有数都变成 0 ,该序列 在该序列的排列中是不是操作次数最少的,如果是,输出Yes,否则No

分析:

如果整个区间是 一个 凸的形状,当然可以,但如果有一个 凹的形状,也就是有个极小值且不在边界上,就需要先把这个极小值左边的所有数变成0,再把这个极小值右边的数变成0

只要看看有没有一个先减小,再增加的位置就可以了

复制代码
//#define int ll
const int N = 1e5+10;
int n,m;
int a[N];
void solve()
{
    cin>>n;
    ms(a,0);
    fo(i,1,n) cin>>a[i];
    bool f = 0,ff = 0;
    
    fo(i,2,n-1) {
        if(a[i] < a[i-1]) f = 1;if(f && a[i] < a[i+1]) ff = 1;
    }
    if(ff) {
        NO;
    }  else {
        YES;
    }
}
复制代码

C. Build Permutation

题意:给一个 n ,构造一个长度 为 n 的序列 ,使这个序列的所有数ai 和 它的位置 i 相加后 是一个平方数(开根号后不是小数)

分析:

从n-1 枚举到0,每个位置,取能取的最大值,比如 第 n - 1 个位置,首先取一个比 n -  1 更大的平方数 x ,如果 x == n - 1 + k , k < n 且未被取过的数

首先 n < 100000,这个范围内的平方数总共 317 个,且最后一个大于100000的数,i * i = 100489

 

 

 且每个数之后的第一个平方数,最多比这个平方数大 一倍

所以直接对 (n - 1) * 2 取根号 再平方就是比这个数大的第一个平方数

比如 9999,直接从最大的平方数往下枚举到当前数 i 的根号数,每个数最多枚举 300次,总共 100000个数,时间复杂度远小于 3e8,没到一秒可以过

复制代码
//#define int ll
const int N = 1e5+10;
int n,m,ans[N];
bool st[N];void solve()
{
    cin>>n;ms(st,0);
    int lim = sqrt(2 * n - 2);
    of(i,n-1,0) {
        int s = sqrt(i);
        if(s * s != i) s ++ ;
        for(int j = lim;j >= s;j -- ) {
            if(j * j - i > n - 1 || st[j * j - i]) continue;
            st[j * j - i ] = 1;
            ans[i] = j * j - i;
            break;
        }
    }
    fo(i,0,n-1)cout<<ans[i]<<" \n"[i==n-1];
}
复制代码

 D. Tournament Countdown

题意:交互题,两两打擂台赛,每次可以询问任意两个人之间谁赢得更多,如果第一个多,输入 1; 第二个多 ,输入2;两个一样多,输入0

分析:

每次选四个人观察,第一第二个人打,第三第四个人打。如果1,2。3,4这样询问,需要问三次才能知道哪个人是最强的。如果1,3先问,如果是1,再问1,4。如果是0,那再问2,3。如果是2,再问2,3。这几种方案都只用问两次就问出了三个人的情况。

将赢的人放入向量,下一轮再继续问就可以了。

n个人,每次可以删掉 3/4的人,n * (3/4)^k <= 2 总共的询问次数就是 k 。且每次需要操作两次。

 

 

 

复制代码
//#define int ll
const int N = 2e6+10;
int n,m,res,k,l,r;
int a[N],vis[N];
 
V<int> p,tmp;
 
int ask(int x,int y) {
    cout<<"? "<<x<<' '<<y<<endl;
    int n;cin>>n;
    return n;
}
 
void solve()
{
//    cin>>n>>m;
    cin>>n;
    p.clear();
    tmp.clear();
    fo(i,1,(1<<n)) {
        p.pb(i);
    }
    while(p.size() > 2) {
        for(int i = 0;i<p.size();i+=4) {
            int k = ask(p[i],p[i+2]);
            if(k == 1) {
                if(ask(p[i],p[i+3]) == 1) {
                    tmp.pb(p[i]);
                } else {
                    tmp.pb(p[i+3]);
                }
            } else if(k == 2) {
                if(ask(p[i+1],p[i+2]) == 1) {
                    tmp.pb(p[i+1]);
                } else {
                    tmp.pb(p[i+2]);
                }
            } else {
                if(ask(p[i+1],p[i+3]) == 1) {
                    tmp.pb(p[i+1]);
                } else {
                    tmp.pb(p[i+3]);
                }
            }
        }
        p = tmp;
        tmp.clear();
    }
    if(p.size() == 2) {
        if(ask(p[0],p[1]) == 1) {
            cout<<"! "<<p[0]<<endl;
        } else {
            cout<<"! "<<p[1]<<endl;
        }
    } else {
        cout<<"! "<<p[0]<<endl;
    }
}
 
复制代码

E. Cross Swapping

题意:每次可以选择一个 k ,a[i][k] 和 a[k][i] 互换,问能得到的最小字典序是多少

分析:

首先明确一点

k 相同的两个操作相互抵消,且当 k == 1 的时候,

所以能够真正执行的操作只有 n 次

要实现字典序最小,

假设 j > i

a[i][j] > a[j][i], 且这个位置还没被交换过,一定要交换 

a[i][j] < a[j][i] ,且这个位置还没被交换过,一定不交换

a[i][j] == a[j][i] ,交换不交换都可以

对于每个位置有两种交换方式。对于 a[i][j] (j > i) 可以在k = i 的时候和 a[j][k] 交换 。在k = j 的时候和 a[k][i] 交换。

另外:

同时因为 j > i ,遍历顺序是 for(int i = 1;i<=n;i++) for(int j = i+1;j<=n;j++) 。

所以当遍历当前层的时候,k = i如果在前面已经被反转了,不会被反转第二遍,而会去反转 k = j

但是遍历下一层,有可能某个 k = j 刚好和上一层的 k = i 重合了。

a[i][j] > a[j][i] ,如果这个位置已经被交换过,一定要交换 

a[i][j] < a[j][i] ,且这个位置还没被交换过,一定不交换

 

用扩展域并查集

将所有 i 和 j 是第一种情况的,通过 i + n 合并到 j ,i 合并到 j + n 

如果是第二种情况, 将 i 和 j 放在一个集合, i + n 和 j + n 放在一个集合

最后输出所有 根节点小于等于 n 或者大于 n 的

为什么一定要把 i 合并到 j + n ,把 i + n 合并到 j 呢?

有两个作用:

1.在后面判断两个数是不是不在一个集合的时候有用

2.后面输出的时候可以直接输出 根节点小于等于 n 的和 大于 n 的所有点 ,因为 如果是敌人关系只会交换一次,同伴关系会交换两次换回来

 

 

 事实证明:手写合并操作容易出错还是要写个merge函数。。。

复制代码
//-------------------------代码----------------------------

#define int ll
const int N = 4010;
int n,m;

int a[N][N];
int p[N];
int find(int x) {
    return x == p[x]?x:p[x] = find(p[x]);
}

void merge(int x,int y) {
    int fx = find(x),fy = find(y);
    p[fx] = fy;
}

void solve()
{
//    cin>>n>>m;
    cin>>n;
    fo(i,1,n) {
        fo(j,1,n) {
            cin>>a[i][j];
        }
    }
    fo(i,1,2*n)p[i] = i;
    fo(i,1,n) {
        fo(j,i+1,n) {
            if(a[i][j] < a[j][i]) {
                if(find(i) != find(j+n)) {
                    merge(i,j);
                    merge(i+n,j+n);
                }
            } else if(a[i][j] > a[j][i]) {
                if(find(i) != find(j)) {
                    merge(i,j+n);
                    merge(i+n,j);
                }
            }  
        }
    }
    fo(k,1,n) {
        if(find(k) <= n) continue;
        fo(i,1,n) {
            swap(a[i][k],a[k][i]);
        }
    }
    fo(i,1,n) {
        fo(j,1,n) {
            cout<<a[i][j]<<' ';
        } cout<<endl;
    }
    
}
void main_init() {}
signed main(){
    AC();clapping();TLE;
    cout<<fixed<<setprecision(12);
    main_init();
//  while(cin>>n,n)
//  while(cin>>n>>m,n,m)
    int t;cin>>t;while(t -- )
    solve();
//    {solve(); }
    return 0;
}

/*样例区


*/

//------------------------------------------------------------
复制代码

 

posted @   er007  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示