Codeforces Round #571 (Div. 2)题解

Codeforces Round #571 (Div. 2)

话说等了差不多一年,我终于有了一点时间上CF。。。然而。。。
本图引自CF讨论区

A. Vus the Cossack and a Contest

◆题目传送门◇

题目大意

N N N个人,两种奖品,每种奖品分别有 A A A个, B B B个。问每个人能否都能得到两种奖品,且每种奖品至少有一个。

分析

emmm…简单的if 语句的运用,直接写就是了。

参考代码

#include<cstdio>
#include<algorithm>
using namespace std;
 
int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	int n,a,b;
	scanf("%d %d %d",&n,&a,&b);
	if(a<n||b<n)
		puts("No");
	else puts("Yes");
	return 0;
}

B.Vus the Cossack and a Game

(此题已被删除)

题目大意

给定一个 N , M N,M N,M表示一个 N × M N\times M N×M的方格,要求放上一些 1 × 2 1\times2 1×2的方块,使得每个方块周围一圈没有其他方块,且每个方块的每个角不相对。

举个例子:不合法的状态:

合法的状态:


问在这个 N × M N\times M N×M的方格上可以放多少个 1 × 2 1\times2 1×2的方块。

分析

这道题是个结论题,答案就是 ⌊ ( N + 1 ) ( M + 1 ) 6 ⌋ \lfloor\frac{(N+1)(M+1)}{6}\rfloor 6(N+1)(M+1),具体证明点这里

参考代码

我觉得没这必要吧

C. Vus the Cossack and Strings

◆题目传送门◇

题目大意

定义 f ( b , c ) f(b,c) f(b,c)为两串不相同字符的数量。给定两个01串 s 1 , s 2 s_1,s_2 s1,s2,保证 ∣ s 2 ∣ ≤ ∣ s 1 ∣ |s_2|\le|s_1| s2s1,将 s 1 s_1 s1中长度为 ∣ s 2 ∣ |s_2| s2的子串取出,与 s 2 s_2 s2相比较并求得此时的 f f f,问总共有多少个 f f f值是偶数。

分析

我们显然不能 O ( N 2 ) O(N^2) O(N2)暴力比较。

由于这是一个01串,所以我们考虑在 s 2 s_2 s2向右移动一位后,它对应位置上的变化情况。

由于这是一个01串,所以只要 s 1 s_1 s1的第 i i i个位置和 s 2 s_2 s2的第 j j j个位置相同, s 1 s_1 s1的第 i i i个位置和第 i + 1 i+1 i+1个位置不同,那么 s 2 s_2 s2向右平移后, j j j位置对上了 s 1 s_1 s1 i + 1 i+1 i+1位置,显然我们可以发现这个函数值由于这个位置的变化应加上1。

所以我们只需预处理出 s 1 s_1 s1中不相同的位置的个数前缀和,并比较即可。

参考代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
 
const int Maxn=1e6;
 
char a[Maxn+5],b[Maxn+5];
int s[Maxn+5];
 
int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	scanf("%s\n%s",a+1,b+1);
	int lena=strlen(a+1),lenb=strlen(b+1);
	for(int i=1;i<lena;i++)
		s[i]=s[i-1]+(a[i]!=a[i+1]);
	int t=0,ans=0;
	for(int i=1;i<=lenb;i++)
		if(a[i]!=b[i])
			t++;
	if(t%2==0)
		ans++;
	for(int i=0;i<lena-lenb;i++) {
		if((s[i+lenb]-s[i])%2==1)
			t++;
		if(t%2==0)ans++;
	}
	printf("%d\n",ans);
	return 0;
}

D. Vus the Cossack and Numbers

◆题目传送门◇

题目大意

给定有 N N N个数的实数,保证它们的和为 0 0 0,现要求将这些数向上或向下取整,要求取整后和仍然为 0 0 0,输出任意一种方案。

分析

我们可以把所有数向0取整(利用doubleint的特性)。

然后求一遍和,这个和有三种情况:

  1. 0 0 0,直接输出即可;
  2. 大于 0 0 0,我们选出这么多的数,并将它们全部减去 1 1 1即可;
  3. 小于 0 0 0,做法同大于 0 0 0的情况。

也可以用floor函数全部向下取整,这样所有的和肯定是小于等于 0 0 0,那么只有一种情况出现。

参考代码

#include<map>
#include<cstdio>
#include<algorithm>
using namespace std;
 
const int Maxn=1e5;
 
int N;
double A[Maxn+5];
int t[Maxn+5];
 
double Abs(double x) {
	if(x<0)return -x;
	return x;
}
 
