Codeforces Round 1416 简要题解

47 篇文章 1 订阅
10 篇文章 0 订阅

A. k-Amazing Numbers

B. Make Them Equal

C. XOR Inverse

D. Graph and Queries

处理仅含删边的图连通性问题,可以离线后倒着处理,转化为仅含加边的图连通性问题,可以简单地用并查集维护。不仅如此,我们还可以尝试对连通块的合并建出一个有根树森林,其中叶子节点是初始时的点,每次合并两个连通块新建一个点作为它们的共同父亲即可。这样,任意时刻某个连通块对应的点集对应某个子树,若我们只维护真实的节点,可以理解为dfs序上一段区间。
这样问题就比较简单了。我们再正着扫一遍处理询问,对dfs序用一棵线段树维护区间最大值,每次询问即为找出区间最大值并做单点修改。
时间复杂度为 O ( ( n + m + q ) log ⁡ n ) \mathcal O((n+m+q)\log n) O((n+m+q)logn)

#include <bits/stdc++.h>
#define last last2
#define inf 0x3f3f3f3f
#define lowbit(x) (x&-x)
#define FR first
#define SE second

using namespace std;

typedef long long ll;
typedef pair<int,int> pr;

namespace SETS {

int fa[500005];

void init(int n) {
  for(int i=1;i<=n;i++) fa[i]=i;
}

int find_father(int x) {
  return (fa[x]==x)?x:fa[x]=find_father(fa[x]);
}

bool check(int x,int y) {
  x=find_father(x);y=find_father(y);
  return x!=y;
}

void merge(int x,int y) {
  x=find_father(x);y=find_father(y);
  if (x==y) return;
  fa[x]=y;
}

}

int num[500005],bel[500005],pos[500005];

namespace SGT {

int maxn[2000000];

inline void pushup(int o) {
  if (num[maxn[o*2]]>=num[maxn[o*2+1]]) maxn[o]=maxn[o*2];
  else maxn[o]=maxn[o*2+1];
}

void build(int l,int r,int o) {
  if (l==r) maxn[o]=bel[l];
  else {
  	int m=((l+r)>>1);
  	build(l,m,o*2);
  	build(m+1,r,o*2+1);
  	pushup(o);
  }
}

void update(int l,int r,int o,int p) {
  if (l==r) return;
  else {
  	int m=((l+r)>>1);
  	if (m>=p) update(l,m,o*2,p);
  	else update(m+1,r,o*2+1,p);
  	pushup(o);
  }
}

int query(int l,int r,int o,int lx,int rx) {
  if (l>=lx&&r<=rx) return maxn[o];
  else {
  	int m=((l+r)>>1);
  	if (m>=rx) return query(l,m,o*2,lx,rx);
  	if (m<lx) return query(m+1,r,o*2+1,lx,rx);
  	int a=query(l,m,o*2,lx,rx),b=query(m+1,r,o*2+1,lx,rx);
  	return (num[a]>=num[b])?a:b;
  }
}

}

int ch[1000005][2],dfn[1000005],ed[1000005],dfs_cnt,n;

void dfs(int x) {
  dfn[x]=dfs_cnt+1;
  if (x<=n) bel[++dfs_cnt]=x;
  if (ch[x][0]) dfs(ch[x][0]);
  if (ch[x][1]) dfs(ch[x][1]);
  ed[x]=dfs_cnt;
}

pr a[500005],e[500005];
bool vis[500005];
int rt[500005],id[500005],sz;

void merge(int x,int y) {
  x=SETS::find_father(x);y=SETS::find_father(y);
  if (x==y) return;
  SETS::merge(x,y);
  sz++;
  ch[sz][0]=rt[x];ch[sz][1]=rt[y];
  rt[SETS::find_father(x)]=sz;
}

