洛谷 P1973 [NOI2011] NOI 嘉年华(单调队列优化DP)

1 篇文章 0 订阅

题目链接:P1973 [NOI2011] NOI 嘉年华

题解:

离散化每个活动时间后,用num[i][j]表示时间段[i,j]内的完整活动数量,用f[i][j]表示1-i时间内,第一个场所办了j个活动时,第二个场所能办的活动的最大数量,g[i][j]表示i-cnt时间内,第一个场所办了j个活动时,第二个场所能办的活动的最大数量,这样没有约束时就很好求了,有约束时我们枚举包含当前活动的区间左右端点,强制这段时间内活动都在第一个场地办,再枚举[1,左端点]和[右端点,时间结束]内在第一个场地办的活动数量,通过f和g函数就可以轻松求出第二个场地办的数量,此时复杂度是是O(N^5),我们需要剪枝下,观察转移方程ans[k]=max(ans[k],min(num[i][j]+l+r,f[i][l]+g[j][r])),因为f[i][l]和g[j][r]在i和j固定时都随着l或r递减,要使得min(num[i][j]+l+r,f[i][l]+g[j][r])最大,需要num[i][j]+l+r=f[i][l]+g[j][r],因为num[i][j]>=0,所以最优情况的l和r是满足g[j][r]>=r,f[i][l]>=l,再加上比较明显的剪枝,实际剪枝后只有O(n^3)左右

细节和注释见代码:

#include<iostream>
#include<stack>
#include<list>
#include<set>
#include<vector>
#include<algorithm>
#include<math.h>
#include<numeric>
#include<map>
#include<cstring>
#include<queue>
#include<iomanip>
#include<cmath>
#include<queue>
#include <bitset>
#include<unordered_map>
	#ifndef local
	#define endl '\n'
#endif */
#define mkp make_pair
using namespace std;
using std::bitset;
typedef long long ll;
typedef long double ld;
const int inf=0x3f3f3f3f;
const ll MAXN=2e6+10;
const ll INF=1e18;
const ll N=2e5+100;
const ll mod=1e9+7;
const ll hash_p1=1610612741;
const ll hash_p2=805306457;
const ll hash_p3=402653189;
//-----------------------------------------------------------------------------------------------------------------*/
// ll head[MAXN],net[MAXN],to[MAXN],edge[MAXN]/*流量*/,cost[MAXN]//费用;
/* 
void add(ll u,ll v,ll w,ll s){
	to[++cnt]=v;net[cnt]=head[u];edge[cnt]=w;cost[cnt]=s;head[u]=cnt;
	to[++cnt]=u;net[cnt]=head[v];edge[cnt]=0;cost[cnt]=-s;head[v]=cnt;
}
struct elemt{
	int p,v;
};
-----------------------------------
求[1,MAXN]组合式和逆元 
ll mi(ll a,ll b){
	ll res=1;
	while(b){
		if(b%2){
			res=res*a%mod;
		}	
		a=a*a%mod;
		b/=2;
	}
	return res;
}
ll fac[MAXN+10],inv[MAXN+10]
ll C(int m,int n){//组合式C(m,n); 
	if(!n){
		return 1;
	}
	return fac[m]*(inv[n]*inv[m-n]%mod)%mod;
}
fac[0]=1;inv[0]=1;
for(ll i=1;i<=MAXN;i++){
	fac[i]=(fac[i-1]*i)%mod;
	inv[i]=mi(fac[i],mod-2);
}
---------------------------------
 unordered_map<int,int>mp;
//优先队列默认小顶堆 , greater<int> --小顶堆  less<int> --大顶堆  
priority_queue<elemt,vector<elemt>,comp>q;
struct comp{
	public:
		bool operator()(elemt v1,elemt v2){
			return v1.v<v2.v;
		}
};
	set<int>::iterator it=st.begin();
*/
//emplace_back()  等于push_back(),但效率更高,传输pair时emplace_back(i,j)==push_back({i,j}) 
// vector<vector<int>>edge; 二维虚拟储存坐标 
//-----------------------------------------------------------------------------------------------------------------*/
  map<int,int>mp; 
  //emplace_back() 
