Codeforces Round #571 (Div. 2) (C/思维题+E/矩形翻转+F/图论 or 贪心)

心得

B题不会结果据说4*4的有问题然后被删了……

E题是一个01矩阵找规律,

F题是一个欧拉图的构造之后删边,但可以用顶点的度贪心过

思路来源

https://www.cnblogs.com/hfctf0210/p/11104352.html

C. Vus the Cossack and Strings(思维题)

两个01串a和b,|b|<=|a|<=1e6,

求a的所有长度为b的子串中,和b对应位置不同的总个数为偶数的串c的数量

 

据说可以FFT做然而不知道怎么做,就是前缀和/尺取做就好

如果c和b的1的数量相同,则无论如何,二者的diff数量都为偶数

把1和1,0和0对应的去掉,剩下只要有一个1对应0 就有一个0对应1,必为偶数

所以,只需统计c和b的1的数量差即可,前缀和作差

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
char a[maxn],b[maxn];
int ans,sum[maxn];
int lena,lenb;
int cnta,cntb;
int main()
{
	scanf("%s%s",a+1,b+1);
	lena=strlen(a+1);
	lenb=strlen(b+1);
	for(int i=1;i<=lena;++i)
	sum[i]=sum[i-1]+(a[i]=='1');
	for(int i=1;i<=lenb;++i)
	cntb+=b[i]=='1';
	for(int i=lenb;i<=lena;++i)
	{
		cnta=sum[i]-sum[i-lenb];
		if((cnta-cntb)%2==0)ans++;
	}
	printf("%d\n",ans);
	return 0;
}

E. Vus the Cossack and a Field(01矩阵)

给你一个n*m(1<=n,m<=1e3)的01矩阵A,其左上角顶点为(1,1),右下角顶点为(n,m)

将矩阵A所有位取反,构成新矩阵记为B,

A右边放一个B,下面放一个B,右下放一个A,构成新矩阵

AB
BA

将这个矩阵定义为新矩阵C,然后重复这个过程,不断扩展,至铺满二维平面……

以下q(q<=1e5)个询问,每次询问四元组(x1,y1,x2,y2),代表两个点的坐标

要求询问左上角顶点为(x1,y1),右下角为(x2,y2)的矩形内1的个数,

 

考虑二维dp,这样变成了只需统计四元组(1,1,x,y)的答案的数量,

记|A|代表A所代表的矩阵的1的数量,

引理1:把A和B的矩阵对应位置相加,则所有位置均为1,

所以|A|+|B|=n*m,有其面积之和等于1的数量的二倍

那么把矩阵扩成2n*2m的矩阵,开dp[2*maxn][2*maxm]统计答案

 

 

对于(x,y)的询问,把面积分为四部分,

①是完整的2n*2m块,

对于②内每一块,每个块内n*1的竖条,其下面的n*1的竖条都是它的取反,

同理对于③内每一块,每个块内1*m的横块,其右边1*m的横块都是它的取反,

所以①、②、③都符合引理1,ll res=(1ll*x*y-1ll*(x%n)*(y%m))/2;

去判断④的面积,只需判断④所在矩阵是A还是B,

引理2:记bitcnt(x)为x的二进制表示下1的数量,则对于(x,y)所在的块号P(x/n,y/m)

若bitcnt(x/n)+bitcnt(y/m)为奇数,则(x,y)处于矩阵B中,否则(x,y)处于矩阵A中

证明考虑把A矩阵记做0,B矩阵记做1,先考虑一维的复制过程

①一个一个invert,0->01 ②两个两个invert,01->0110 ③四个四个invert,0110->01101001

这样看看不出来什么,但是如果把②中的01两位看作0,则②也是0->01的过程

那么,比如(x/n,y/m)为(7,7)的情形,由于7=二进制(111),

最高位1说明,在四个四个invert的过程中,7位于右侧即invert矩阵内,再invert一次之后与3相同

同理第二个1说明,两个两个invert的过程中,7位于右侧即invert矩阵内,再invert一次之后与1相同

而最低位的1说明,7处于原矩阵内,没有被invert,处于1的位置的是原矩阵

刚才是逆向考虑的,考虑从原矩阵invert到7的过程,invert了两次,

那么行和列这个过程是独立的,只需统计invert奇数次还是偶数次即可,

实际invert的次数是bitcnt(x/n)+bitcnt(y/m)-2,所以考虑二者之和的奇偶性即可

如果位于B矩阵,④的面积就是(x%n)*(y%m)-dp[x%n][y%m];

