2019.07.11【NOIP提高组】模拟 B 组

洛谷 3084 照片 Photo

题目

在长度为 n n n的线段中给出若干个区间,询问是否存在一种情况使最多的点在每个区间中有且仅出现一次


分析

d p [ i ] dp[i] dp[i]表示前 i i i个点第 i i i个点必选所能产生的最多的点,那么显然在后面要开一个虚拟点,因为最后一个点不必选
那么记录该点所能被推导的最小区间,用单调队列维护dp,时间复杂度 O ( n ) O(n) O(n)


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
const int N=200101;
int n,l[N],r[N],f[N],q[N];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
signed main(){
	n=iut();
	for (rr int i=1;i<=n+1;++i) r[i]=i-1;
	for (rr int m=iut();m;--m){
		rr int x=iut(),y=iut();
		r[y]=min(r[y],x-1);
		l[y+1]=max(l[y+1],x);
	}
	for (rr int i=n;i;--i) r[i]=min(r[i],r[i+1]);
	for (rr int i=1;i<=n+1;++i) l[i]=max(l[i],l[i-1]);
	rr int head=1,tail=1; q[1]=0;
	for (rr int i=1,j=1;i<=n+1;++i){
		for (;j<=r[i]&&j<=n;++j) if (~f[j]){
			while (head<=tail&&f[q[tail]]<f[j]) --tail;
			q[++tail]=j; 
		}
		while (head<=tail&&q[head]<l[i]) ++head;
		f[i]=(head<=tail)?(f[q[head]]+(i!=n+1)):-1;
	}
	return !printf("%d\n",f[n+1]);
}

洛谷 3085 阴和阳 Yin and Yang

题目

给出一棵 n n n个点的树,每条边为黑色或白色。问满足以下条件的路径条数:路径上存在一个不是端点的点,使得两端点到该点的路径上两种颜色的边数都相等。


分析

把黑色看为1,把白色看为-1,那么显然路径和为0,可以列出一个dp方程
f / g [ n o w ] [ 0 / 1 ] f/g[now][0/1] f/g[now][0/1]表示以当前节点为重心,路径和为 n o w now now,且以前/现在(g/f)遍历过,找不到/找得到端点的方案
dp方程显然,点分治求解,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
详见代码


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
const int N=100011; bool v[N]; struct node{int y,w,next;}e[N<<1]; long long ans;
int big[N],son[N],d[N<<1],mson,root,mxdep,n,m,ls[N],tot,k=1,f[N<<1][2],g[N<<1][2];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans; 
}
inline void dfs(int x,int fa){
	son[x]=1,big[x]=0;
	for (rr int i=ls[x];i;i=e[i].next)
	if (e[i].y!=fa&&!v[e[i].y]){
		dfs(e[i].y,x);
		son[x]+=son[e[i].y];
		big[x]=max(big[x],son[e[i].y]);
	}
	big[x]=max(big[x],mson-son[x]);
	if (big[x]<big[root]) root=x;
}
inline void getd(int x,int fa,int now,int dep){
	mxdep=max(mxdep,dep);
	d[now]?++f[now][1]:++f[now][0];
	++d[now];
	for (rr int i=ls[x];i;i=e[i].next)
	if (e[i].y!=fa&&!v[e[i].y])
	    getd(e[i].y,x,now+e[i].w,dep+1);
	--d[now];
}
inline void calc(int x){
	rr int mx=0; g[n][0]=1;
	for (rr int i=ls[x];i;i=e[i].next)
	if (!v[e[i].y]){
		mxdep=1; getd(e[i].y,x,n+e[i].w,1);
		mx=mx>mxdep?mx:mxdep;
		ans+=f[n][0]*(g[n][0]-1);//它自己显然不能算
		for (rr int j=-mxdep;j<=mxdep;++j)
		    ans+=f[n+j][1]*g[n-j][1]+f[n+j][0]*g[n-j][1]+f[n+j][1]*g[n-j][0];//至少要又一个找到中转点
		for (rr int j=n-mxdep;j<=n+mxdep;++j)
			g[j][0]+=f[j][0],g[j][1]+=f[j][1],f[j][0]=f[j][1]=0;
	}
	for (rr int i=n-mx;i<=n+mx;++i)
	    g[i][0]=g[i][1]=0; 
}
inline void dp(int x){
	v[x]=1,calc(x); 
	for (rr int i=ls[x];i;i=e[i].next)
	if (!v[e[i].y]){
		big[0]=mson=son[e[i].y];
		dfs(e[i].y,root=0),dp(root);
	}
}
signed main(){
	n=iut();
	for (rr int i=1;i<n;++i){
		rr int x=iut(),y=iut(),w=(iut()<<1)-1;
		e[++k]=(node){y,w,ls[x]},ls[x]=k;
		e[++k]=(node){x,w,ls[y]},ls[y]=k;
	}
	big[0]=mson=n,dfs(1,0),dp(root);
	return !printf("%lld",ans);
}

洛谷 3086 数字8 Figure Eight

题目

问在一个方阵中有多少个数字8,结果输出8上面所组成矩形的面积*8下面所组成的矩形的面积的总和


分析

原理很简单,设 d p [ k ] [ i ] [ j ] dp[k][i][j] dp[k][i][j]表示第 k k k行为矩形底部,以 i , j i,j i,j为左右边界所能组成的最大矩形的面积,先用 O ( n 3 ) O(n^3) O(n3)求出,再往两边延伸,那么最后以同样的方式暴力,时间复杂度 O ( n 3 ) O(n^3) O(n3)


代码

#include <cstdio>
#define rr register
using namespace std;
const int N=301; bool s[N][N];
int sum[N][N],dp[N][N][N],n,ans;
inline signed max(int a,int b){return a>b?a:b;}
signed main(){
	scanf("%d",&n);
	for (rr int i=1;i<=n;++i)
	for (rr int j=1;j<=n;++j){
		rr char c=getchar();
		while (c!='.'&&c!='*') c=getchar();
		sum[i][j]=sum[i][j-1]+(s[i][j]=(c=='*')); 
	}
	for (rr int i=1;i<n-1;++i)
	for (rr int j=i+2,p=0;j<=n;++j,p=0)
	for (rr int k=1;k<=n;++k){
		if (s[k][i]||s[k][j]) p=0;
		if (sum[k][i-1]==sum[k][j])
		    !p?p=k:dp[k][i][j]=(k-p-1)*(j-i-1);
	}
	for (rr int k=1;k<=n;++k){
		for (rr int j=n;j>3;--j)
		for (rr int i=j-3;~i;--i)
		if (sum[k][i-1]==sum[k][j])
		    dp[k][i][j]=max(dp[k][i][j],dp[k][i+1][j]);
		for (rr int i=1;i<n-1;++i)
		for (rr int j=i+3;j<=n;++j)
		if (sum[k][i-1]==sum[k][j])
		    dp[k][i][j]=max(dp[k][i][j],dp[k][i][j-1]); 
	}
	for (rr int i=1;i<n-1;++i)
	for (rr int j=i+2,p=0;j<=n;++j,p=0)
	for (rr int k=n;k;--k){
		if (s[k][i]||s[k][j]) p=0;
		if (sum[k][i-1]==sum[k][j])
		    !p?p=k:ans=max(ans,dp[k][i][j]*(p-k-1)*(j-i-1));
	}
	if (!ans) printf("%d",-1);
	    else printf("%d",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值