int main() {
  int m,k;
  scanf("%d%d%d",&n,&m,&k);
  for(int i=1;i<=n;i++) scanf("%d",&num[i]);
  for(int i=1;i<=m;i++) scanf("%d%d",&e[i].FR,&e[i].SE);
  for(int i=1;i<=k;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	a[i]=pr(x,y);
  	if (x==2) vis[y]=1;
  }
  SETS::init(n);
  for(int i=1;i<=n;i++) rt[i]=i;
  sz=n;
  for(int i=1;i<=m;i++)
    if (!vis[i]) merge(e[i].FR,e[i].SE);
  for(int i=k;i>0;i--)
    if (a[i].FR==1) id[i]=rt[SETS::find_father(a[i].SE)];
    else merge(e[a[i].SE].FR,e[a[i].SE].SE);
  for(int i=1;i<=n;i++)
    if (SETS::find_father(i)==i) dfs(rt[i]);
  SGT::build(1,n,1);
  for(int i=1;i<=k;i++)
    if (a[i].FR==1) {
    	int x=id[i];
    	int v=SGT::query(1,n,1,dfn[x],ed[x]);
    	printf("%d\n",num[v]);
    	num[v]=0;
    	SGT::update(1,n,1,dfn[v]);
	}
  return 0;
}

E. Split

可以将问题转化为最大化 b i = b i + 1 b_i=b_{i+1} bi=bi+1的对数。
首先考虑一个暴力的DP。令 f i , j f_{i,j} fi,j仅考虑 b 1 ∼ b 2 i b_1\sim b_{2i} b1b2i,且 b 2 i = j b_{2i}=j b2i=j的最大值,这里有 1 ≤ j < a i 1\leq j<a_i 1j<ai,转移是容易的。
容易发现对于某个 i i i,令 max ⁡ j = 1 ∼ a i − 1 f i , j = p i \max_{j=1\sim a_i-1}f_{i,j}=p_i maxj=1ai1fi,j=pi,显然只有那些 f i , j = p i f_{i,j}=p_i fi,j=pi j j j是有用的(否则假设最优解中前 2 i 2i 2i个的 f < p i f<p_i f<pi,那么直接取某个 f i , j = p i f_{i,j}=p_i fi,j=pi的,将后面设为与最优解中相同,这样一定不会变劣)。
那么我们可以考虑记录下 p i p_i pi,并且只维护满足 f i , j = p i f_{i,j}=p_i fi,j=pi j j j组成的集合 S i S_i Si。转移的时候我们可能会将 S i S_i Si中的元素 x x x变为 a i + 1 − x a_{i+1}-x ai+1x,或是删除 < 1 <1 <1 x x x,或是单点插入,随后分类讨论一下 p i + 1 p_{i+1} pi+1即可。显然 S i S_i Si中的极大连续段(区间)只有 O ( n ) \mathcal O(n) O(n)个,我们可以用set维护这些区间,每次暴力删除越界的区间。转移的时候要涉及将 x x x变为 a i − x a_i-x aix,比较简单的方式是打一个整体修改的标记,这样不需要特别的处理。
单组数据时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

#include <bits/stdc++.h>
#define last last2
#define inf 0x3f3f3f3f3f3f3f3fLL
#define lowbit(x) (x&-x)
#define FR first
#define SE second

using namespace std;

typedef long long ll;
typedef pair<ll,ll> pr;

set <pr> st;
ll v,c;

void pop() {
  if (v==1) {
  	while (!st.empty()) {
  		set<pr>::iterator it=st.begin();
  		if ((it->SE)+c>0) break;
  		else {
  			pr t=*it;
  			st.erase(it);
  			if (t.FR+c>0) {
  				st.insert(pr(t.FR,1-c));
  				break;
			  }
		  }
	  }
  }
  else {
  	while (!st.empty()) {
  		set<pr>::iterator it=st.end();
  		it--;
  		if (-(it->FR)+c>0) break;
  		else {
  			pr t=*it;
  			st.erase(it);
  			if (-t.SE+c>0) {
  				st.insert(pr(c-1,t.SE));
  				break;
			  }
		  }
	  }
  }
}

