AGC033D Complexity 和 Atcoder Manga Market

Complexity

有一个 𝑛 × 𝑚 的 01 矩形,若矩形中的所有元素都相同,则这个矩形的代价为 0。 否则选择一种将它分成两个子矩形的方案(横着割一刀或竖着割一刀),代价为所有方案中(两个子矩形的代价的较大值 +1)的最小值。

𝑛, 𝑚 ≤ 185。

题解

𝑓[𝑖][𝑗][𝑘][𝑙] 表示左上角为 (𝑖,𝑗),右下角为 (𝑘,𝑙) 的子矩形的代价最小值。

通过枚举割哪里转移,复杂度 𝑂(𝑛5)。

注意到答案很小(不超过 log 𝑛 + log 𝑚)且满足单调性,于是可以把答案和状态互换。

状态改为 𝑓[𝑖][𝑙][𝑟][𝑘] 表示左上角为 (𝑙,𝑖),左下角为 (𝑟,𝑖) 的矩形,代价为 𝑘 往右最多能延伸到哪。

考虑 𝑓 的转移,一种是竖着割,这样割出的靠左的那块矩形的右边界要尽可能靠右。可以简单的用 𝑓 转移得来。

https://www.luogu.com.cn/blog/Doube-Suzerain/solution-at4927

另一种是横着割,注意到 𝑟-𝑙+1 越大时DP值越小,那么可以二分最优的分割点使得上半边和下半边的DP值尽量接近即可。

时间复杂度 \(O(n^3 \log^2 n)\)

CO int N=200;
char grid[N][N];
int sum[N][N],dp[2][N][N][N];

IN int query(int i,int l,int j,int r){
	return sum[j][r]-sum[i-1][r]-sum[j][l-1]+sum[i-1][l-1];
}
IN bool equal(int i,int l,int j,int r){
	int s=query(i,l,j,r);
	return s==0 or s==(j-i+1)*(r-l+1);
}
int find(int ans,int i,int j,int l){
	int L=i,R=j-1,res=-1;
	while(L<=R){
		int mid=(L+R)>>1;
		int u=dp[ans&1][i][mid][l];
		int v=dp[ans&1][mid+1][j][l];
		res=max(res,min(u,v));
		if(u<v) R=mid-1;
		else L=mid+1;
	}
	return res;
}
int main(){
	int n=read<int>(),m=read<int>();
	for(int i=1;i<=n;++i){
		scanf("%s",grid[i]+1);
		for(int j=1;j<=m;++j) sum[i][j]=grid[i][j]=='#';
	}
	for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)
		sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
	for(int i=1;i<=n;++i)for(int j=i;j<=n;++j)
		for(int l=1,r;l<=m;l=r+1){
			for(r=l-1;r<m and equal(i,l,j,r+1);++r);
			for(int p=l;p<=r;++p) dp[0][i][j][p]=r;
			if(r==l-1) dp[0][i][j][l]=r,++r;
		}
	int ans=0;
	for(ans=1;dp[(ans-1)&1][1][n][1]<m;++ans)
		for(int i=1;i<=n;++i)for(int j=i;j<=n;++j)
			for(int l=1;l<=m;++l){
				int v=dp[(ans-1)&1][i][j][l];
				if(v==m) {dp[ans&1][i][j][l]=m; continue;}
				dp[ans&1][i][j][l]=max(dp[(ans-1)&1][i][j][v+1],find(ans-1,i,j,l));
			}
	printf("%d\n",--ans);
	return 0;
}

Manga Market

给定 \(n\)\(T\),你初始在 \(0\) 号点,有 \(n\) 个商店,你在任意两个商店或者 \(0\) 号点与商店之间往返的时间均为 \(1\)

当你于时刻 \(t\) 到达商店 \(i\) 时,你需要花费 \(a_i\times t+b_i\) 的时间进行等待(\(a_i,b_i\)给定),等待完之后,你可以购物,我们假设购物不花费时间也就是说你只有排队等待和往返于商店之间会花费时间。

假定所有商店都会在 \(T+0.5\) 时刻关门,你想知道,你最多能在几个不同的商店内购物。

\(n\le 2\times 10^5,T,a,b\le 10^9\)

