1. 程式人生 > 實用技巧 >水站

水站

題目大意

  • 已知有一個n層的水站:
    • Wi表示未操作之前第i層的已有水量;
    • Li表示第i個水站能夠維持或者儲存的水的重量;
    • Pi表示在第i層進行減壓放水操作所需的費用.
    • 被壓減放水層所儲存的所有水都將流向下一層。如果第i層的水量比Li大,則這一層也會(自動)減壓(不需要任何費用)。
  • 現在想要使最後一層減壓(第n級),求最少的花費。這個任務現在交給了你。

輸入格式

  • 每個輸入的第一行包含一個自然數n(1 =< n <= 150000)
  • 接下來n行每行包含3個數Wi,Li,Pi。

輸出格式

  • 第一行輸出所需的最小費用
  • 第二行若干個整數,從小到大輸出必須減壓的層的編號。

樣例

樣例輸入

3
1000 1000 1
0 1000 2
2 10 100

樣例輸出

3
1 2

演算法分析

  • 模擬很好想吧 首先先來看一個事實: 如果當前節點i是第一個花錢把它減壓的 那麼從i到n一定所有的節點都減壓了 因為如果在中間的某個節點沒有減壓的話 那麼它前面的減壓花費相當於白給
  • 但是模擬會T 所以我們要準備想正解了
  • 如果假設當前節點為k 在節點k之前有一個節點i 如果從i之前開始那麼k不需要主動減壓 從i之後開始需要主動減壓(i越靠前到k處的水就越多 很容易 自己想)
  • 而關於這個節點的位置我們可以二分查詢
  • 然後我們定義 ci 為從第 i 層開始放水一直放到第 n 層所需的費用. 考慮第 k 層的強制放水花費會對整個 ⟨ci⟩ 產生什麼樣的影響. 顯然有當且僅當從第 i 層到第 k 層的總水量都不足以使第 k 層自動減壓時 ci 中會包含第 k 層的強制放水費用. 因為當 k 不變時總水量隨著 i 的減少而單調增加, 所以合法的 i 必然是比 k 小的連續一段, 二分找到最小的 i , 把最小的 i 到 k 之間的所有 ci 都加上 k 的花費即可. 而這個操作可以通過簡單的字首和解決. 總時間複雜度是 O(nlogn).

程式碼展示



#include<bits/stdc++.h>
using namespace std;
const int maxn = 15e4+10;
int n;
int w[maxn],l[maxn],p[maxn];
int sum[maxn],v[maxn];
int now,ans = 0x3f3f3f3f;

int main(){
	scanf("%d",&n);
	for(int i = 1;i <= n;++i){
		scanf("%d%d%d",&w[i],&l[i],&p[i]);
		sum[i] = sum[i-1] + w[i];
	}
	for(int i = 1;i <= n;++i){
		int L = 1,R = i;
		while(L < R) {
			int mid = (L + R) >> 1;
			if(sum[i] - sum[mid - 1] <= l[i])R = mid;
			if(sum[i] - sum[mid - 1] > l[i])L = mid + 1;
		}
		v[R] += p[i];
		v[i+1] -= p[i];
	}
	for(int i = 1;i <= n;++i){
		v[i] += v[i-1];
		if(ans > v[i])ans = v[i],now = i;
	}
	printf("%d\n",ans);
	for(int i = now;i <= n;++i)
		if(sum[i] - sum[now - 1] <= l[i])printf("%d ",i);
	return 0;
}