bool find(int k) {
  if (st.empty()) return 0;
  if (v==1) {
  	set<pr>::iterator it=st.lower_bound(pr(k-c,-inf));
  	if (it!=st.end()&&(it->SE)<=k-c) return 1;
  	else return 0;
  }
  else {
  	set<pr>::iterator it=st.lower_bound(pr(c-k,-inf));
  	if (it!=st.end()&&(it->SE)<=c-k) return 1;
  	else return 0;
  }
}

void insert(int k) {
  if (v==1) st.insert(pr(k-c,k-c));
  else st.insert(pr(c-k,c-k));
}

int num[500005];

int main() {
  int cases;
  scanf("%d",&cases);
  for(;cases;cases--) {
  	st.clear();
  	v=1;c=0;
  	int n;
  	scanf("%d",&n);
  	for(int i=1;i<=n;i++) scanf("%d",&num[i]);
  	int ans=2*n;
  	if (num[1]%2==0) {
  		ans--;
  		st.insert(pr(num[1]>>1,num[1]>>1));
	  }
	else st.insert(pr(num[1]-1,1));
	for(int i=2;i<=n;i++) {
		v=-v;c=num[i]-c;
		pop();
		if (num[i]%2==0) {
			if (find(num[i]>>1)) {
				ans-=2;
				v=1;c=0;
				st.clear();
				st.insert(pr(num[i]>>1,num[i]>>1));
			}
			else {
				ans-=1;
				insert(num[i]>>1);
			}
		}
		else if (!st.empty()) ans-=1;
		else {
			v=1;c=0;
			st.clear();
			st.insert(pr(num[i]-1,1));
		}
	}
	printf("%d\n",ans);
  }
  return 0;
}

F. Showing Off

令最终的矩阵为 A A A
容易发现按题目中的方式,最终形成的有向图一定是一个基环内向树森林,且每个环的长度都是偶数,环上所有的 a a a都相同,还要满足这个 a a a至少是环长,且不在环上的点一定满足父亲的 a a a小于自己的 a a a。进一步可以发现,对于一个长度 > 2 >2 >2的偶环我们可以任取一个相邻点的匹配,匹配的点之间互相连边,这样就只有长为 2 2 2的环了,那么 a a a一定不小于环长。
这样问题就比较简单了。可以发现一个点一定不可能连向 a a a大于它的点,因此我们可以对 a a a从小到大构造。每次设当前还没有确定父亲的点中最小的 a a a k ≥ 2 k\geq 2 k2,我们考虑 a = k a=k a=k的点的集合 S S S,确定这些点的出边。那么对于某个 x ∈ S x\in S xS,它可能跟另一个相邻的 y ∈ S y\in S yS匹配成一个长为 2 2 2的环,或是连向某个相邻的满足 a < k a<k a<k的点 z z z
注意到这是一个网格图,一定是二分图。那么对 S S S中点黑白染色后,容易建出一个上下界网络流模型:令源点为 S S S,汇点为 T T T,对于某个黑点 x x x,连边 ( S , x ) (S,x) (S,x),上下界均为 1 1 1,且若它与某个 a < k a<k a<k的点 z z z相邻,连边 ( x , T ) (x,T) (x,T),下界为 0 0 0,上界为 1 1 1;对于某个黑点 y y y,连边 ( y , T ) (y,T) (y,T),上下界均为 1 1 1,且若它与某个 a < k a<k a<k的点 z z z相邻,连边 ( S , y ) (S,y) (S,y),下界为 0 0 0,上界为 1 1 1;若 x x x y y y相邻,额外连边 ( x , y ) (x,y) (x,y),下界为 0 0 0,上界为 1 1 1。那么若该上下界网络流没有可行解显然原问题无解,否则容易根据得到的可行流构造方案。
我们构出的的图总点数和边数都是 O ( n m ) \mathcal O(nm) O(nm)级别的,且这是个单位权图,直接跑dinic算法单组数据时间复杂度为 O ( n m n m ) \mathcal O(nm\sqrt{nm}) O(nmnm )

#include <bits/stdc++.h>
#define last last2
#define inf 0x3f3f3f3f
#define lowbit(x) (x&-x)
#define FR first
#define SE second