否则位于A矩阵,面积为dp[x%n][y%m]

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
const int N=1e3+10;
int n,m,q;
int dp[2*N][2*N];
char s[N];
int x1,x2,y1,y2;
ll cal(int x,int y) 
{
	ll res=(1ll*x*y-1ll*(x%n)*(y%m))/2;
	int pos=(x/n)^(y/m),num=0;
	for(;pos;pos>>=1)
	num+=(pos&1);
	if(num&1)res+=(x%n)*(y%m)-dp[x%n][y%m];
	else res+=dp[x%n][y%m];
	return res;
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;++i)
	{
		scanf("%s",s+1);
		for(int j=1;j<=m;++j)
		{
			dp[i][j]=(s[j]=='1');
			dp[i+n][j+m]=dp[i][j];
			dp[i+n][j]=dp[i][j+m]=dp[i][j]^1;
		}
	}
	n*=2;m*=2;
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		dp[i][j]+=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1];
	}
	while(q--)
	{
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		printf("%lld\n",cal(x2,y2)-cal(x1-1,y2)-cal(x2,y1-1)+cal(x1-1,y1-1));
	}
	return 0;
}

 

F. Vus the Cossack and a Graph(图论/贪心)

还是要习惯图论构图的数组写法,简单快捷

给一个n点m边(1<=n<=1e6,0<=m<=1e6)的简单图,第i个点的度为di

在保留不超过(n+m)/2条边(向上取整)的条件下,要求新的图中每个的点的度fi>=di/2(向上取整)

输出最后保留的边的条数,和每条边的u、v

题解1(贪心)

让每个点的最终的度fi为第二行左式,

考虑到di为偶数时分子+1向下取整不变,为奇数时分子+1相当于向上取整,会比直接除以2的值大1;

那么全为偶数就相当于+0,全为奇数相当于+n,故<=第二行中式

由于存在向下取整,显然<=第二行右式,

这样只需贪心地从小考虑当前度没有删到fi的点,把多余的边删掉即可

题解2(欧拉图 官方题解)

考虑欧拉图的充要条件,所有点都是偶度的节点即偶结点

这样由于有偶数个奇节点(总度数为偶数),把这些节点统统连向虚节点0构成一个菊花图,

则所有奇结点的度变为偶数,且0的度为偶数,是一个欧拉图

寻找欧拉回路,把这条路径上标号为偶数的边删掉,如果最后是奇数条边就把最后一条留下

这样连在同一个点上的边,每两条边才会删一条,所以剩下的度至少是原度的一半

 

再考虑删去虚节点0之后的情形,3号节点可能没有度,是因为3号和4号这两条边实际是不存在的

那就在删实际边2号边的过程中,考虑其欧拉回路序列的前驱1号边和3号边,

如果有虚边,则优先删虚边,即什么也不操作,保留2号边

特别地,在这个欧拉回路中,认为4号边的后继为1号边

两条实际边不会删同一条虚边,

例如2-3-4-5,其中2-3是实边,3-4是虚边,4-5是实边,则3和4都是实点,与虚边矛盾

代码还是看官方题解叭,感觉这题从这个方向考虑很秀啊

 

 

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
struct node{int id,d;};//(节点,度)
bool operator<(node a,node b){return a.d>b.d;}
int edge,ans;
int n,m;
int deg[N],now[N];
int U[N],V[N];
int v[N*2],nex[N*2],w[N*2],head[N],cnt;
bool del[N],vis[N];
priority_queue<node>q;
void add(int x,int y,int z)
{
	v[++cnt]=y;
	nex[cnt]=head[x];
	w[cnt]=z;
	head[x]=cnt;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i)
	{
		scanf("%d%d",&U[i],&V[i]);
		add(U[i],V[i],i);
		add(V[i],U[i],i);
		deg[U[i]]++;
		deg[V[i]]++;
	}
	for(int i=1;i<=n;++i)
	{
		now[i]=deg[i];
		q.push((node){i,now[i]});
	} 
	edge=m;
	while(!q.empty())
	{
		if(edge<=(n+m+1)/2)break;
		int x=q.top().id;
		q.pop();
		if(vis[x])continue;
		vis[x]=1;
		for(int i=head[x];i&&now[x]!=(deg[x]+1)/2;i=nex[i])
		{
			int y=v[i];
			if(!del[w[i]]&&now[y]!=(deg[y]+1)/2)
			{
				now[x]--;
				now[y]--;
				edge--;
				del[w[i]]=1;
				q.push((node){y,now[y]});
			}
		}
	}
	for(int i=1;i<=m;++i)
	if(!del[i])ans++;
	printf("%d\n",ans);
	for(int i=1;i<=m;++i)
	if(!del[i])printf("%d %d\n",U[i],V[i]);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值