题解 尽梨了

传送门

考场推式子的时候错了一个小细节导致整体爆炸

先考虑临项扰动,则 \(i\)\(j\) 前仅当这样走完两者的总时间更小

\[a_it+b_i+(\color{red}{t}+a_it+b_i+\color{red}{1})a_j+b_j \leqslant a_jt+b_j+(\color{red}{t}+a_jt+b_j+\color{red}{1})a_a+b_a \]

考场上就是忽略了上面这两个地方
然后我们得到了一个序列,但发现最优解不一定是在这个序列上贪心选
举个例子:\(i\)\(j\) 前但剩下的时间只来得及走完 \(j\) 而走不完 \(i\)
重新想下我们刚才对“更优”的定义就会发现,原定义是\(i, j\) 都被选择的情况下,先选某一个更优
于是在整个序列都被选完时,按这个顺序选一定更优

  • 临项扰动完得到的序列不一定就是答案,可能还得在上面做DP什么的

但现在并不能都选完,于是懵了
发现若有两个元素都被选了,它们被选顺序与在按刚才方法排过序中这两个元素的顺序相同
即最优解是这个序列的一个子序列
于是可以DP,令 \(dp[i][j]\) 为考虑到第 \(i\) 个物品,选了 \(j\) 个的最小时间

  • 仔细检察一下有没有什么东西是倍增/指数级的

于是当 \(a_i>0\) 时,所需时间随选的个数(至少)倍增,于是只能选log个
所以第二维开到log,这部分复杂度 \(nlogn\)
然后考虑将 \(a_i=0\) 的贡献加到答案里
所以将这些 \(b_i\) 排序求前缀和,枚举所有合法状态并二分出最多再选多少个 \(a_i=0\)
特别注意一个细节是从一个商店到另一个需要1的时间
所以求前缀和时每个位置要额外加1
这部分我的实现是 \(O(nlog^2n)\) 的,跑的还挺快的

Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 200010
#define ll long long
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n;
ll a[N], b[N], T;

namespace force{
	int p[N], ans;
	void solve() {
		for (int i=1; i<=n; ++i) p[i]=i;
		do {
			int cnt=0; ll t=1;
			for (int i=1; i<=n; ++i) {
				if (t+a[p[i]]*t+b[p[i]] <= T) {
					++cnt;
					t+=a[p[i]]*t+b[p[i]]+1;
				}
				else break;
			}
			ans=max(ans, cnt);
		} while (next_permutation(p+1, p+n+1));
		printf("%d\n", ans);
		exit(0);
	}
}

namespace task{
	int tot, ans;
	ll dp[N][30], minn[30], sum[N], tem[N];
	struct ele{ll a, b; inline void build(ll x, ll y) {a=x; b=y;}}p[N];
	inline bool operator < (ele a, ele b) {return b.a*(a.b+1)<a.a*(b.b+1);}
	void solve() {
		// cout<<double(sizeof(dp)+sizeof(p))/1000/1000<<endl;
		for (int i=1; i<=n; ++i) p[i].build(a[i], b[i]);
		sort(p+1, p+n+1);
		// cout<<"p: "; for (int i=1; i<=n; ++i) cout<<p[i].a<<','<<p[i].b<<' '; cout<<endl;
		for (int i=n; i; --i)
			if (!p[i].a) tem[++tot]=p[i].b;
			else break;
		sort(tem+1, tem+tot+1);
		tem[0]=-1;
		for (int i=1; i<=tot; ++i) sum[i]=sum[i-1]+tem[i]+1;
		memset(dp, 0x3f, sizeof(dp));
		dp[0][0]=1;
		for (int i=1; i<30; ++i) minn[i]=T+1;
		minn[0]=1;
		for (int i=1; i<=n; ++i) if (p[i].a) {
			for (int j=29; j; --j) if (minn[j-1]<T) {
				dp[i][j]=(p[i].a+1)*minn[j-1]+p[i].b;
				minn[j]=min(minn[j], dp[i][j]+1);
			}
		}
		for (int i=0; i<=n; ++i) if (p[i].a) {
			for (int j=29; ~j; --j) if (dp[i][j]<=T) {
				ans=max(ans, int(j+(upper_bound(sum+1, sum+tot+1, T-dp[i][j])-sum-1)));
			}
		}
		printf("%d\n", ans);
		exit(0);
	}
}

signed main()
{
	freopen("eriri.in", "r", stdin);
	freopen("eriri.out", "w", stdout);

	n=read(); T=read();
	for (int i=1; i<=n; ++i) a[i]=read(), b[i]=read();
	// force::solve();
	task::solve();

	return 0;
}
posted @ 2021-10-29 21:44  Administrator-09  阅读(4)  评论(0编辑  收藏  举报