2019.12.07【NOIP提高组】模拟A 组

47 篇文章 0 订阅
31 篇文章 0 订阅

JZOJ 3918 蛋糕

题目

把一个矩阵横切三刀,竖切三刀,问当中的子矩阵总和最小的最大能是多少


分析

首先相当暴力的方法就是暴力切的位置然后用前缀和,时间复杂度应该是 O ( n 6 ) O(n^6) O(n6),但是这个东西是二分的套路,考虑二分答案,首先竖切三刀,然后 O ( n ) O(n) O(n)判断,只要能分成4段或以上即为合法


代码

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
int n,m,ans,a[101][101],tot;
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 bool check(int j1,int j2,int j3,int k){
	rr int s1=0,s2=0,s3=0,s4=0,kuai=0;
	for (rr int i=1;i<=n;++i){
		s1+=a[i][j1],s2+=a[i][j2]-a[i][j1],s3+=a[i][j3]-a[i][j2],s4+=a[i][m]-a[i][j3];
		if (s1>=k&&s2>=k&&s3>=k&&s4>=k) s1=s2=s3=s4=0,++kuai;
	}
	return kuai>=4;
}
signed main(){
	n=iut(); m=iut();
	for (rr int i=1;i<=n;++i)
	for (rr int j=1;j<=m;++j){
	    rr char c=getchar();
	    while (!isdigit(c)) c=getchar(); 
		a[i][j]=a[i][j-1]+(c^48);
	}
	for (rr int i=1;i<=n;++i) tot+=a[i][m];
	for (rr int j1=1;j1<m-2;++j1)
	for (rr int j2=j1+1;j2<m-1;++j2)
	for (rr int j3=j2+1;j3<m;++j3){
	    rr int l=1,r=tot>>4;
		while (l<r){
	    	rr int mid=(l+r+1)>>1;
	    	if (check(j1,j2,j3,mid)) l=mid;
	    	    else r=mid-1;
		}
		ans=ans>l?ans:l;
	} 
	return !printf("%d",ans);
}

BZOJ 3743 JZOJ 3919 志愿者

题目

一颗树 n n n个点, n − 1 n-1 n1条边,经过每条边都要花费一定的时间,任意两个点都是联通的。
K K K个人(分布在 K K K个不同的点)要集中到一个点举行聚会。
聚会结束后需要一辆车从举行聚会的这点出发,把这 K K K个人分别送回去。
请你回答,对于 i = 1 ∼ n i=1\sim n i=1n,如果在第i个点举行聚会,司机最少需要多少时间把 K K K个人都送回家。


分析

那我们会发现,司机花费的时间其实就是 2 * (【第i个点 + K个点组成的点集】生成的最小的树的边权和) - K个点中离第i个点最远的距离 即可。
那么分别处理,换根O(n)解决


代码

