Codeforces Round 1065 (Div. 3)
Dashboard - Codeforces Round 1065 (Div. 3) - Codeforces
C1&C2
令\(S_a=\oplus_{i=1}^{n}{a_i},S_b=\oplus_{i=1}^{n}{b_i}\),那么我们知道\(S=S_a\oplus S_b\)是一个定值,这是一个关键性质,我们可以根据这个讨论\(S\)二进制的每一位:
- 如果\(S_i=0\),那么也就是不论如何交换\(a_j\)和\(b_j\),最后\(S_{a,i}=S_{b,i}\);
- 如果\(S_i=1\),那么也就是\(S_{a,i}\ne S_{b,i}\),那么最后这一位肯定不同,而且可以知道的是,第\(i\)位仅由最大的\(j\in[1,n]\and ((a_j\oplus b_j)>>i)\&1\)决定,这个是很显然的;
所以最后判断得分,我们需要找到\(S\)最高位的\(1\),然后找到\(j\),如果\(j\equiv 1\mod 2\),那么赢家是Ajisai,否则是Mai。
#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;
void solve() {
int n;
cin >> n;
vector<int> a(n+1),b(n+1);
int S=0;
for(int i=1;i<=n;i++){
cin >> a[i];
S^=a[i];
}
for(int i=1;i<=n;i++){
cin >> b[i];
S^=b[i];
}
if(S==0){
cout << "Tie" << endl;
return;
}
int pos=-1;
for(int i=31;i>=0;i--){
if(S>>i&1){
pos=i;
break;
}
}
int j=0;
for(int i=1;i<=n;i++){
if((a[i]^b[i])>>pos&1){
j=i;
}
}
if(j&1) cout << "Ajisai" << endl;
else cout << "Mai" << endl;
}
/*
*/
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
solve();
}
return 0;
}
D&F
题意是给定排列\(p\),如果\(1\le u <v \le n\),那么当且仅当\(pos_u<pos_v\),也就是如果\(u\)向\(v\)连边,那么需要\(u\)在\(p\)中的位置\(pos_u\)小于\(v\)在\(p\)中的位置\(pos_v\),问对于给定的排列\(p\),是否可以可以构成一棵树,也就是判断所有点是否可以连通。\(F\)是在\(D\)的基础上,还需要输出构造的树。
主播赛时写这道题的时候用的并查集,就是很明显的我们可以从后往前分成一些单调增的连通块,块内都是连通的,而且我们可以在块内选出代表元素,之后我们考虑如何合并没有连通的块。这样是错的,而且非常难写,主播刚刚对拍出了一个错误数据:
1
13
11 1 9 7 5 8 4 12 13 3 6 10 2
\(sol\):
我们应该考虑维护一个连通集合\(S\),同时记录\(S\)的最小值\(mn\),同时用一棵权值线段树\(tr\)(可以支持单点修改和区间查询\(max\)),还需要一个\(pos_v\)和\(fa_v\)数组,\(pos_v\)表示值为\(v\)的位置,\(fa_v\)表示值为\(v\)的父亲,最后\(vis_i\)表示第\(i\)个位置是否已经加入集合\(S\)。
之后枚举新的元素\(p_i\),按照下面步骤扩充\(S\):
- 当集合\(S\)为空时,将第一个元素加入\(S\),更新最小值\(mn\),将\(v\in[mn+1,n]\)的所有元素加入集合\(S\),同时单点更新\(tr\),即\(tr.update(v,v,pos_v)\),标记\(vis_i=1\);
- 如果\(vis_i=1\),那么跳过;
- 否则我们考虑加入\(p_i\),可以知道的是\(p_i<mn\),这是肯定的,因为在上一步,我们已经把\(p_i>mn\)的元素都处理了。之后我们查询\(r=tr.query(mn+1,n)\),如果\(r<i\),那么肯定\(p_i\)无法加入集合\(S\),那么输出
NO;如果\(r>i\),将\([p_i+1,mn-1]\)的元素都连向\(p_i\),同时更新\(tr.update(v,v,pos_v)\),标记\(vis_i=1\),最后记得将\(p_i\)连向\(p_r\),之后更新\(mn\)。
如果一直没有输出NO,那么输出YES,同时打印这棵树。
#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
#define lc u<<1
#define rc u<<1|1
typedef long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;
struct Segment {
struct node {
int l, r, sum;
};
vector<int> vc;
vector<node> seg;
Segment() {}
Segment(int n) {
vc.resize(n + 10);
seg.resize((n << 2) + 10);
}
void pushup(int u) {
seg[u].sum = max(seg[lc].sum ,seg[rc].sum);
}
void build(int u, int l, int r) {
seg[u] = {l, r, 0};
if (l == r) return;
int mid = (l + r) / 2;
build(lc, l, mid), build(rc, mid + 1, r);
pushup(u);
}
//区间修改
void update(int u, int l, int r, int z) {
if (seg[u].l >= l && seg[u].r <= r) {
seg[u].sum = max(seg[u].sum,z);
return;
}
int mid = (seg[u].l + seg[u].r) / 2;
if (l <= mid) update(lc, l, r, z);
if (r > mid) update(rc, l, r, z);
pushup(u);
}
//区间查询
int query(int u, int l, int r) {
if (seg[u].l >= l && seg[u].r <= r) {
return seg[u].sum;
}
int res = 0LL;
int mid = (seg[u].r + seg[u].l) / 2;
if (l <= mid) res =max(res, query(lc, l, r));
if (r > mid) res = max(query(rc, l, r),res);
return res;
}
};
void solve() {
int n;
cin >> n;
vector<int> p(n+2);
vector<int> pos(n+1);
vector<int> fa(n+1);
for(int i=1;i<=n;i++){
cin >> p[i];
pos[p[i]]=i;
}
//维护集合S,需要S中的最小元素mn,以及S中的最大元素位置
Segment tr(n);
tr.build(1,1,n);
int mn=p[1];
vector<int> vis(n+1);
for(int v=mn+1;v<=n;v++){
tr.update(1,v,v,pos[v]);
fa[v]=mn;
vis[pos[v]]=1;
}
for(int i=2;i<=n;i++){
if(vis[i]==1) continue;
//p[i]<mn,mx>p[i],要求tr.query(mn+1,n)>i
int r=tr.query(1,mn+1,n);
if(r<i){
cout << "NO" << endl;
return;
}
fa[p[i]]=p[r];
for(int v=p[i]+1;v<mn;v++){
tr.update(1,v,v,pos[v]);
fa[v]=p[i];
vis[pos[v]]=1;
}
mn=min(mn,p[i]);
}
cout << "YES" << endl;
for(int i=2;i<=n;i++){
cout << p[i] << " " << fa[p[i]] << endl;
}
}
/*
*/
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
solve();
}
return 0;
}
E
构造题。
要构造长度为\(n\)的排列,且坏索引个数小于等于6。
主播自己的想法是枚举质数,然后双指针来写。有个很显然的贪心,我们从大到小枚举质数\(p\),把\(p\mid i\)的\(i\)加入\(p\)的集合,因为对于一个数\(i\),可能有两个质数\(p_1,p_2\mid i\and p_1<p_2\),因为\(p_1<p_2\),所以\(n/p_1\ge n/p_2\),所以先把\(i\)分给\(p_2\)是更优的,然后用\(i\)枚举质数\(p_i\),同时一个指针\(j\)从\(p_0\)开始,分下面情况讨论:
- 如果\(fac[p_i].size()>1\),那么直接输出\(fac[p_i]\);
- 如果\(fac[p_i].size()=1\),那么先输出\(fac[p_i][0]\),之后再来两个\(fac[p_j]\)的元素;
这样当\(n\leq 3000\)时都还是比较正确的,当\(n=3442\)时,按照主播这种构造方法会有8个坏索引。但是我感觉主播这个还可以优化一下,可是没有坏索引的上界证明,所以还是不采取这种做法。
来看看\(std\):
考虑偶数,有\(n/2\)个,那么每两个配合一个其他数,大概可以用掉\(3n/4\)个,之后我们再用3的奇数倍个数来构造,大概有\(n/6\)个,每两个配合一个,所以有\(n/6+n/12=n/4\)个,配合上前面大概有\(n\)个,有证明保证这个构造方法的坏索引不会超过4个。
void solve() {
int n;
cin >> n;
vector<int> p1,p2;
vector<int> vis(n+1);
for(int i=1;i*2<=n;i++){
p1.push_back(i*2);
vis[i*2]=1;
}
for(int i=1;i*3<=n;i+=2){
p2.push_back(i*3);
vis[i*3]=1;
}
vector<int> re;
for(int i=1;i<=n;i++){
if(!vis[i]) re.push_back(i);
}
vector<int> ans;
while(re.size()&&p1.size()>=2){
ans.push_back(re.back());
re.pop_back();
ans.push_back(p1.back());
p1.pop_back();
ans.push_back(p1.back());
p1.pop_back();
}
while(re.size()&&p2.size()>=2){
ans.push_back(re.back());
re.pop_back();
ans.push_back(p2.back());
p2.pop_back();
ans.push_back(p2.back());
p2.pop_back();
}
while(p1.size()){
ans.push_back(p1.back());
p1.pop_back();
}
while(p2.size()){
ans.push_back(p2.back());
p2.pop_back();
}
while(re.size()){
ans.push_back(re.back());
re.pop_back();
}
for(int i=0;i<n;i++){
cout << ans[i] << " \n"[i==n-1];
}
}
主播的写法(\(WRONG\)):
#include <bits/stdc++.h>
using namespace std;
#define inf 1e18
#define endl '\n'
#define int long long
typedef long long ll;
typedef pair<int, int> pii;
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const int N = 2e5 + 9, M = 2e5 + 9, mod = 1e9 + 7;
vector<int> p;
int vis[N];
void init(){
for(int i=2;i<N;i++){
if(!vis[i]){
p.push_back(i);
}
for(int v:p){
if(v*i>=N) break;
vis[v*i]=1;
if(i%v==0) break;
}
}
}
void solve() {
int n;
cin >> n;
//优先搭配大的质数
int cnt=lower_bound(p.begin(),p.end(),n+1)-p.begin();
int mxp=p[cnt-1];
vector<set<int>> fac(mxp+1);
vector<int> vis(n+1);
for(int i=cnt-1;i>=0;i--){
for(int j=1;j*p[i]<=n;j++){
if(vis[j*p[i]]) continue;
fac[p[i]].insert(j*p[i]);
vis[j*p[i]]=1;
}
}
vector<int> ans(n+1);
int j=0;
for(int i=cnt-1;i>=j;i--){
if(fac[p[i]].empty()) continue;
if(fac[p[i]].size()==1){
cout << *fac[p[i]].begin() << " ";
fac[p[i]].erase(fac[p[i]].begin());
int tot=2;
while(fac[p[j]].size()&&tot>0){
// int v=*fac[p[j]].begin();
cout << *fac[p[j]].begin() << " ";
fac[p[j]].erase(fac[p[j]].begin());
tot--;
}
if(tot>0){
j++;
while(fac[p[j]].size()&&tot>0){
// int v=*fac[p[j]].begin();
cout << *fac[p[j]].begin() << " ";
fac[p[j]].erase(fac[p[j]].begin());
tot--;
}
}
}else{
while(fac[p[i]].size()){
// int v=*fac[p[i]].begin();
cout << *fac[p[i]].begin() << " ";
fac[p[i]].erase(fac[p[i]].begin());
}
}
}
cout << 1 << endl;
}
/*
*/
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
init();
int t = 1;
cin >> t;
while (t--) {
solve();
}
return 0;
}

浙公网安备 33010602011771号