題目鏈接:點(diǎn)擊查看
題目大意:給出 n 個(gè)仆人可以召喚,場上最多可以同時(shí)存在 k 個(gè)仆人,每個(gè)仆人帶有一個(gè)屬性 a 和屬性 b ,仆人本身的價(jià)值為 a[ i ] ,當(dāng)召喚出仆人 i 后,場上的所有仆人價(jià)值都加 b[ i ] ,每個(gè)仆人只能被召喚(銷毀)一次,問如何確定召喚順序,可以使最后場上的仆人價(jià)值總和最大,給出一種召喚方案
題目分析:因?yàn)樗械?a[ i ] 都是非負(fù)的,所以顯然最后場上的仆人個(gè)數(shù)為 k 個(gè)是最優(yōu)的
其次,如果場上只能有 k 個(gè)仆人的話,那么其余 n - k 個(gè)仆人的屬性 a 就失去了意義,不過可以讓他們當(dāng)炮灰,也就是在召喚出來充分利用其價(jià)值 b 后,再將其銷毀,不難看出,當(dāng)其被召喚出來之后,其價(jià)值就已經(jīng)發(fā)揮到最大了,所以下一回合直接將其銷毀是最優(yōu)的選擇
綜上所述,最優(yōu)的方案可以簡述為兩個(gè)點(diǎn),第一點(diǎn)是所有的仆人都需要召喚,只不過有些仆人當(dāng)炮灰了而已,第二點(diǎn)是 n 個(gè)仆人的召喚順序決定了最后的權(quán)值和
那么我們只需要給 n 個(gè)仆人分配一個(gè)合理的順序然后再依次召喚就好了,這里還有一個(gè)思維點(diǎn)需要想到,那就是 n 個(gè)仆人可以大體分為三段:
1 ~ k - 1:這 k - 1 個(gè)仆人最后是保留在場上的 k ~ n - 1 :這 n - k 個(gè)仆人是炮灰 1 : 最后的這個(gè)仆人是當(dāng)炮灰都送完人頭之后,最后再上場,并且保留在場上
大體分成三段后,不難看出讓第一段的仆人先登場,然后恰好留出了一個(gè)位置,讓第二段的炮灰去送人頭,給第一段的仆人增加權(quán)值,然后再讓最后的仆人登場是一種最優(yōu)的方法
既然這樣,就不難看出每個(gè)位置的權(quán)值大小了:
i ∈ [ 1 ,?k - 1 ]:a[ i ] + b[ i ] * ( i - 1 ) i ∈ [ k , n?- 1 ]:b[ i ] * ( k - 1 ) i? == n :a[ i ] + b[ i ] * ( k - 1 )
然后因?yàn)閿?shù)據(jù)比較小,可以用最大費(fèi)用最大流來求解,一方面可以限制流量,另一方面可以求動(dòng)態(tài)的最優(yōu)方案,建圖方法如下:
超級(jí)源點(diǎn) -> 每個(gè)仆人,流量為 1 ,花費(fèi)為 0 每個(gè)仆人 -> 每個(gè)位置,流量為 1 ,花費(fèi)為上述分情況討論的結(jié)果 每個(gè)位置 -> 超級(jí)匯點(diǎn),流量為 1 ,花費(fèi)為 0
建好圖后直接跑模板就好了,關(guān)于路徑輸出這里就不多贅述了,比較簡單
代碼: ?
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
using namespace std;typedef long long LL;typedef unsigned long long ull;const int inf=0x3f3f3f3f;const int N=210;//點(diǎn)const int M=N*N;//邊int a[N],b[N],pos[N];struct Edge
{int to,w,cost,next;
}edge[M];int head[N],cnt;void addedge(int u,int v,int w,int cost)
{edge[cnt].to=v;edge[cnt].w=w;edge[cnt].cost=cost;edge[cnt].next=head[u];head[u]=cnt++;edge[cnt].to=u;edge[cnt].w=0;edge[cnt].cost=-cost;edge[cnt].next=head[v];head[v]=cnt++;
}int d[N],incf[N],pre[N];bool vis[N];bool spfa(int s,int t)
{memset(d,0xcf,sizeof(d));memset(vis,false,sizeof(vis));memset(pre,-1,sizeof(pre));queue<int>q;q.push(s);vis[s]=true;incf[s]=inf;d[s]=0;while(!q.empty()){int u=q.front();q.pop();vis[u]=false;for(int i=head[u];i!=-1;i=edge[i].next){int v=edge[i].to;int w=edge[i].w;int cost=edge[i].cost;if(!w)continue;if(d[v]<d[u]+cost){d[v]=d[u]+cost;pre[v]=i;incf[v]=min(incf[u],w);if(!vis[v]){vis[v]=true;q.push(v);}}}}return pre[t]!=-1;
}int update(int s,int t)
{int x=t;while(x!=s){int i=pre[x];edge[i].w-=incf[t];edge[i^1].w+=incf[t];x=edge[i^1].to;}return d[t]*incf[t];
}void init()
{memset(head,-1,sizeof(head));cnt=0;
}int solve(int st,int ed)
{int ans=0;while(spfa(st,ed))ans+=update(st,ed);return ans;
}int main()
{
#ifndef ONLINE_JUDGE
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);int w;cin>>w;while(w--){init();int n,k,st=N-1,ed=st-1;scanf("%d%d",&n,&k);for(int i=1;i<=n;i++)scanf("%d%d",a+i,b+i);for(int i=1;i<=n;i++){addedge(st,i,1,0);addedge(i+n,ed,1,0);}for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){if(j<=k-1)addedge(i,j+n,1,a[i]+b[i]*(j-1));else if(j==n)addedge(i,j+n,1,a[i]+b[i]*(k-1));elseaddedge(i,j+n,1,b[i]*(k-1));}solve(st,ed);for(int i=1;i<=n;i++)for(int j=head[i];j!=-1;j=edge[j].next)if(edge[j].to>n&&edge[j].to<=2*n&&edge[j].w==0)pos[edge[j].to-n]=i;printf("%d\n",k+(n-k)*2);for(int i=1;i<=n;i++){printf("%d ",pos[i]);if(i>k-1&&i!=n)printf("%d ",-pos[i]);}puts("");}return 0;
}
?
總結(jié)
以上是生活随笔 為你收集整理的CodeForces - 1354F Summoning Minions(最大费用最大流) 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。