#include <cstdio>
#include <cctype>
#define rr register
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
const int N=500011;
struct node{int y,w,next;}e[N<<1];
typedef long long lll; lll dis[N],dp[N][3];
int m,s[N],n,ls[N],is[N],k=1;
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 print(lll ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
inline void dfs1(int x,int fa){
	s[x]=is[x],dis[x]=0;
	for (rr int i=ls[x];i;i=e[i].next)
	if (e[i].y!=fa){
		dfs1(e[i].y,x),s[x]+=s[e[i].y];
		dis[x]+=dis[e[i].y]+(s[e[i].y]?e[i].w:0);
	}
}
inline void dfs2(int x,int fa){
	for (rr int i=ls[x];i;i=e[i].next)
	if (e[i].y!=fa){
		dis[e[i].y]=dis[x];
		if (s[e[i].y]) dis[e[i].y]-=e[i].w;
		if (s[e[i].y]<m) dis[e[i].y]+=e[i].w;
		dfs2(e[i].y,x);
	}
}
inline void dfs3(int x,int fa){
	dp[x][0]=dp[x][1]=(is[x]?0:-1e18);
	for (rr int i=ls[x];i;i=e[i].next)
	if (e[i].y!=fa){
		dfs3(e[i].y,x);
		if (dp[e[i].y][0]+e[i].w>dp[x][0])
			dp[x][1]=dp[x][0],dp[x][0]=dp[e[i].y][0]+e[i].w;
		else if (dp[e[i].y][0]+e[i].w>dp[x][1]) dp[x][1]=dp[e[i].y][0]+e[i].w;
	}
}
inline void dfs4(int x,int fa){
	for (rr int i=ls[x];i;i=e[i].next)
	if (e[i].y!=fa){
		if (dp[e[i].y][0]+e[i].w==dp[x][0]) dp[e[i].y][2]=dp[x][1]+e[i].w;
		    else dp[e[i].y][2]=dp[x][0]+e[i].w;
		dp[e[i].y][2]=max(dp[e[i].y][2],dp[x][2]+e[i].w); 
		dfs4(e[i].y,x);
	}
}
signed main(){
	n=iut(),m=iut();
	for (rr int i=1;i<n;++i){
		rr int x=iut(),y=iut(),w=iut();
		e[++k]=(node){y,w,ls[x]},ls[x]=k,
		e[++k]=(node){x,w,ls[y]},ls[y]=k;
    }
    for (rr int i=1;i<=m;++i) is[iut()]=1;
	dfs1(1,0),dfs2(1,0),dfs3(1,0),
    dp[1][2]=is[1]?0:-1e18,dfs4(1,0);
	for (rr int i=1;i<=n;++i,putchar(10))
	    print((dis[i]<<1)-max(dp[i][0],dp[i][2]));
	return 0;
} 

JZOJ 3920 噪音

题目

M M M个牛棚,编号 1 ∼ M 1\sim M 1M,刚开始所有牛棚都是空的。有 N N N头牛,编号 1 ∼ N 1\sim N 1N,这 N N N头牛按照编号从小到大依次排队走进牛棚,每一天只有一头奶牛走进牛棚。第 i i i头奶牛选择走进第 p [ i ] p[i] p[i]个牛棚。由于奶牛是群体动物,所以每当一头奶牛 x x x进入牛棚 y y y之后,牛棚y里的所有奶牛们都会喊一声“欢迎欢迎,热烈欢迎”,由于声音很大,所以产生噪音,产生噪音的大小等于该牛棚里所有奶牛(包括刚进去的奶牛x在内)的数量。最多可以使用 K K K次“清空”操作,每次“清空”操作就是选择一个牛棚,把该牛棚里所有奶牛都清理出去,那些奶牛永远消失。“清空”操作只能在噪音产生后执行。应该如何执行“清空”操作,才能使得所有奶牛进入牛棚后所产生的噪音总和最小?


分析

首先这 n n n头牛没有什么用,真正有用的是总牛棚里牛的个数,设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i个牛棚消除 j j j次所产生的最小噪音,那么 d p [ i ] [ j ] = d p [ i − 1 ] [ j − k ] + c a l c ( p [ i ] , k ) dp[i][j]=dp[i-1][j-k]+calc(p[i],k) dp[i][j]=dp[i1][jk]+calc(p[i],k)


代码

#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
typedef long long ll;
int m,n,a[101]; ll ans,dp[501];
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 ll min(ll a,ll b){return a<b?a:b;}
inline ll calc(int w,int mo){
	rr ll di=w/mo,yu=w%mo;
	return (di*mo*(di+1)>>1)+yu*(di+1);
} 
signed main(){
	rr int nn=iut(); n=iut(); m=iut();
	while (nn--) ++a[iut()];
	memset(dp,0x7f,sizeof(dp)); dp[0]=0;
	for (rr int i=1;i<=n;++i)
	for (rr int j=m;~j;--j){
	    dp[j]+=calc(a[i],1);
	    for (rr int k=j-1;~k;--k)
	        dp[j]=min(dp[j],dp[k]+calc(a[i],j-k+1));
	}
	return !printf("%lld",dp[m]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值