using namespace std;

typedef long long ll;
typedef pair<int,int> pr;

struct Edge {
  int t,f,next;
  Edge() {}
  Edge(int a,int b,int c):t(a),f(b),next(c) {}
};

Edge e[5000000];
int head[100010],vs,vt,tot;

inline void addEdge(int x,int y,int z) {
  e[++tot]=Edge(y,z,head[x]);
  head[x]=tot;
  e[++tot]=Edge(x,0,head[y]);
  head[y]=tot;
}

namespace Flow {

int d[100010],cur[100010];
queue <int> q;

bool bfs() {
  while (!q.empty()) q.pop();
  for(int i=vs;i<=vt;i++) d[i]=-1;
  d[vs]=0;cur[vs]=head[vs];
  q.push(vs);
  while (!q.empty()) {
  	int x=q.front();q.pop();
  	for(int i=head[x];i!=-1;i=e[i].next)
  	  if (e[i].f&&d[e[i].t]==-1) {
  	  	  int u=e[i].t;
  	  	  d[u]=d[x]+1;
  	  	  cur[u]=head[u];
  	  	  if (u==vt) return 1;
  	  	  q.push(u);
		}
  }
  return 0;
}

int dfs(int x,int a) {
  if (x==vt||!a) return a;
  int ans=0;
  for(int &i=cur[x];i!=-1;i=e[i].next)
    if (e[i].f&&d[e[i].t]==d[x]+1) {
    	int u=e[i].t;
    	int f=dfs(u,min(a,e[i].f));
    	if (f) {
    		e[i].f-=f;
    		e[i^1].f+=f;
    		ans+=f;
    		a-=f;
    		if (!a) break;
		}
	}
  return ans;
}

int maxflow() {
  int ans=0;
  while (bfs())
    ans+=dfs(vs,inf);
  return ans;
}

}

int *num[100005],*id[100005],*ans1[100005];
char *ans2[100005];

pr a[100005];

