题解:P7688 [CEOI 2005] Ticket Office

模拟赛场切了,一个比较简单的做法!

考虑直接从前往后 dp,设 \(dp_i\) 表示只考虑 \(1\) 到 \(i\) 的位置时,最多的总收入是多少(先不管订单数 \(n\))。转移就是你能放 \(2\) 就直接令 \(dp_i\) 为 \(dp_{i-l}+2\),否则选择 \(dp_{i-l}+1\) 与 \(dp_{i-1}\) 中的最大值转移即可。

你可能会问:假如说有一种情况选择了 \(1\) 个 \(2\) 和 \(4\) 个 \(1\),第二种情况 \(2\) 个 \(2\) 和 \(1\) 个 \(1\),那我们的 dp 显然会选择第一种情况。但是,假如说 \(n=3\),那么第一种情况就会截断,就没有第二种情况优了。

但是,如果你删去 \(1\) 个 \(2\),实际上最多只能再加入 \(2\) 个 \(1\),总收入不会变化,而需要的订单个数就更多了,所以一定不优,故其实不会上述的情况。同时,我们在总收入相同的时候,要选订单个数最少,即 \(2\) 的个数最多的情况,上面的 dp 正好满足这一点。所以这个 dp 是完全正确的。

算出来答案之后,构造只需要在 dp 的同时记录 \(pre_i\) 表示 \(dp_i\) 是从 \(dp_{pre_i}\) 转移过来的,然后直接在上面构造即可。主要难度在代码。复杂度显然可以线性(我偷懒带了个 \(\log\)),显然可以通过。

代码

可能和推导略有不同,但本质相同。

int m,l,n,a[1000005],vis[1000005],dp[1000005],cnt[1000005],pre[1000005];
vector<pii>v,ans;
void solve(){
	cin>>m>>l>>n;
	fo(i,1,n)cin>>a[i],vis[a[i]]=i;
	fo(i,1,m){
		int x=(i>=l?dp[i-l]+1+(!!vis[i-l+1]):0),y=dp[i-1];
		if(x>y)dp[i]=x,pre[i]=i-l,cnt[i]=cnt[i-l]+!!vis[i-l+1];
		else dp[i]=y,pre[i]=i-1,cnt[i]=cnt[i-1];
	}
	cout<<min(dp[m],n+cnt[m])<<'\n';
	set<int>s;
	fo(i,1,n)s.insert(i);
	int pos=m;
	while(pos){
		if(pre[pos]==pos-l){
			v.pb({0,pre[pos]+1});
			if(vis[pre[pos]+1])v.back().fi=vis[pre[pos]+1],s.erase(vis[pre[pos]+1]);
		}
		pos=pre[pos];
	}
	for(int i:s){
		while(pos<v.size()&&v[pos].fi)pos++;
		if(pos<v.size())v[pos].fi=i;
	}
	for(pii i:v)if(i.fi)ans.pb(i);
	cout<<ans.size()<<'\n',reverse(ans.begin(),ans.end());
	for(pii i:ans)cout<<i.fi<<' '<<i.se<<'\n';
}
posted @ 2026-09-10 21:23  qwertim  阅读(6)  评论(0)    收藏  举报