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; } /*样例区 */ //------------------------------------------------------------
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)