Codeforces Round #720 (Div. 2)
Codeforces Round #720 (Div. 2)
A
给定正整数 \(A,B\)。
若 \(A\times B|C\),称 \(C\) 为“好数”。
若 \(A\times B\not |C\) 但 \(A|C\),称 \(C\) 为“接近好数”。
要求找出互不相同的正整数 \(x,y,z\),使他们中恰好有一个数是“好数”且另外两个数是“接近好数”,且满足 \(x+y=z\)。
不难发现,若 \(B=1\) 则一定无解。
否则一般情况下可以构造:\(x=A,y=A\times(B-1),z=A\times B\) 来符合要求。
但是互不相同限制了当 \(B=2\) 时我们不能这么干。
但是在 \(B=2\) 时直接考虑一下,不难发现 \(x=A,y=A\times(B+1),z=A\times(B+2)\) 符合要求。
于是简单讨论一下之后,这道题结束。
#include<cstdio>
using namespace std;
int main(){
int T; scanf("%d", &T);
while(T --){
int a, b;
scanf("%d %d", &a, &b);
if(b == 1) puts("NO");
else{
puts("YES");
if(b == 2) b = 4;
long long x = a, y = 1LL * a * (b - 1), z = 1LL * a * b;
printf("%lld %lld %lld\n", x, y, z);
}
}
return 0;
}
B
给定一个数列 \(A\),每次操作形如 \((i,j,x,y)\),要求是 \(min(a(i),a(j))=min(x,y)\)。
操作结束后,\(a(i)=x,a(j)=y\)。
要求任意构造一组操作,使得操作后 \(A\) 中任意相邻的两个数均互质。
题目不要求操作数最小化。
数据范围:\(a(i)\leq 10^9,x,y\leq 2\times 10^9\)
有了最后一句话就有了无限大的发展空间。
显然一个大于所有 \(a(i)\) 的大质数是与任何序列中的原数互质的,例如 \(p=10^9+7\)。
那么只需要将序列 \(A=a_1,a_2,\dots,a_n\),变为 \(A'=min(a_1,a_2),p,min(a_3,a_4),p,\dots\) 即可。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 100010;
const int INF = 1e9 + 7;
int T, n, a[N];
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int main(){
T = read();
while(T --){
n = read();
for(int i = 1; i <= n; i ++) a[i] = read();
printf("%d\n", n / 2);
for(int i = 1; i + 1 <= n; i += 2){
printf("%d %d %d %d\n", i, i + 1, min(a[i], a[i + 1]), INF);
}
}
return 0;
}
C
交互题。让你猜一个 \(n\) 排列。
每次询问形如 \((t,i,j,x)\),返回值为:
- \(t = 1\):\(\max{(\min{(x, p_i)}, \min{(x + 1, p_j)})}\)
- \(t = 2\):\(\min{(\max{(x, p_i)}, \max{(x + 1, p_j)})}\)
其中 \(1\leq x\leq n-1\)询问次数不得超过 \(\lfloor \frac {3 \cdot n} {2} \rfloor + 30\) 次。
很好的交互题。
猜排列的策略一般为,先确定一个特殊的数(如 \(1/n\)),然后根据特殊数再推出每个位置的数。
假设 \(1\) 的位置为 \(p\),不难发现,利用询问 \((1,p,i,n-1)\) 可以直接确定位置 \(i\) 上的数。
那么任务变为用 \(\lfloor \frac {n} {2} \rfloor + 30\) 次询问得到 \(1\) 的位置。
再次观察操作,发现只有当 \((2,i,i+1,1)\leq 2\) 时 \(i,i+1\) 中才可能有 \(1\) 的出现。
此时再利用 \((1,i,i+1,1)\) 和 \((1,i+1,i,1)\) 即可确定 \(1\) 的位置。
这样的询问数显然不超过限制条件。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 20010;
int T, n, ans[N];
int main(){
scanf("%d", &T);
while(T --){
scanf("%d", &n);
int pos = 0, now;
for(int i = 1; i + 1 <= n; i += 2){
printf("? 2 %d %d 1\n", i, i + 1);
fflush(stdout); scanf("%d", &now);
if(now <= 2){
int x, y;
printf("? 1 %d %d 1\n", i, i + 1);
fflush(stdout); scanf("%d", &x);
printf("? 1 %d %d 1\n", i + 1, i);
fflush(stdout); scanf("%d", &y);
if(x == 1) {pos = i + 1; break;}
if(y == 1) {pos = i; break;}
}
}
if(!pos) pos = n;
ans[pos] = 1;
for(int i = 1; i <= n; i ++) if(i != pos){
printf("? 1 %d %d %d\n", pos, i, n - 1);
fflush(stdout);
scanf("%d", &ans[i]);
}
printf("!");
for(int i = 1; i <= n; i ++) printf(" %d", ans[i]);
puts("");
fflush(stdout);
}
return 0;
}
D
给定一棵 \(n\) 个节点的树,规定一次操作为 短一条边 + 连一条边。
构造操作数最小的方案,使得树变为一条链。
个人认为比 C 简单。
一开始的想法是直接选一个度数最大的节点为根,然后简单贪心。
每次将多余的子节点断开后连接到上一个子节点为首的链的下端,根能包含两个子节点,其余只能包含一个。
正解其实差不多,假设处理到的节点的子节点数为 \(x\):
- \(x\leq 2\),啥也不干。
- \(x=3\),断开这个节点和它的父亲节点(如果有的话)。
- \(x>3\),断开这个节点和它的父亲节点(如果有的话),然后任意断开 \(x-2\) 条边。
每次断开是真的断开,需要删边,而不是像上述 native 做法一样的简单处理。
这样贪心的好处是可以缩小父亲节点的子节点数,这样就减少了很多 native 想法中的操作。
删边时记录答案,最后还要单独处理出每条链的 起始节点 和 结束节点。
显然链的数量 = 删边数 + 1,所以每次利用两条链(顺序随意)输出答案即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 100010;
int T, n, cnt, head[N], deg[N];
bool Delet[N << 1], vis[N];
struct Edge{int nxt, to;} ed[N << 1];
vector<pair<int, int> > ans1, ans2;
void Clear(){
cnt = 1;
ans1.clear(); ans2.clear();
memset(head, 0, sizeof(head));
memset(Delet, false, sizeof(Delet));
memset(deg, 0, sizeof(deg));
memset(vis, false, sizeof(vis));
}
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void add(int u, int v){
ed[++ cnt] = (Edge){head[u], v};
head[u] = cnt;
}
void dfs(int u, int fa, int frm){
vector<pair<int, int> > now;
for(int i = head[u], v; i; i = ed[i].nxt)
if((v = ed[i].to) != fa){
dfs(v, u, i);
if(!Delet[i]) now.push_back(make_pair(v, i));
}
if(now.size() <= 1) return;
if(fa){
Delet[frm] = Delet[frm ^ 1] = true;
deg[fa] --, deg[u] --;
ans1.push_back(make_pair(u, fa));
}
if(now.size() == 2) return;
for(int i = 2; i < now.size(); i ++){
int v = now[i].first;
int frm = now[i].second;
Delet[frm] = Delet[frm ^ 1] = true;
deg[v] --, deg[u] --;
ans1.push_back(make_pair(u, v));
}
}
int Chain(int u){
vis[u] = true;
for(int i = head[u], v; i; i = ed[i].nxt)
if(!vis[(v = ed[i].to)] && !Delet[i]) return Chain(v);
return u;
}
int main(){
T = read();
while(T --){
Clear();
n = read();
for(int i = 1; i < n; i ++){
int u = read(), v = read();
add(u, v), add(v, u);
deg[u] ++, deg[v] ++;
}
dfs(1, 0, 0);
for(int i = 1; i <= n; i ++)
if(deg[i] == 0)
ans2.push_back(make_pair(i, i));
else if(deg[i] == 1 && !vis[i]){
int to = Chain(i);
ans2.push_back(make_pair(i, to));
}
printf("%d\n", ans1.size());
for(int i = 0; i < ans1.size(); i ++)
printf("%d %d %d %d\n", ans1[i].first, ans1[i].second, ans2[i].second, ans2[i + 1].first);
}
return 0;
}
E
给定 \(k\) 个数字,他们分别是 \(1,2,3,...,k\),对于数字 \(i\) 你有相同的 \(a_i\) 个。
定义一个 \(n\times n\) 的矩阵为美丽矩阵:
- 这个 \(n\times n\) 的矩阵包含了你所拥有的所有数字。
- 对于每个 \(2\times 2\) 的子矩阵,占用的格子不能超过 \(3\)。
现在,你要构造一个最小的美丽矩阵。
奇特的构造题。
将格子分类为 \(x,y,z,0\) 四类,如图。
\(x\to red,y\to blue,z\to yellow\)。
首先将众数调整至位置 \(1\)。
二分出方阵大小 \(n\),条件为 \(x+y\geq a(1)\) 且 \(x+y+z\geq m\),这显然是下限。
现在要证明存在合法的构造方案。
方案为从头开始(众数被调整至第一个),按照 \(x\to y\to z\) 的方法随意填充。
现在需要证明不存在不合法的情况,即红黄格子中没有相同的数。
-
假设众数填满了红色格子,根据二分限制,众数最多再填满蓝色格子。
-
假设众数没有填满红色格子,那么也不会存在数字 \(m\) 填了红色格子并填满蓝色格子。
因为 \(y\geq x\),如果出现这种情况,与众数的定义相违背。
故得证明了构造的正确性。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 200010;
const int M = 1010;
int T, m, k, a[N], id[N];
int ans[M][M];
vector<pair<int, int> > x, y, z;
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int Get(){
int l = 0, r = 1000;
while(l < r){
int mid = (l + r) >> 1;
if(mid * ((mid + 1) / 2) >= a[1] && mid * mid - (mid / 2) * (mid / 2) >= m) r = mid;
else l = mid + 1;
}
return l;
}
int main(){
T = read();
while(T --){
m = read(), k = read();
int mx = 0;
for(int i = 1; i <= k; i ++){
a[i] = read(); id[i] = i;
if(a[i] > a[mx]) mx = i;
}
swap(a[1], a[mx]), swap(id[1], id[mx]);
int n = Get();
printf("%d\n", n);
x.clear(), y.clear(), z.clear();
for(int i = 2; i <= n; i += 2)
for(int j = 1; j <= n; j += 2)
x.push_back(make_pair(i, j));
for(int i = 1; i <= n; i += 2)
for(int j = 1; j <= n; j += 2)
y.push_back(make_pair(i, j));
for(int i = 1; i <= n; i += 2)
for(int j = 2; j <= n; j += 2)
z.push_back(make_pair(i, j));
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
ans[i][j] = 0;
for(int i = 1; i <= k; i ++)
for(int j = 1; j <= a[i]; j ++){
if(x.size()){
ans[x.back().first][x.back().second] = id[i];
x.pop_back();
}
else if(y.size()){
ans[y.back().first][y.back().second] = id[i];
y.pop_back();
}
else{
ans[z.back().first][z.back().second] = id[i];
z.pop_back();
}
}
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++) printf("%d ", ans[i][j]);
puts("");
}
}
}