CS R14 C(模拟+二维前缀和) ,D(好题,前缀第i位异或+滑动区间),E(树计数+DP(前缀和优化))

Round 14:
Problem C:
题意:n*m(01)矩阵,n,m<=1e3.求最大包含全1的子矩形并且其被0包围(外围一圈必须为0).
画几个样例 可以发现任意一个合法的全1子矩形都形成一个全'1'联通分量.
dfs搜出全1的联通分量 并记录左上和右下位置.不能落在边界.

在利用行/列前缀判断左上和右下一圈是否都为0,二维前缀和判断搜出来的子矩形是否全部为1即可.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e3+20,inf=0x3f3f3f3f;
const ll mod=1e9+7;
int n,m,vis[N][N],r[N][N],c[N][N],a[N][N],f[N][N]; 
int lef,rig,up,dow;
int dx[]={-1,1,0,0};
int dy[]={0,0,-1,1};
void dfs(int x,int y)
{
	vis[x][y]=1;
	rig=max(rig,y),lef=min(lef,y);
	up=min(up,x),dow=max(dow,x);
	for(int i=0;i<4;i++)
	{
		int c=x+dx[i],d=y+dy[i];
		if(c>=1&&c<=n&&d>=1&&d<=m&& !vis[c][d] &&a[c][d]==1)
			dfs(c,d);	
	}
}
bool check()
{
	int x1=up,y1=lef,x2=dow,y2=rig;
	bool flag=true;
	//cout<<x1<<' '<<y1<<' '<<x2<<' '<<y2<<endl;
	int num=(rig-lef+1)*(dow-up+1);
	int act=f[x2][y2]-f[x2][y1-1]-f[x1-1][y2]+f[x1-1][y1-1];
	if(num!=act)
		flag=false;
	//cout<<num<<' '<<act<<endl;
	x1--,y1--,x2++,y2++;
	int r0=y2-y1+1,c0=x2-x1+1;
	int r1=r[x1][y2]-r[x1][y1-1],r2=r[x2][y2]-r[x2][y1-1];
	int c1=c[y1][x2]-c[y1][x1-1],c2=c[y2][x2]-c[y2][x1-1];
	//cout<<r0<<' '<<c0<<endl;
	//cout<<r1<<' '<<r2<<endl;
	//cout<<c1<<' '<<c2<<endl;
	if(r1!=r0||r2!=r0)
		flag=false;
	if(c1!=c0||c2!=c0)
		flag=false;
	if(x1<=0||x2>n||y1<=0||y2>m)
		flag=false;
	return flag;
}
int main()
{ 
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d",&a[i][j]),r[i][j]=r[i][j-1]+(a[i][j]==0);
	for(int j=1;j<=m;j++)
		for(int i=1;i<=n;i++)
			c[j][i]=c[j][i-1]+(a[i][j]==0);
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			f[i][j]=a[i][j]+f[i][j-1]+f[i-1][j]-f[i-1][j-1];		
	int ans=-1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			if(a[i][j]==1&& !vis[i][j])
			{
				lef=up=inf;
				rig=dow=0;
				dfs(i,j);
				if(check())
					ans=max(ans,(rig-lef+1)*(dow-up+1));
			}
		}
	cout<<ans<<endl;
	return 0;
} 
Problem D
题意:给出长度为n的序列c,求所有区间长度为[a,b]之间的异或和之和,n<=1e5,c[i]<=1e9.

异或和的累加和 -> 一种套路,就是将按二进制每位的贡献来算累加和.
也就是说,算有多少个区间其异或和的第b位为1,则第b位总贡献为num * (1<<b).

固定当前位b以后,处理出前缀第b位异或的值.
现在枚举右端点j,则左端点在[j-B+1,j-A+1]间.
若前缀j第b位异或为0 则需要知道前缀[j-B,j-A]之间第b位异或为1的个数,用cnt[0/1]来记录这段区间即可.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+20;
const ll mod=1e9+7; 
ll n,c[N],a,b;
ll pre[N];
int main()
{ 
	cin>>n>>a>>b;
	for(int i=1;i<=n;i++)
		scanf("%lld",&c[i]);
	ll ans=0;
	pre[0]=0;
	for(int bit=0;bit<=30;bit++)
	{
		int cnt[2]={0,0};
		for(int i=1;i<=n;i++)
		{
			pre[i]=pre[i-1]^((c[i]>>bit)&1);
			if(i-b-1>=0)
				cnt[pre[i-b-1]]--;//del old time
			if(i-a>=0)
				cnt[pre[i-a]]++;//add new
			ans=(ans+cnt[pre[i]^1]*(1ll<<bit))%mod;
		}
	}
	cout<<ans<<endl;
	return 0;
} 
Problem E:
题意:有多少个n个结点的orederd树,其直径<=m? n,m<=300.
ordered tree:结点从根开始从左到右编号,若两棵树存在边,其端点编号不同,则这两个树就算是不同的.


直径要<=m 则根结点连接的两个子树其高度相加在加1要<=m.
令dp[i][j]为 i个结点,高度为j的order tree个数,(转移时要注意其直径要<=m).
因为tree是ordered.所以按照根第一次连接的子树其高度来转移状态.

dp[i][j]
根只有一个子树,dp[i][j]+=dp[i-1][j-1].
根有若干个子树:
第一个子树的高度为j-1,枚举其大小为k,则dp[i][j]+=dp[k][j-1]*dp[i-k][x] (x=1...min(j,m-j)).
第一个子树高度为j',大小为k,则剩余部分高度要为j,dp[i][j]+=dp[k][j']*dp[i-k][j] (j'=1..min(j-2,m-j-1))
直接计算时间复杂度为O(n^4) 

因为dp时j'和x都是从某个值累加到某个值,想到用前缀和sum[i][j]=dp[i][1..j]来优化一下就能得到O(n^3).

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e2+20;
int n,m;
ll mod,dp[N][N],sum[N][N];//sum[i][j]= dp[i][1]+dp[i][2]+..dp[i][j]. 
int main()
{ 
	cin>>n>>m>>mod;
	dp[1][0]=1;
	for(int j=0;j<=m;j++)
		sum[1][j]=1;
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{	
			dp[i][j]=(dp[i][j]+dp[i-1][j-1])%mod;
			for(int k=1;k<=i-2;k++)
			{
				//dp[i][j]+=dp[k][j-1]*dp[i-k][x],x=[1..min(j,m-j)].
				int x=min(j,m-j);
				dp[i][j]=(dp[i][j]+(dp[k][j-1]*sum[i-k][x])%mod)%mod;
		
				//dp[i][j]+=dp[k][x]*dp[i-k][j] , x=[1..min(j-2,m-j-1)]
				x=min(j-2,m-j-1);
				dp[i][j]=(dp[i][j]+(dp[i-k][j]*sum[k][x])%mod)%mod;
			}	
			sum[i][j]=(sum[i][j-1]+dp[i][j])%mod;
		}	
	} 
	cout<<sum[n][m]<<endl;
	return 0;
} 





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值