int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	scanf("%d",&N);
	int sum=0;
	for(int i=1;i<=N;i++) {
		scanf("%lf",&A[i]);
		t[i]=A[i];
		sum+=t[i];
	}
	if(sum>0) {
		for(int i=1;i<=N;i++) {
			if(Abs(1.0*t[i]-1-A[i])<1.0)
				t[i]--,sum--;
			if(sum==0)break;
		}
	} else if(sum<0) {
		for(int i=1;i<=N;i++) {
			if(Abs(1.0*t[i]+1-A[i])<1.0)
				t[i]++,sum++;
			if(sum==0)break;
		}
	}
	for(int i=1;i<=N;i++)
		printf("%d\n",t[i]);
	return 0;
}

E. Vus the Cossack and a Field

◆题目传送门◇

题目大意

给定一个 N × M N\times M N×M初始01矩阵,按照如下步骤变换:

  1. 将这个矩阵取反;
  2. 将取反后矩阵摆到原矩阵的右方和下方;
  3. 将原矩阵复制一个放在原矩阵的右下方;
  4. 重复这个操作无限次。

再给出 q q q个询问,每个询问表示以 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)为左上角, ( x 2 , y 2 ) (x_2,y_2) (x2,y2)为右下角的矩形中 1 1 1的个数。

分析

这不是分治题,这是规律题!!!

我们记 f ( x , y ) f(x,y) f(x,y)为以 ( 1 , 1 ) (1,1) (1,1)为左上角, ( x , y ) (x,y) (x,y)为右下角的矩阵的 1 1 1的个数。

则原问题可转化为求 f ( x 2 , y 2 ) − f ( x 1 − 1 , y 2 ) − f ( x 2 , y 1 − 1 ) + f ( x 1 − 1 , y 1 − 1 ) f(x_2,y_2)-f(x_1-1,y_2)-f(x_2,y_1-1)+f(x_1-1,y_1-1) f(x2,y2)f(x11,y2)f(x2,y11)+f(x11,y11)

的值,所以我们只需设计出 f ( x , y ) f(x,y) f(x,y)的求法即可。

我们先考虑原始矩阵只有一个元素的情况。

当未做任何变换时: 1 1 1

做了一次变换: 1   0 0   1 1\ 0\\0\ 1 1 00 1

做了两次变换: 1   0   0   1 0   1   1   0 0   1   1   0 1   0   0   1 1\ 0\ 0\ 1\\0\ 1\ 1\ 0\\0\ 1\ 1\ 0\\1\ 0\ 0\ 1 1 0 0 10 1 1 00 1 1 01 0 0 1

做三次: 1   0   0   1   0   1   1   0 0   1   1   0   1   0   0   1 0   1   1   0   1   0   0   1 1   0   0   1   0   1   1   0 0   1   1   0   1   0   0   1 1   0   0   1   0   1   1   0 1   0   0   1   0   1   1   0 0   1   1   0   1   0   0   1 1\ 0\ 0\ 1\ 0\ 1\ 1\ 0\\0\ 1\ 1\ 0\ 1\ 0\ 0\ 1\\0\ 1\ 1\ 0\ 1\ 0\ 0\ 1\\1\ 0\ 0\ 1\ 0\ 1\ 1\ 0\\0\ 1\ 1\ 0\ 1\ 0\ 0\ 1\\1\ 0\ 0\ 1\ 0\ 1\ 1\ 0\\1\ 0 \ 0\ 1\ 0\ 1\ 1\ 0\\0\ 1\ 1\ 0\ 1\ 0\ 0\ 1 1 0 0 1 0 1 1 00 1 1 0 1 0 0 10 1 1 0 1 0 0 11 0 0 1 0 1 1 00 1 1 0 1 0 0 11 0 0 1 0 1 1 01 0 0 1 0 1 1 00 1 1 0 1 0 0 1

不难发现,相邻的奇数行和偶数行、奇数列与偶数列,都能抵消为1个 1 1 1

那么扩展到 N × M N\times M N×M的方格呢?

很显然由于取反,矩阵中原来为 1 1 1的位置变成了 0 0 0,原来为 0 0 0的地方变成了 1 1 1,所以这样相邻的矩阵仍能抵消,只是这是 N × M N\times M N×M 1 1 1了。

对于不满 N × M N\times M N×M的矩阵,只要残余部分是相同的,仍有这种规律。

说了这么多,接下来进入正题:求 f ( x , y ) f(x,y) f(x,y)

仍以 1 1 1代替 N × M N\times M N×M的矩阵。

我们定义 f x = ⌊ x − 1 N ⌋ + 1 , f y = ⌊ y − 1 M ⌋ + 1 f_x=\lfloor\frac{x-1}{N}\rfloor+1,f_y=\lfloor\frac{y-1}{M}\rfloor+1 fx=Nx1+1,fy=My1+1

