【题解】CF1819 合集

CF1819A Constructive Problem

标签:思维题 \(C^-\)

你考虑这道题中判 No 显然有两种情况:

  • 如果说 mexn 的话,即我们的所有数都是必不可少不能更改的,那么就是 No
  • 如果说原序列中有 mex+1 那么我们就可以发现添加 mex 显然会有很大的问题,我们显然要将所有的 mex+1 的区间替换为 mex,并且保证其他的数的 mex 和原序列的 mex 相同。

code:

#include<bits/stdc++.h>
using namespace std;
const int NN = 2e5 + 8;
int t,n;
int a[NN];
int b[NN];
int sta[NN],top;
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);top = 0;
		for(int i = 1; i <= n; ++i) scanf("%d",&a[i]),b[i] = a[i];
		sort(b+1,b+1+n);
		int mex = 0;
		for(int i = 1; i <= n; ++i)
			if(b[i] == mex) ++mex;
		if(mex == n){puts("No");continue;}
		for(int i = 1; i <= n; ++i) if(a[i] == mex+1) sta[++top] = i;
		if(top == 1 || top == 0){puts("Yes");continue;}
		int cnt = 0;
		for(int i = 1; i < sta[1]; ++i) b[++cnt] = a[i];
		for(int i = sta[top] + 1; i <= n; ++i) b[++cnt] = a[i];
		sort(b+1,b+1+cnt);
		int mex2 = 0;
		for(int i = 1; i <= cnt; ++i)
			if(b[i] == mex2) ++mex2;
		if(mex2 == mex) puts("Yes");
		else puts("No");
	}
	return 0;
}

CF1819B The Butcher

标签:思维题 \(C^+\)

我们可以清楚地发现,最后剩下的矩形有个性质:

  • 最后一定有一个切出来的子矩形包含原矩形的长/宽
  • 接着我们可以发现,最后切出来的所有子矩形的最长的长和宽才有可能是原矩形的长或宽
  • 由以上性质可以得到,我们可能的原矩形个数不超过 \(2\)

如何 \(check\) 一个矩形是否能被拼出来呢?

  • 我们显然就是找长或宽和现在矩形相同的,然后将现在的矩形减去这个子矩形。
  • 如果最后减不了/减多了就是不能拼凑出来的

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 2e5 + 8;
int t;
int n;
bool vis[NN];
ll a[NN],b[NN];
map<int,vector<int> > MPa,MPb;
bool check(ll x,ll y){
	MPa.clear();MPb.clear();
	for(int i = 1; i <= n; ++i)
		MPa[a[i]].push_back(i),MPb[b[i]].push_back(i),vis[i] = 0;
	while(x != 0 && y != 0){
		auto xx = MPa.find(x),yy = MPb.find(y);
		if(xx != MPa.end()){
			int get = 0;
			for(int i : xx->second){
				if(!vis[i]){
					y -= b[i];
					vis[i] = 1;
					get = 1;
					if(y < 0) return false;
				}
			}
			if(get) continue;
		}
		if(yy != MPb.end()){
			int get = 0;
			for(int i : yy->second){
				if(!vis[i]){
					x -= a[i];
					vis[i] = 1;
					get = 1;
					if(x < 0) return false;
				}
			}
			if(get) continue;
		}
		return false;
	}
	return true;
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		ll maxa = 0,maxb = 0,sum = 0;
		for(int i = 1; i <= n; ++i){
			scanf("%lld%lld",&a[i],&b[i]);
			maxa = max(maxa,a[i]),maxb = max(maxb,b[i]);
			sum += a[i] * b[i];
		}
		int visa = 0,visb = 0;
		if(sum % maxa == 0) visa = check(maxa,sum/maxa);
		if(maxa != sum/maxb && sum % maxb == 0) visb = check(sum/maxb,maxb);
		printf("%d\n",visa + visb);
		if(visa) printf("%lld %lld\n",maxa,sum/maxa);
		if(visb) printf("%lld %lld\n",sum/maxb,maxb);
	}
}