bool solve(int n,int m,int k,int val) {
  vs=0;vt=k+3;
  tot=-1;
  for(int i=vs;i<=vt;i++) head[i]=-1;
  int sz1=0,sz2=0;
  for(int i=1;i<=k;i++) id[a[i].FR][a[i].SE]=i;
  for(int i=1;i<=k;i++) {
  	int x=a[i].FR,y=a[i].SE;
  	if (!((x^y)&1)) {
  		sz1++;
  		addEdge(vs,i,1);
  		bool v=0;
  		if (x>1) {
  			if (num[x-1][y]<val) v=1;
  			else if (num[x-1][y]==val) addEdge(id[x][y],id[x-1][y],1);
		  }
		if (x<n) {
			if (num[x+1][y]<val) v=1;
			else if (num[x+1][y]==val) addEdge(id[x][y],id[x+1][y],1);
		}
		if (y>1) {
			if (num[x][y-1]<val) v=1;
			else if (num[x][y-1]==val) addEdge(id[x][y],id[x][y-1],1);
		}
		if (y<m) {
			if (num[x][y+1]<val) v=1;
			else if (num[x][y+1]==val) addEdge(id[x][y],id[x][y+1],1);
		}
		if (v) addEdge(i,k+2,1);
	  }
	else {
		sz2++;
		addEdge(i,vt,1);
		bool v=0;
		if (x>1&&num[x-1][y]<val) v=1;
		if (x<n&&num[x+1][y]<val) v=1;
		if (y>1&&num[x][y-1]<val) v=1;
		if (y<m&&num[x][y+1]<val) v=1;
		if (v) addEdge(k+1,i,1);
	}
  }
  addEdge(k+2,k+1,inf);
  addEdge(k+1,vt,sz1);
  addEdge(vs,k+2,sz2);
  if (Flow::maxflow()<sz1+sz2) return 0;
  for(int i=1;i<=k;i++) {
  	int x=a[i].FR,y=a[i].SE;
  	if (!((x^y)&1)) {
  		for(int j=head[i];j!=-1;j=e[j].next)
  		  if (e[j].t==k+2&&!e[j].f) {
  		  	  if (x>1&&num[x-1][y]<val) {
  		  	    ans1[x][y]=val-num[x-1][y];
  		  	    ans2[x][y]='U';
			  }
			  else if (x<n&&num[x+1][y]<val) {
			  	ans1[x][y]=val-num[x+1][y];
			  	ans2[x][y]='D';
			  }
			  else if (y>1&&num[x][y-1]<val) {
			  	ans1[x][y]=val-num[x][y-1];
			  	ans2[x][y]='L';
			  }
			  else if (y<m&&num[x][y+1]<val) {
			  	ans1[x][y]=val-num[x][y+1];
			  	ans2[x][y]='R';
			  }
			}
		  else if (e[j].t>=1&&e[j].t<=k&&!e[j].f) {
		  	  int u=a[e[j].t].FR,v=a[e[j].t].SE;
		  	  ans1[x][y]=1;
		  	  ans1[u][v]=val-1;
		  	  if (u==x-1) {
		  	  	  ans2[x][y]='U';
		  	  	  ans2[u][v]='D';
				}
			  else if (u==x+1) {
			      ans2[x][y]='D';
			      ans2[u][v]='U';
			  }
			  else if (v==y-1) {
			  	  ans2[x][y]='L';
			  	  ans2[u][v]='R';
			  }
			  else if (v==y+1) {
			  	  ans2[x][y]='R';
			  	  ans2[u][v]='L';
			  }
		    }
	  }
	else {
  		for(int j=head[i];j!=-1;j=e[j].next)
  		  if (e[j].t==k+1&&e[j].f) {
  		  	  if (x>1&&num[x-1][y]<val) {
  		  	    ans1[x][y]=val-num[x-1][y];
  		  	    ans2[x][y]='U';
			  }
			  else if (x<n&&num[x+1][y]<val) {
			  	ans1[x][y]=val-num[x+1][y];
			  	ans2[x][y]='D';
			  }
			  else if (y>1&&num[x][y-1]<val) {
			  	ans1[x][y]=val-num[x][y-1];
			  	ans2[x][y]='L';
			  }
			  else if (y<m&&num[x][y+1]<val) {
			  	ans1[x][y]=val-num[x][y+1];
			  	ans2[x][y]='R';
			  }
			}
	}
  }
  return 1;
}

bool cmp(pr x,pr y) {
  return num[x.FR][x.SE]<num[y.FR][y.SE];
}

pr b[100005];

int main() {
  int cases;
  scanf("%d",&cases);
  for(;cases;cases--) {
  	int n,m;
  	scanf("%d%d",&n,&m);
  	for(int i=0;i<=n+1;i++) {
  		num[i]=new int[m+2];
  		id[i]=new int[m+2];
  		ans1[i]=new int[m+2];
  		ans2[i]=new char[m+2];
  		memset(num[i],0,sizeof(int)*(m+2));
  		memset(id[i],0,sizeof(int)*(m+2));
  		memset(ans1[i],0,sizeof(int)*(m+2));
  		memset(ans2[i],0,sizeof(char)*(m+2));
	  }
	int sz=0;
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=m;j++) {
	  	scanf("%d",&num[i][j]);
	  	b[++sz]=pr(i,j);
	  }
	sort(b+1,b+sz+1,cmp);
	bool v=1;
	for(int i=1,j=1;i<=sz;i=j+1) {
		while (j<sz&&num[b[i].FR][b[i].SE]==num[b[j+1].FR][b[j+1].SE]) j++;
		int sz2=0;
		for(int k=i;k<=j;k++) a[++sz2]=b[k];
		if (!solve(n,m,sz2,num[b[i].FR][b[i].SE])) {
			v=0;
			break;
		}
	} 
	if (!v) puts("NO");
	else {
		puts("YES");
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=m;j++) printf("%d ",ans1[i][j]);
			printf("\n");
		}
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=m;j++) printf("%c ",ans2[i][j]);
			printf("\n");
		}
	}
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值