所以结论就分为四种情况讨论:

  1. f x , f y f_x,f_y fx,fy均为偶数;
  2. f x , f y f_x,f_y fx,fy均为奇数;
  3. f x f_x fx为偶数, f y f_y fy为奇数;
  4. f x f_x fx为奇数, f y f_y fy为偶数。

这四种情况的讨论是差不多的,所以接下来我们只以第一种情况为例。

如下图:

黄色的点是我们得到的 ( x , y ) (x,y) (x,y)

我们很容易推知红色部分答案为 ( ( f x − 1 ) ( f y − 1 ) − 1 ) N M 2 \frac{((f_x-1)(f_y-1)-1)NM}{2} 2((fx1)(fy1)1)NM

同理可知紫色,褐色,蓝色部分的答案,将它们加起来就可以了。

那么如何判断一个点所在的位置是否是取反的矩阵?

我们定义 B i t C o u n t ( x ) BitCount(x) BitCount(x) x x x二进制表示中的 1 1 1的个数,那么当 B i t C o u n t ( b ) + B i t C o u n t ( a ) BitCount(b)+BitCount(a) BitCount(b)+BitCount(a) ( a , b ) (a,b) (a,b)为该矩阵在整个矩阵中的坐标)是奇数时,则该矩阵是取反的。

我也不知道怎么证这玩意。。。

参考代码

#include<cstdio>
#include<algorithm>
using namespace std;
 
typedef long long ll;
const int Maxn=1000;
 
int N,M;
char s[Maxn+5][Maxn+5];
ll f[Maxn+5][Maxn+5];
 
ll Normal(ll x,ll y) {return f[x][y];}
ll Inverse(ll x,ll y) {return (x+1)*(y+1)-f[x][y];}
 
int BitCount(ll x) {
	int ret=0;
	while(x)
		ret++,x-=(x&(-x));
	return ret;
}
 
ll Solve(ll x,ll y) {
	if(x<0||y<0)return 0;
	ll fx=x/N+1,fy=y/M+1;
	ll ret=0;
	if(fx%2==0&&fy%2==0) {
		ret+=(((fx-1)*(fy-1)-1)/2*N*M);
		x%=N,y%=M;
		ret+=((fx-2)/2*(y+1)*N);
		ret+=((fy-2)/2*(x+1)*M);
		if((BitCount(fx-2)+BitCount(fy-2))%2)
			ret+=Inverse(N-1,M-1);
		else ret+=Normal(N-1,M-1);
		if((BitCount(fx-1)+BitCount(fy-1))%2)
			ret+=Inverse(x,y)+Normal(x,M-1)+Normal(N-1,y);
		else ret+=Normal(x,y)+Inverse(x,M-1)+Inverse(N-1,y);
	} else if(fx%2&&fy%2) {
		ret+=((fx-1)/2*(fy-1)*N*M);
		x%=N,y%=M;
		ret+=((fx-1)/2*(y+1)*N);
		ret+=((fy-1)/2*(x+1)*M);
		if((BitCount(fx-1)+BitCount(fy-1))%2)
			ret+=Inverse(x,y);
		else ret+=Normal(x,y);
	} else if(fx%2==0&&fy%2) {
		ret+=((fx-1)*(fy-1)/2*N*M);
		x%=N,y%=M;
		ret+=((fx-2)/2*(y+1)*N);
		ret+=((fy-1)/2*(x+1)*M);
		if((BitCount(fx-1)+BitCount(fy-1))%2)
			ret+=Inverse(x,y)+Normal(N-1,y);
		else ret+=Normal(x,y)+Inverse(N-1,y);
	} else if(fx%2&&fy%2==0) {
		ret+=((fx-1)/2*(fy-1)*N*M);
		x%=N,y%=M;
		ret+=((fx-1)/2*(y+1)*N);
		ret+=((fy-2)/2*(x+1)*M);
		if((BitCount(fx-1)+BitCount(fy-1))%2)
			ret+=Inverse(x,y)+Normal(x,M-1);
		else ret+=Normal(x,y)+Inverse(x,M-1);
	}
	return ret;
}
 
int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	int q;
	scanf("%d %d %d",&N,&M,&q);
	for(int i=0;i<N;i++) {
		scanf("%s",s[i]);
		for(int j=0;j<M;j++)
			f[i][j]=s[i][j]-'0';
	}
	for(int i=1;i<N;i++)
		f[i][0]+=f[i-1][0];
	for(int j=1;j<M;j++)
		f[0][j]+=f[0][j-1];
	for(int i=1;i<N;i++)
		for(int j=1;j<M;j++)
			f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
	while(q--) {
		ll x1,y1,x2,y2;
		scanf("%lld %lld %lld %lld",&x1,&y1,&x2,&y2);
		x1--,x2--,y1--,y2--;
		ll ans=Solve(x2,y2)-Solve(x1-1,y2)-Solve(x2,y1-1)+Solve(x1-1,y1-1);
		printf("%lld\n",ans);
	}
	return 0;
}

