CodeForces 1385 - Codeforces Round #656 (Div. 3)

今天下午看到有7个题,打了个vp,一开始准备用chenxiaoyan打的,然后调错时间了,无奈只能用小号WinnieThePooh打/xk

最终还剩几分钟的时候AK了/cy

吐槽一下,这场咋全多测啊?

CF比赛页面传送门

A - Three Pairwise Maximums

洛谷还没爬,下同,管理员快爪巴 & CF题目页面传送门

题意紫帆。

考虑将\(a,b,c\)从小到大排序,则\(x=b,y=z=c\)。将题目给出的\(x,y,z\)排序之后,若\(y\neq z\)则无解,否则\(a=1,b=x,c=y\)是一组解。

代码:

#include<bits/stdc++.h>
using namespace std;
void mian(){
	int x,y,z;
	scanf("%d%d%d",&x,&y,&z);
	if(x>y)swap(x,y);if(y>z)swap(y,z);
	if(x>y)swap(x,y);if(y>z)swap(y,z);
	if(y!=z)return puts("NO"),void();
	puts("YES");
	cout<<1<<" "<<x<<" "<<y<<"\n";
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

B - Restore the Permutation by Merger

CF题目页面传送门

题意紫帆。

\(1\sim n\)所有数以第一次出现的位置取下来,相对位置不变,组成一个排列,就是答案。自证不难。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=50;
int n;
bool hav[N+1];
void mian(){
	cin>>n;
	memset(hav,0,sizeof(hav));
	for(int i=1;i<=2*n;i++){
		int x;
		scanf("%d",&x);
		if(!hav[x])hav[x]=true,printf("%d ",x);
	}
	puts("");
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

C - Make It Good

CF题目页面传送门

题意紫帆。

不难发现一个数列是好的当且仅当它非严格单峰。自证不难。又发现,删掉前缀相当于留下后缀(这个出题人迷惑的太失败了),右端点是不变的。于是贪心,从右往左找到最左边的一个可以当峰值的位置,然后再从峰值往左边找到最左边的左端点。最终答案就是左端点减一。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=200000;
int n;
int a[N+1];
void mian(){
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%d",a+i);
	int las;
	for(int i=n;i;i--){//找最左峰值 
		if(i<n&&a[i]<a[i+1])break;
		las=i;
	}
	int las0;
	for(int i=las;i;i--){//找最左左端点 
		if(i<las&&a[i]>a[i+1])break;
		las0=i;
	}
	cout<<las0-1<<"\n";
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

D - a-Good String

CF题目页面传送门

题意紫帆。

做法很显然,考虑DP,设\(dp_{l,r,x}\)表示子串\(s_{l\sim r}\)成为\(x\)-good string所需要的最小操作数。边界:\(dp_{l,l,x}=[s_l\neq x]\),目标:\(dp_{1,n,\texttt a}\),转移:\(dp_{l,r,x}=\min\left(\sum\limits_{i=l}^{\frac{l+r-1}2}[s_i\neq x]+dp_{\frac{l+r-1}2+1,r,x+1},\sum\limits_{i=\frac{l+r-1}2+1}^{r}[s_i\neq x]+dp_{l,\frac{l+r-1}2,x+1}\right)\)

然而这样看起来空间是\(\mathrm O\!\left(n^2\log n\right)\)的,其实合法的\((l,r)\)对只有\(\mathrm O(n)\)个,跟线段树类似,而且对于每个合法的\((l,r)\)都只有一个合法的\(x\)对应。所以空间复杂度\(\mathrm O(n)\),时间复杂度类似归并树,是\(\mathrm O\!\left(\sum n\log n\right)\)的。然鹅现场我没有考虑到,于是空间开了\(\mathrm O(n\log n)\),清空数组的时候也花了这么多。不管了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=200000,LET=20;
int n;
char a[N+5];
int dp[N<<2][LET];
int dfs(int l=1,int r=n,int c=0,int p=1){//记忆化搜索 
	if(l==r)return a[l]!='a'+c;
	if(~dp[p][c])return dp[p][c]; 
	int &res=dp[p][c],mid=l+r>>1,sum1=0,sum2=0;
	for(int i=l;i<=mid;i++)sum1+=a[i]!='a'+c;
	for(int i=mid+1;i<=r;i++)sum2+=a[i]!='a'+c;
	res=min(sum1+dfs(mid+1,r,c+1,p<<1|1),sum2+dfs(l,mid,c+1,p<<1));//转移方程 
//	printf("dp[%d][%d][%d]=%d\n",l,r,c,res);
	return res;
}
void mian(){
	cin>>n;
	scanf("%s",a+1);
	for(int i=1;i<=4*n;i++)for(int j=0;j<LET;j++)dp[i][j]=-1;//清空 
	cout<<dfs()<<"\n";
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

E - Directing Edges

CF题目页面传送门

有一张\(n\)个点\(m\)条边的混合图,你需要给所有无向边定向,使得最终得到的有向图无环。或报告无解。本题多测。

\(\sum n\in\left[2,2\times10^5\right],\sum m\in\left[1,2\times10^5\right]\)

事实证明,这是本场最难的一题,因为我到最后才想出来

首先注意到,若不考虑所有无向边,剩下来的有向图是有环的,那显然无解。

然后呢?当时顺序做到这题的时候我想了DFS,Tarjan缩点(这个一年没写了,我已经不会了),都没有思路,却没有考虑到有向图上经常用到的拓扑排序(最后才想到)。考虑对不考虑所有无向边得到的有向图拓扑排序,那么无向边的定向方案很容易构造:拓扑序小的连向拓扑序大的即可保证无环。

代码:

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
const int N=200000;
int n,m;
vector<pair<int,int> > nei[N+1];
int ideg[N+1];
vector<int> topo;
int id[N+1];
void toposort(){//拓扑排序 
	topo.clear();
	queue<int> q;
	for(int i=1;i<=n;i++)if(!ideg[i])q.push(i);
	while(q.size()){
		int x=q.front();
		q.pop();
		topo.pb(x);
		for(int i=0;i<nei[x].size();i++){
			int y=nei[x][i].X,z=nei[x][i].Y;
			if(z)/*不考虑无向边*/if(!--ideg[y])q.push(y);
		}
	}
}
void mian(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)ideg[i]=0,nei[i].clear();
	while(m--){
		int x,y,z;
		scanf("%d%d%d",&z,&x,&y);
		if(z)nei[x].pb(mp(y,z)),ideg[y]++;
		else nei[x].pb(mp(y,z)),nei[y].pb(mp(x,z));
	}
	toposort();
	if(topo.size()!=n)return puts("NO"),void();//无解 
	puts("YES");
	for(int i=0;i<n;i++)id[topo[i]]=i;
	for(int i=1;i<=n;i++)for(int j=0;j<nei[i].size();j++){
		int x=nei[i][j].X,y=nei[i][j].Y;
		if(y)printf("%d %d\n",i,x);
		else if(id[i]<id[x])printf("%d %d\n",i,x);//拓扑序小的连向拓扑序大的 
	}
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

F - Removing Leaves

CF题目页面传送门

给定一棵无根树\(T=(V,E),|V|=n,|E|=n-1\)和一个常数\(m\)。每次可以选择恰好\(m\)个唯一连接的节点相同的叶子并删除。求最多能删多少次。本题多测。

\(\sum n\in\left[1,2\times 10^5\right]\)

我记得我在这篇里吐槽过打的3次D3F全是树形DP?这次树形DP喜加一。

注意到,对于任何一种删的方案,最终必定会有至少一个节点留下来删不掉。我们可以钦定这个点为根树形DP,最后二次扫描。接下来考虑如何DP。

\(dp0_i\)表示子树\(i\)是否能删得只剩一个\(i\)\(dp_i\)表示子树\(i\)内最多能删几次。转移挺简单的,设\(cnt_i=\sum\limits_{j\in son_i}dp0_j\),则

\[\begin{cases}dp0_i=[cnt_i=|son_i|][m\mid cnt_i]\\dp_i=\sum\limits_{j\in son_i}dp_j+\left\lfloor\dfrac{cnt_i}m\right\rfloor\end{cases} \]

接下来就愉快地做出来了。

然而现场我nt了。我就懒得写严格\(\mathrm O(1)\)的换根,写了个calc函数计算DP值(\(\mathrm O(|son_x|)\)),然后换根的时候调用这个函数。我当时zz地认为这样是均摊\(\mathrm O(1)\)的,交上去,TLE13。我就很生气,CF啥时候也卡常了?就算卡,这也要卡?于是吸臭氧码读优还是T。无奈之下只好改成严格\(\mathrm O(1)\)的换根,这样还需要记录\(cnt\)数组。然后就A了。赛后才发现一个菊花图就能把我卡没了。。。。。

代码:

#pragma GCC optimize(3)///xk
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
void read(int &x){///xk
	x=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
void prt(int x){///xk
	if(x>9)prt(x/10);
	putchar(x%10^48);
}
const int N=200000;
int n,m;
vector<int> nei[N+1];
int cnt[N+1];
bool dp0[N+1];
int dp[N+1];
void dfs(int x=1,int fa=0){//初DP 
	cnt[x]=0;dp0[x]=true;dp[x]=0;
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i];
		if(y==fa)continue;
		dfs(y,x);
		cnt[x]+=dp0[y];dp0[x]&=dp0[y];dp[x]+=dp[y];
	}
	dp[x]+=cnt[x]/m;
	dp0[x]&=cnt[x]%m==0;
//	printf("dp[%d]=%d\n",x,dp[x]);
}
int ans;
void dfs0(int x=1,int fa=0){//二次扫描 
//	printf("%d=%d\n",x,dp[x]);
	ans=max(ans,dp[x]);//更新答案 
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i];
		if(y==fa)continue;
		int cnt_x=cnt[x],dp0_x=dp0[x],dp_x=dp[x],cnt_y=cnt[y],dp0_y=dp0[y],dp_y=dp[y];
		cnt[x]-=dp0[y];dp0[x]=cnt[x]==nei[x].size()-1&&cnt[x]%m==0;dp[x]=dp[x]-dp[y]-(cnt[x]+dp0[y])/m+cnt[x]/m;
		cnt[y]+=dp0[x];dp0[y]=cnt[y]==nei[y].size()&&cnt[y]%m==0;dp[y]=dp[y]+dp[x]-(cnt[y]-dp0[x])/m+cnt[y]/m;//换根 
		dfs0(y,x);
		cnt[x]=cnt_x;dp0[x]=dp0_x;dp[x]=dp_x;cnt[y]=cnt_y;dp0[y]=dp0_y;dp[y]=dp_y;//还原 
	}
}
void mian(){
	read(n);read(m);
	for(int i=1;i<=n;i++)nei[i].clear();
	for(int i=1;i<n;i++){
		int x,y;
		read(x);read(y);
		nei[x].pb(y);nei[y].pb(x);
	} 
	dfs();
	ans=0;dfs0();
	prt(ans);putchar('\n');
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

G - Columns Swaps

CF题目页面传送门

有一个\(2\times n\)的矩阵\(a\),每个数在\([1,n]\)内。要求若干次交换某一列的\(2\)个值,使得最后每行都是\(1\sim n\)的排列。求最小次数以及对应方案,或报告无解。本题多测。

\(\sum n\in[1,2\times 10^5]\)

首先,每个数出现的次数必须要是\(2\),否则无解。

然后。看到排列想到图论。不难发现结论:若\(\forall i\in[1,n]\),连有向边\((a_{1,i},a_{2,i})\),得到的图可以表示为一个排列当且仅当上下都是排列。证明的话,必要性可以用置换的乘积证,充分性xjb随便证即可(就是每个点入出度都为\(1\),则在上下各出现过一次)。

那么,原操作相当于将一条边反向。考虑将原\(a\)的图建出来,不考虑方向依次考虑每个CC(环),然后可以整体调成\(2\)种方向,比个大小即可。对于CC大小为\(1\)\(2\)需要讨论一下,有点烦?(当时差点讨论绝望了)

代码:

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
const int N=200000;
int n;
int a[N+1],b[N+1];
vector<pair<int,pair<int,int> > > nei[N+1];
int cnt[N+1];
bool vis[N+1];
vector<int> zero,one;
int st,to;
void dfs(int x){//找环 
	vis[x]=true;
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i].X,z=nei[x][i].Y.X,xx=nei[x][i].Y.Y;
		if(!vis[y]){
			if(x==st)to=y;
			dfs(y);
			if(xx)one.pb(z);else zero.pb(z);
		}
	}
}
void mian(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)cnt[i]=0,nei[i].clear(),vis[i]=false;
	for(int i=1;i<=n;i++)scanf("%d",a+i),cnt[a[i]]++;
	for(int i=1;i<=n;i++)scanf("%d",b+i),cnt[b[i]]++;
	for(int i=1;i<=n;i++)if(cnt[i]!=2)return puts("-1"),void();//判无解 
	for(int i=1;i<=n;i++)nei[a[i]].pb(mp(b[i],mp(i,1))),nei[b[i]].pb(mp(a[i],mp(i,0)));//建图 
	vector<int> ans;
	for(int i=1;i<=n;i++)if(!vis[i]){
		one.clear(),zero.clear(),st=i,dfs(i);
		if(one.empty()&&zero.empty())continue;//大小为1 
		if(nei[st][0].X==nei[st][1].X){//大小为2 
			if(nei[st][0].Y.Y==nei[st][1].Y.Y)ans.pb(nei[st][0].Y.X);
			continue;
		}
		int id=nei[st][0].X==to?1:0;
		if(nei[st][id].Y.Y)zero.pb(nei[st][id].Y.X);else one.pb(nei[st][id].Y.X);
		if(one.size()<zero.size())for(int i=0;i<one.size();i++)ans.pb(one[i]);
		else for(int i=0;i<zero.size();i++)ans.pb(zero[i]);//比大小压进答案序列 
	}
	printf("%d\n",int(ans.size()));
	for(int i=0;i<ans.size();i++)printf("%d ",ans[i]);
	puts("");
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}
posted @ 2020-07-18 20:06  ycx060617  阅读(603)  评论(5编辑  收藏  举报