int s[210],t[210];
int num[410][410];//记录区间[i,j]内被完整包裹的活动数量
int f[410][210];//1-i时间内,第一个场所办了j个活动时,第二个场所能办的活动的最大数量
int g[410][210];//i-cnt时间内,第一个场所办了j个活动时,第二个场所能办的活动的最大数量
int ans[210];//各个答案
int main(){
/*cout<<setiosflags(ios::fixed)<<setprecision(8)<<ans<<endl;//输出ans(float)格式控制为8位小数(不含整数部分)*/
/*cout<<setprecision(8)<<ans<<endl;//输出ans(float)格式控制为8位小数(含整数部分)*/
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);//同步流
	int n;
	cin>>n;
	vector<int>b;
	for(int i=1;i<=n;i++){
		cin>>s[i]>>t[i];
		t[i]+=s[i];
		b.emplace_back(s[i]);
		b.emplace_back(t[i]);
	}
	//---------------------离散化----------
	sort(b.begin(),b.end());
	int cnt=0;
	mp[b[0]]=++cnt;
	for(int i=1;i<b.size();i++){
		if(b[i]!=b[i-1]){
			mp[b[i]]=++cnt;
		}
	}
	for(int i=1;i<=n;i++){
		s[i]=mp[s[i]];
		t[i]=mp[t[i]];
	}
	//----------------求num数组-------
	for(int i=1;i<=n;i++){//枚举活动,更新包裹该活动的num区间
		for(int l=s[i];l>=1;l--){
			for(int r=t[i];r<=cnt;r++){
				num[l][r]++;
			}
		}
	}
	//----------------求f和g数组
	memset(f,-inf,sizeof(f));
	memset(g,-inf,sizeof(g));
	for(int i=1;i<=cnt;i++){
		f[i][0]=0;
		g[i][0]=0;
	}
	for(int i=1;i<=cnt;i++){
		for(int j=0;j<=n;j++){
			for(int k=1;k<=i;k++){
				f[i][j]=max(f[i][j],f[k][j]+num[k][i]);//[k,i]段内活动在第二个场所办
				if(j>=num[k][i]){
					f[i][j]=max(f[i][j],f[k][j-num[k][i]]);//[k,i]段内活动在第一个场所办
				}
			}
			ans[0]=max(ans[0],min(f[i][j],j));
		}
	}
	for(int i=cnt;i>=1;i--){
		for(int j=0;j<=n;j++){
			for(int k=cnt;k>=i;k--){
				g[i][j]=max(g[i][j],g[k][j]+num[i][k]);//[k,i]段内活动在第二个场所办
				if(j>=num[i][k]){
					g[i][j]=max(g[i][j],g[k][j-num[i][k]]);//[k,i]段内活动在第一个场所办
				}
			}
		}
	}
	for(int k=1;k<=n;k++){//必须选择第k个活动
		for(int i=s[k];i>=1;i--){//包含活动k的区间[i,j]
			for(int j=t[k];j<=cnt;j++){
				for(int l=0;l<=num[1][i];l++){//[1,i]中在第一个活动场地举办的活动数
					for(int r=0;r<=num[j][cnt];r++){//[1,i]中在第一个活动场地举办的活动数
						ans[k]=max(ans[k],min(num[i][j]+l+r,f[i][l]+g[j][r]));
						if(g[j][r]<r){//g和f为减函数,
							break;
						}
					}
					if(f[i][l]<l){
						break;
					}
				}
				if(num[1][i]+num[j][cnt]<num[i][j]||num[1][j]+num[j][cnt]<ans[k]){
					break;//此时继续往下都不可能再更新ans[k]了
				}
			}
		}
	}
	for(int i=0;i<=n;i++){
		cout<<ans[i]<<endl;
	}
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值