1. 程式人生 > 實用技巧 >【暑假集訓】HZOI2019 水站 多種解法

【暑假集訓】HZOI2019 水站 多種解法

題目內容

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

輸入格式

  • 每個輸入的第一行包含一個自然數\(n(1\le n\le 150000)\)
  • 接下來\(n\)行每行包含\(3\)個數\(W_i,L_i,P_i(0\le W_i,L_i,P_i\le 15000)\)

輸出格式

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

程式碼

列舉+剪枝

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=150000+10;
int n,temp;
int w[maxn],l[maxn],p[maxn];
ll ans;
vector<int> res,mem;

void work(ll now){
    mem.clear();
    int x=now,tempw=w[now],tot=p[now],temp;
    mem.push_back(now);
    while(x<=n){
	temp=w[++x]+tempw;
	tempw=temp;
	if(temp<=l[x]){
	    tot+=p[x];
	    mem.push_back(x);
	}
	if(tot>=ans)return;
    }
    if(tot<ans){
	ans=tot;
	res.clear();
	for(int i=0;i<mem.size();i++){
	    res.push_back(mem[i]);
	}
    }
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
	scanf("%d%d%d",&w[i],&l[i],&p[i]);

    ans=p[n];res.push_back(n);

    for(int i=1;i<n;i++)
	work(i);

    printf("%lld\n",ans);

    for(int i=0;i<res.size();i++)
	printf("%d ",res[i]);

    return 0;
}

二分+差分

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=150000+10;
const ll INF=1e15;
int n;
ll ans=INF;
int w[maxn],l[maxn],p[maxn];
int c[maxn],sum[maxn];

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 x=lower_bound(sum,sum+i+1,sum[i]-l[i])-sum;
		x++;
		c[x]+=p[i];
        c[i+1]-=p[i];
	}

	int temp;

	for(int i=1;i<=n;i++){
		c[i]=c[i-1]+c[i];
		if(ans>c[i]){
			ans=c[i];
            temp=i;
		}
	}

	printf("%lld\n",ans);
	
    int tot=0;
	for(int i=temp;i<=n;i++){
		tot+=w[i];
		if(tot<=l[i])
            printf("%d ",i);
	}
	return 0;
}

優先佇列

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=150000+10;
const ll INF=1e15;
int n;
ll ans=INF;
int sum,num;
int w[maxn],l[maxn],p[maxn];
int s[maxn];

struct Node{
	int sum,id;
	bool operator < (const Node& x)const{
		return x.sum<sum;
	}
};

priority_queue<Node> q;

int main(){
	freopen("a.in","r",stdin);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
        scanf("%d%d%d",&w[i],&l[i],&p[i]);
    }
	for(int i=n;i>=1;i--)
		s[i]=s[i+1]+w[i];
		
	for(int i=n;i>=1;i--){
		sum+=p[i];
		while(!q.empty()){
			Node t=q.top();
			if(s[i]<=t.sum)break;
			sum-=p[t.id];
			q.pop();
		}
		Node cur;
		cur.id=i;
		cur.sum=l[i]+s[i+1];
		q.push(cur);
		if(ans>sum){
			ans=sum;
			num=i;
		}
	}
	
	printf("%lld\n",ans);
	
	sum = 0;
	for(int i=num;i<=n;++i){
		sum+=w[i];
		if(sum<=l[i])printf("%d ",i);
	}
	
	return 0;
}

線段樹

參見林dalao程式碼,%%%。
學長:不要用資料結構