2022.2.11 刷题日记
模拟赛
T1
主要思路是把所有能连的边都连上,然后检查所有必须要连的边有没有连上,然后利用并查集,把所有必须要连的边连上,同时检查有没有环,然后考虑连上剩下的边,最后还需要检查是不是一个连通块。
#include<bits/stdc++.h>
#define ll long long
#define dd double
#define ld long double
#define uint unsigned int
#define ull unsigned long long
#define N 1010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x){
x=0;int f=1;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f*=-1;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int a[N][N];
struct Squ{
int l,r;
}sq[N];
int n;
inline bool Insq(int x,int l,int r){
return x>=l&&x<=r;
}
int fa[N],Size[N];
typedef pair<int,int> P;
P ans[N];
int tail;
inline int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}
int main(){
// freopen("data6.in","r",stdin);
// freopen("tree.out","w",stdout);
read(n);
for(int i=1;i<=n;i++){
read(sq[i].l);read(sq[i].r);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
if(i==j) continue;
if(Insq(i,sq[j].l,sq[j].r)&&Insq(j,sq[i].l,sq[i].r)) a[j][i]=a[i][j]=1;
}
bool op=1;
for(int i=1;i<=n;i++){
if(!(a[i][sq[i].l]&&a[i][sq[i].r])){op=0;break;}
}
if(!op){
// cout<<"here1"<<endl;
puts("-1");
return 0;
}
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++) Size[i]=1;
for(int i=1;i<=n;i++){
a[i][sq[i].l]=a[sq[i].l][i]=2;
a[i][sq[i].r]=a[sq[i].r][i]=2;
}
// printf("%d\n",a[138][176]);
op=1;
for(int i=1;i<=n&&op;i++){
for(int j=i+1;j<=n&&op;j++){
if(a[i][j]==2){
int fai=Find(i),faj=Find(j);
if(fai==faj){op=0;break;}
a[i][j]=0;a[j][i]=0;
ans[++tail]=make_pair(i,j);
if(Size[fai]>Size[faj]) swap(fai,faj);
fa[fai]=faj;Size[faj]+=Size[fai];
}
}
if(!op) break;
}
// printf("%d\n",a[138][176]);
if(!op){
// cout<<"here2"<<endl;
puts("-1");return 0;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j) continue;
if(!a[i][j]) continue;
int fai=Find(i),faj=Find(j);
if(fai==faj) continue;
ans[++tail]=make_pair(i,j);
if(Size[fai]>Size[faj]) swap(fai,faj);
fa[fai]=faj;Size[faj]+=Size[fai];
}
}
int fa1=Find(1);
if(Size[fa1]!=n){
// cout<<"here3"<<endl;
puts("-1");return 0;
}
// assert(tail==n-1);
for(int i=1;i<=tail;i++){
printf("%d %d\n",ans[i].first,ans[i].second);
}
return 0;
}
T2
我们考虑找到 \(1\) 节点,考虑二节点位置,要么 \(2\) 节点在其父亲节点,要么在其右儿子的一条左链上。我们用一个 Solve(int l,int Lastl)
来表示当前我们的这颗子树,最小的节点编号是 \(l\),上一个最小的节点编号是 \(Lastl\)。然后这个函数返回一个 pair
,第一个位置表示这颗子树的根,第二个位置表示这颗子树的最大节点编号。
我们返回刚刚的讨论。对于 \(2\) 节点位置的判断我们可以通过询问 \(query(1,1,1)\) 来得到,如果 \(2\) 是父亲,我们接下来就看 \(3\) 的位置,构造 \(2\) 的右子树。否则,我们就需要构造 \(1\) 的右子树,这就是一个子问题。递归调用函数后更新下一个测试的节点。
注意到我们如何找到当前这颗子树的根,我们每次都询问一下 \(l-1\) 这个节点是否包含 \(Last,now\) 内的所有结点,就可以知道上一个 \(now\) 是不是根了,所以我们要时刻记录 \(now\) 的上一个值。
综上所述,询问次数应该是最大 \(2n-1\)
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include"interact.h"
using namespace std;
int n;
pair<int,int>solve(int l,int checkl)
{
int cur=l;
while(cur<=n)
{
int pre=cur;
if(!query(cur,l,cur))
{
pair<int,int>ans=solve(cur+1,l);
cur=ans.second;
report(pre,ans.first);
}
if(l!=1&&query(l-1,checkl,cur))return make_pair(pre,cur);
cur++;
if(cur<=n)report(pre,cur);
}
return make_pair(0,0);
}
void guess(int N)
{
n=N;
solve(1,0);
}
T3
因为所有度数为 \(k\) 的点不相邻,我们很容易想到了利用二分图来构造,那么让左部点全部是度数为 \(k\) 的点,右部点全部是度数为 \(k+1\) 的点。我们考虑一下左右两边的点数。
显然,如果连完边之后,左边节点都是度数为 \(k\) 右边都是 \(k+1\) 的点是最优的。然而这种情况的出现需要满足 \(2k+1|n\),如果不满足,那么我们就有两种点的方案,我们都试一下就可以知道哪个更优。
值得一提的是,出题人吴清月说下取整或上取整中有一个一定是更优的,但是实测中却是不一定,换句话说,我们需要在一定范围内随机扰动一下。
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 1001000
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int n,k,R,L;
typedef pair<int,int> P;
P ans[N],ans2[N];
int tail,tail2,d[N];
int main(){
// freopen("my.out","w",stdout);
read(n);read(k);R=n*k/(2*k+1);L=n-R;
// printf("L=%d R=%d\n",L,R);
int now=0;
for(int i=1;i<=L;i++){
for(int j=1;j<=k;j++){
ans[++tail]=make_pair(i,now+1+L);
now++;now%=R;
}
}
R++;L=n-R;
// printf("L=%d R=%d\n",L,R);
now=0;
for(int i=1;i<=L;i++){
for(int j=1;j<=k;j++){
ans2[++tail2]=make_pair(i,now+1+L);
d[now+1+L]++;
now++;now%=R;
}
}
while(d[now+1+L]<=k){
// printf("now=%d\n",now);
int nxt=now+1;nxt%=R;
ans2[++tail2]=make_pair(nxt+1+L,now+1+L);d[now+1+L]++;d[nxt+1+L]++;
now=nxt;now++;now%=R;
}
// printf("tail1=%d tail2=%d\n",tail,tail2);
if(tail<tail2){
printf("%d\n",tail);
for(int i=1;i<=tail;i++) printf("%d %d\n",ans[i].first,ans[i].second);
}
else{
printf("%d\n",tail2);
for(int i=1;i<=tail2;i++) printf("%d %d\n",ans2[i].first,ans2[i].second);
}
return 0;
}
刷题笔记
AGC30C
我们找到一个 \(n\),如果 \(k\) 小于 \(500\),就把它设为 \(n\),否则 \(n\) 为 500。我们开大一点,给我们操作的空间。
那么我们首先在每一行循环的填上 \(n\),然后把偶数行的 \(1\) 到 \(k-n\) 都设为 \(n+1\) 到 \(k\) 即可。
循环数组的目的是为了保证填完之后每个数旁边的数字都相同。
CF1365F
我们观察到,位置相对的两个值在操作的过程中位置永远相对,而且经过手玩我们可以知道,我们可以任意的把一些数对移动到相应位置。这启发我们如果两个序列相对位置数字相同,换句话说,这样的数对相同,就可以进行操作。利用 multiset 进行判断就可以了。
CF1558C
经过手玩我们就可以知道我们可以用 5 补摆好两个位置,模拟即可。
CF1333E
只要构造出局部,这个题目就可以做完,所以我们需要想办法构造出最小的解。
容易发现大小为 3 的矩阵可以被构造出来。
CF1375E
考虑把最大的值换到最后一个位置,找到最大值位置,考虑一定存在这样一个逆序对,所以我们可以先把其他与最后一个位置有关的值交换完,在把这个位置与我们的最大值位置进行交换。
CF1375E
对于一个排列来说,是很好做的。我们把每个值加上下标变成一个二元组,然后做就可以了。
CF1392E
考虑一共有 \(2n-1\) 条边,考虑开的下,所以我们考虑用二进制存边。不难想到利用对角线。
CF1375D
考虑最终我们把数组转化成的形式,然后简化操作步骤。
CF1485D
经过数值分析可以简单进行构造。主要依据是所有的 \(a\) 都很小,所以它们的 lcm 也不是很大,完全可以承受的住,限制就很好满足了。
AT4363
这个题目的性质比较强,除了逆序对个数和位置奇偶性之外,我们还可以发现可以对序列进行分段,然后考虑每一段内向左和向右的数字分别是单调的。