CF1819C The Fox and the Complete Tree Traversal

标签:思维题 \(B\) | 图论 \(C\)

你考虑我们手搓一堆样例可以十分困难地得到性质:

  • 如果树不是毛毛虫就会寄掉(反正我是画了一张草稿纸,提出又 ban 掉了无数条性质才找到的)

知道了这个性质之后,输出方案就很好输出了,每次跳两个,如果中间那个点有儿子,就先把中间点的儿子跳完了,再去那两格。

code:

#include<bits/stdc++.h>
using namespace std;
const int NN = 2e5 + 8;
struct Edge{
	int to,next;
}edge[NN << 1];
int head[NN],cnt;
void init(){
	memset(head,-1,sizeof(head));
	cnt = 1;
}
void add_edge(int u,int v){
	edge[++cnt] = {v,head[u]};
	head[u] = cnt;
}
bool noo;
int sonc[NN],ssc[NN];
void dfs(int u,int fa){
	sonc[u] = 0,ssc[u] = 0;
	for(int i = head[u]; i != -1; i = edge[i].next){
		int v = edge[i].to;
		if(v == fa) continue;
		dfs(v,u);
		++sonc[u];
		if(ssc[u] != 0 && sonc[v] > 0) noo = 1;
		ssc[u] = (sonc[v] > 0) ? v : ssc[u];
	}
	return;
}
void dfs2(int u,int fa,int now){
	if(now) printf("%d ",u);
	if(!now){
		for(int i = head[u]; i != -1; i = edge[i].next){
			int v = edge[i].to;
			if(v == fa || v == ssc[u]) continue;
			printf("%d ",v);
		}
	}
	if(ssc[u]) dfs2(ssc[u],u,now^1);
	
	if(now){
		for(int i = head[u]; i != -1; i = edge[i].next){
			int v = edge[i].to;
			if(v == fa || v == ssc[u]) continue;
			printf("%d ",v);
		}
	}
	if(!now) printf("%d ",u);
}
int n;
int main(){
	scanf("%d",&n);init();
	for(int i = 1,u,v; i < n; ++i){
		scanf("%d%d",&u,&v);
		add_edge(u,v);add_edge(v,u);
	}
	dfs(1,1);
	
	int rt = 1;
	while(ssc[rt] != 0) rt = ssc[rt];
	for(int i = head[rt]; i != -1; i = edge[i].next){
		int v = edge[i].to;
		if(sonc[v] == 0) rt = v;
	}
	
	noo = 0;
	dfs(rt,rt);
	if(noo){puts("No");return 0;}
	puts("Yes");
	dfs2(rt,rt,1);
}

CF1819D Misha and Apples

标签:DP \(B\) | 思维题 \(B^-\)

显然地,我们的序列最后一次清空越早,我们的最后的答案更加优秀。

考虑求出来对每一个 \(i\) 求出 \(lim\) 表示我们能够将到 \(i\) 目前为止的最后一次清空最早在 \(lim\) 处。同时对于每个 \(i\) 求一个 \(cl_i\) 表示在 \(i\) 处清空是否可能

如果说当前的 \(i\)\(lim\sim i\) 中的一个集合重复了且为 \(mx\),为了不让 \(i\) 被清除,那么我们就要将 \(mx\) 清除掉。

即将 \(lim\) 变为 \(mx\) 及其后面的第一个能够被清除的位置。

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 2e5 + 8;

int n,m,k;
int pre[NN],cl[NN],las[NN];
vector<int> a[NN];
void solve(){
	scanf("%d%d",&n,&m);
	for(int i = 1; i <= n; ++i){
		scanf("%d",&k);
		a[i].resize(k);
		for(int j = 0; j < k; ++j) scanf("%d",&a[i][j]),las[a[i][j]] = 0;
		pre[i] = pre[i-1] + (k==0);
	}
	cl[0] = 1;
	int lim = 0;
	for(int i = 1; i <= n; ++i){
		int mx = 0;
		for(int j : a[i]) mx = max(mx,las[j]),las[j] = i;
		if(lim < mx || pre[lim] < pre[i]) cl[i] = 1;
		else cl[i] = 0;
		while(lim < mx || !cl[lim]) ++lim;
	}
    if(pre[lim] < pre[n]) printf("%d\n",m);
    else{
        int sum = 0;
        while(lim + 1 <= n) sum += a[++lim].size();
        printf("%d\n",sum);
    }
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		solve();
	}
}