注意,是到达商店的时间为 \(t\) 就会花费 \(a\times t+b\) 的时间,所以假设你在 \(0\) 时刻走到一个 \(a\)\(1\) 的商店,那么你的时间结算方式实际上是:

  1. 先走过去,时间为 \(1\)

  2. 到达商店,时间花费为 \(1\times a+b\),总花费为 \((0+1)\times (a+1)+b\)

题解

http://jklover.hs-blog.cf/2020/04/22/Hitachi-Programming-Contest-2020/

考虑如果在时刻 t 在商店 i 购买物品,结束后立即去商店 j 购买物品 .

那么 j​ 会因为在 i​ 处等候而额外花费 \((a_i\cdot t +b_i+1)\cdot a_j​\) 的时间.

如果我们将二者交换顺序,在时刻 t​ 在 j​ 购买,结束后立即去 i​ 购买, i​ 会额外花费 \((a_j\cdot t +b_j+1)\cdot a_i\)​ 的时间.

若先去 i​ 比先去 j​ 更优,就需要满足

\[(a_i\cdot t +b_i+1)\cdot a_j\le (a_j\cdot t +b_j+1)\cdot a_i \\ (b_i+1)\cdot a_j \le (b_j+1)\cdot a_i \]

可以发现 i 是否比 j 优与当前时刻 t 无关.

于是可以先对所有商店排序,得到序列 p ,那么我们实际去的商店按照时间先后形成的序列一定是 p 的一个子序列.

考虑 dp, 设 dp(i,j) 表示考虑了序列 p 的前 i 家商店,去了 j 家商店最少用时,转移时枚举去不去第 i 家商店.

这个 dp 的状态数是 \(O(n^2)\) 的,无法直接通过,需要一些优化.

注意到如果没有 \(a_i=0\) 的商店,花费的时间是随去的商店数目指数级增长的,即有效的 j 只会有前 \(O(\log T)\) 个.

而根据我们的排序方式, \(a_i=0\) 的商店一定是排在 \(a_i>0\) 的商店后面的.

于是可以对 \(a_i>0\) 的所有商店做一次 dp ,就只会有 \(O(n\log T)\) 个状态需要计算,设这样的商店共有 k 个,

然后对于每个状态 (j,dp(k,j))​ ,贪心地将 \(a_i=0\)​ 的商店按照 \(b_i\)​ 从小到大依次去一遍,直到全部取完或时间超出 T​ .

只会有 \(O(\log T)\) 个状态 (j,dp(k,j)) ,这部分的时间复杂度也是 \(O(n\log T)\) .

再加上给 n 个商店排序的复杂度,总时间复杂度为 \(O(n\log n+n\log T)\).

CO int N=2e5+10;
struct node {int a,b;} p[N];
map<int,int> now,nxt;

IN int64 calc(CO node&p,int t){
	return (int64)p.a*t+p.b;
}
int main(){
	int n=read<int>(),T=read<int>();
	for(int i=1;i<=n;++i) read(p[i].a),read(p[i].b);
	sort(p+1,p+n+1,[&](CO node&p,CO node&q)->bool{
		return (int64)(p.b+1)*q.a<(int64)(q.b+1)*p.a;
	});
	now[0]=0;
	int k=n+1;
	for(int i=1;i<=n;++i){
		if(p[i].a==0) {k=i; break;}
		nxt=now;
		for(CO pair<int,int>&j:now){
			int c=j.first,t=j.second;
			if(t+1+calc(p[i],t+1)<=T){
				t=t+1+calc(p[i],t+1);
				if(!nxt.count(c+1)) nxt[c+1]=t;
				else nxt[c+1]=min(nxt[c+1],t);
			}
		}
		now=nxt;
	}
	sort(p+k,p+n+1,[&](CO node&p,CO node&q)->bool{
		return p.b<q.b;
	});
	int ans=0;
	for(CO pair<int,int>&j:now){
		int c=j.first,t=j.second;
		for(int i=k;i<=n;++i)
			if(t+1+calc(p[i],t+1)<=T)
				++c,t=t+1+calc(p[i],t+1);
		ans=max(ans,c);
	}
	printf("%d\n",ans);
	return 0;
}

posted on 2020-04-23 21:41  autoint  阅读(222)  评论(0编辑  收藏  举报

导航