F. Vus the Cossack and a Graph

这题STL要爆???惊了(好吧我就这样炸了5次。。。)

◆题目传送门◇

题目大意

给定一个有 N N N个节点和 M M M条边的图,记第 i i i个点的度为 d i d_i di。现要求删除一些边,使得剩余边的数量不大于 ⌈ N + M 2 ⌉ \lceil\frac{N+M}{2}\rceil 2N+M,且第 i i i个点删后的度数不小于 ⌈ d i 2 ⌉ \lceil\frac{d_i}{2}\rceil 2di

分析

我们创建一个虚拟节点即 0 0 0号节点,并把所有度数为奇数的点全部和它相连。很容易通过所有顶点的度数之和等于边数来证明 0 0 0号节点的度数是一个偶数。

由于最多只有不超过 N N N个点的度数为奇数,所以,我们显然可以证明,新的边数 k ≤ N + M k\le N+M kN+M

因为所有的点的度数为偶数,所以这个图存在欧拉回路。考虑做欧拉回路,记按欧拉回路次序找到的边为 e 1 , e 2 , e 3 , … , e k e_1,e_2,e_3,\ldots,e_k e1,e2,e3,,ek,接下来我们删掉编号为偶数的边,这样我们所得到的就只有 ⌈ k 2 ⌉ \lceil\frac{k}{2}\rceil 2k条了。易证到 ⌈ k 2 ⌉ ≤ ⌈ N + M 2 ⌉ \lceil\frac{k}{2}\rceil\le\lceil\frac{N+M}{2}\rceil 2k2N+M

我们应按照先删虚边,再删实边的方法来删边:

  • 若该边是虚边,则直接删掉;
  • 若该边是实边,由于我们只把奇数度数的点连了虚边,所以我们又分为两种情况讨论:
    - 若该边连着一个度数为奇数的点,我们可以发现它的两边有至少一条虚边,我们将这条虚边删去即可;
    - 若该边连着的两个点均为度数为偶数的点,那么我们只能强行删掉这条边了(这条边的两边都不是虚边)。

我们将所有未被删的实边输出即可。

注意由于题目未保证所有点形成一个连通块,所以我们必须对每个点做一次欧拉回路。

参考代码

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
 
const int Maxn=1e6;
 
int N,M,totm;
int d[Maxn+5];
 
struct Node {
	int to;
	int id;
};
vector<Node> G[Maxn+5];
struct Edge {
	int u,v;
	int typ;
}e[Maxn*2+5];
void addedge(int u,int v,int typ) {
	G[u].push_back(Node{v,++totm});
	G[v].push_back(Node{u,totm});
	e[totm]=Edge{u,v,typ};
}
 
bool vis[Maxn*2+5],del[Maxn*2+5];
int tmp[Maxn*2+5],cnt;
int S[Maxn+5];
int stk[Maxn*2+5],ed[Maxn*2+5],top;
void EulerPath(int s) {
	cnt=0,stk[++top]=s;
	while(top>0) {
		int u=stk[top],i;
		for(i=S[u];i<G[u].size();i++)
			if(!vis[G[u][i].id])
				break;
		if(i<G[u].size()) {
			stk[++top]=G[u][i].to;
			ed[top]=G[u][i].id;
			vis[G[u][i].id]=true;
			S[u]=i+1;
		} else tmp[++cnt]=ed[top--];
	}
}
 
int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	scanf("%d %d",&N,&M);
	for(int i=1;i<=M;i++) {
		int u,v;
		scanf("%d %d",&u,&v);
		addedge(u,v,1);
		d[u]++,d[v]++;
	}
	for(int i=1;i<=N;i++)
		if(d[i]%2)
			addedge(0,i,0);
	int num=M;
	for(int i=0;i<=N;i++) {
		EulerPath(i);
		for(int j=2;j<=cnt;j+=2) {
			int id=tmp[j];
			if(e[id].typ==0)
				del[id]=true;
			else {
				int t=tmp[j-1];
				if(e[t].typ==0&&del[t]==false) {
					del[t]=true;
					continue;
				}
				t=tmp[j%cnt+1];
				if(e[t].typ==0&&del[t]==false) {
					del[t]=true;
					continue;
				}
				del[id]=true;
				num--;
			}
		}
	}
	printf("%d\n",num);
	for(int i=1;i<=totm;i++)
		if(del[i]==false&&e[i].typ==1)
			printf("%d %d\n",e[i].u,e[i].v);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值