CF1819E Roads in E City

标签:思维题 \(A\) | 图论 \(C\)

我们可以先找到一个全是特殊边的最小生成树:

  • 我们对于每一条边都断一次,然后对该边的两个点都询问 \(20\) 次,可以证明,如果说该边是当前特殊边图中的桥,有 \(99.9999\%\) 的几率会询问到 \(0\) (即删边后,两个点不连通)
  • 如果不是特殊边上的桥/不是特殊边,那么回答一定全是 \(1\),那么我们便删去这条边。可以知道,我们删去的这条边如果是特殊边,可能生成新特殊边图中的桥。

这样,我们就花费了 \(O(40m)\) 次询问,找到了一个特殊边生成树。

但是,这个时候,还有一些特殊边没有被找出来。

我们如何判断一个边是不是特殊边呢?

  • 我们对于一条非树边,删去树上非树边链接的两个点之间的一条边,建上非树边,然后再按上述方式询问两点是否联通,如果连通,该边就是特殊边,不连通即为普通边。

最后我们便用了 \(O(80m-40n)\) 的代价找到了所有边的类型。

#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f,NN = 2e3 + 8,MM = 2e3 + 8;
int T;
int n,m;
int type[NN],dep[NN],F[NN];
struct Edge{
	int u,v;
}edge[MM];
vector<Edge> tr[NN];
bool check(int i){
	int u = edge[i].u,v = edge[i].v;
	bool flag = 0,ot;
	for(int i = 1; i <= 20; ++i){
		printf("? %d\n",u);fflush(stdout);
		scanf("%d",&ot);flag |= !ot;
		printf("? %d\n",v);fflush(stdout);
		scanf("%d",&ot);flag |= !ot;
		if(flag) return flag;
	}
	return flag;
}
void dfs(int u, int fa){
    for(auto &e : tr[u]){
        int v = e.v;
        if(v == fa) continue;
        F[v] = e.u;
		dep[v] = dep[u] + 1;
		dfs(v, u);
    }
}
int main(){
	scanf("%d",&T);
	while(T--){
		memset(type,0,sizeof(type));
		memset(dep,0,sizeof(dep));
		memset(F,0,sizeof(F));
		for(int i = 1; i <= n; ++i) tr[i].clear();
		scanf("%d%d",&n,&m);
		for(int i = 1; i <= m; ++i) scanf("%d%d",&edge[i].u,&edge[i].v);
		for(int i = 1; i <= m; ++i){
			int u = edge[i].u, v = edge[i].v;
			printf("- %d\n",i);fflush(stdout);
            if(check(i)){
                tr[u].push_back({i,v});
				tr[v].push_back({i,u});
                printf("+ %d\n",i);fflush(stdout);
                type[i] = true;
            }
		} 
		
		dfs(1, 0);
		
        for(int i = 1; i <= m; ++i)
			if(!type[i]){
	            int u = edge[i].u, v = edge[i].v;
	            if(dep[u] < dep[v]) swap(u, v);
	            printf("- %d\n",F[u]);fflush(stdout);
	            printf("+ %d\n",i);fflush(stdout);
	            if(!check(i)) type[i] = true;
	            printf("- %d\n",i);fflush(stdout);
	            printf("+ %d\n",F[u]);fflush(stdout);
	        }
        printf("! ");
        for(int i = 1; i <= m; ++i) printf("%d ",type[i]);
        puts("");fflush(stdout);
        int f; scanf("%d",&f); if(f == 0) return 0;
	}
}
posted @ 2023-09-11 21:29  ricky_lin  阅读(88)  评论(0)    收藏  举报