新三国争霸
新三国争霸( 最小生成树dp\(\star \star \))
- 时限:\(1s\) 内存:\(256M\)
Descrption
- \(PP\) 特别喜欢玩即时战略类游戏,但他觉得那些游戏都有美中不足的地方。灾害总不降临道路,而只降临城市,而且道路不能被占领,没有保护粮草的真实性。于是他就研发了《新三国争霸》。
- 在这款游戏中,加入灾害对道路的影响(也就是一旦道路 \(W_{i,j}\) 受到了灾害的影响,那么在一定时间内,这条路将不能通过)和道路的占领权(对于一条道路 \(W_{i,j}\),至少需要\(K_{i,j}\) 个士兵才能守住)。
- PP可真是高手,不一会,就攻下了 \(N-1\) 座城市,加上原来的就有 \(N\) 座城市了,但他忽略了一点……那就是防守同样重要,不过现在还来的及。因为才打完仗,所以很多城市都需要建设,\(PP\) 估算了一下,大概需要 \(T\) 天。他现在无暇分身进攻了,只好在这 \(T\) 天内好好的搞建设了。所以他要派士兵占领一些道路,以确保任何两个城市之间都有路(不然敌人就要分而攻之了,是很危险的)。
- 士兵可不是白干活的,每个士兵每天都要吃掉 \(V\) 的军粮。因为有灾害,所以方案可能有变化(每改变一次就需要 \(K\) 的军粮,初始方案也需要 \(K\) 的军粮)。
- 因为游戏是 \(PP\) 编的,所以他知道什么时候有灾害。\(PP\) 可是一个很节约的人,他希望这 \(T\) 天在道路的防守上花最少的军粮。
Input
- 第一行有 \(5\) 个整数 \(N,M,T,V,K\)。\(N\) 表示有城市数,\(M\) 表示道路数,\(T\) 表示需要修养的天数,\(V\) 表示每个士兵每天吃掉的军粮数,\(K\) 表示修改一次花掉的军粮数。
- 以下 \(M\) 行,每行 \(3\) 个数 \(A,B,C\)。表示 \(A\) 与 \(B\) 有一条路(路是双向的)需要 \(C\) 个士兵才能守住。
- 第 \(M+2\) 行是一个数 \(P\),表示有 \(P\) 个灾害。
- 以下 \(P\) 行,每行 \(4\) 个数,\(X,Y,T_1,T_2\)。表示 \(X\) 到 \(Y\) 的这条路,在 \(T_1\) 到 \(T_2\) 这几天都会受灾害。
Output
- \(T\) 天在道路的防守上花费最少的军粮。
Sample Input
3 3 5 10 30
1 2 1
2 3 2
1 3 4
1
1 3 2 5
Sample Output
180
Hint
- \(N<=300,M<=5000,T<=50\),保证结果不超过 \(2^{31}-1\) 。
- 来源:
分析
- 如果没有灾难,此问题就是一个再简单不过的最小生成树问题,那加上灾难这个变量那造成的后果是有可能每一天的最小生成树不一样,生成树不一样那每天士兵的人数也不一样。如果我们能直到每一天的需要的士兵人数。那就相当于给一个 \(1\sim T\) 的序列,序列元素是你每天安排的士兵的最少人数,每一天你选择当天的最小值,也可以选择沿用上一天的士兵人数(如果可行的话),但是如果今天的士兵人数和昨天的不一样时需要额外的代价 \(k\)。
- 模型分析到这里,我们就能很容想到用 \(dp\) 来处理。定义 \(dp[i]\) 表示第 \(i\) 天的最小代价,转移方程有:
- \(dp[i]=min(dp[i],d[j]+w[j+1][i]*V*(i-j)+K),(0\le j<i)\) 。
- \(w[j+1][i]\) 表示 \(j+1\sim i\) 天安排的士兵人数。
- 显然现在问题如何知道 \(w[j+1][i]\) ,\(n\le 300\) ,范围比较小,我们可以 \(n^2\) 预处理出区间内的最小生成树。
- 首先向 \(lin4xu\) 大佬说声 \(sorry\) ,好像解释错了一个问题,就是如果说因为灾难造成了图不连通,这样是不合法的。
Code
#include<cstdio>
#include<algorithm>
const int maxn=300+5,maxm=5000+5,Inf=0x7fffffff;
using namespace std;
struct Node{
int x,y,v;
}e[maxm];
bool cmp(Node a,Node b){return a.v<b.v;}
bool cr[51][maxn][maxn];
int n,m,t,v,k,p,father[301],w[51][51],f[51];
int find(int x){return x==father[x]?x:father[x]=find(father[x]);}
bool Check(int t1,int t2,int u,int v){//判断从t1到t2天边(u,v)是否有灾
for(int i=t1;i<=t2;i++)
if(cr[i][u][v])return 0;
return 1;
}
int krus(int a,int b){//从a天到b天,除去受灾的道路的最小生成树
int t=0;
for(int i=1;i<=n;i++)father[i]=i;
for(int i=1;i<=m;i++){
int x=e[i].x,y=e[i].y;
if(Check(a,b,x,y)&&find(x)!=find(y)){
father[find(x)]=find(y);
t+=e[i].v;
}
}
for(int i=1;i<=n;i++)//判断图是否连通
if(find(i)!=find(1))
return Inf;
return t;
}
void dp(){
for(int i=1;i<=t;i++){
f[i]=v*w[1][i]*i+k;
for(int j=1;j<=i;j++)
if(w[j+1][i]!=Inf)
f[i]=min(f[i],f[j]+k+w[j+1][i]*v*(i-j));
}
}
int main(){
scanf("%d%d%d%d%d",&n,&m,&t,&v,&k);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].v);
if(e[i].x>e[i].y)swap(e[i].x,e[i].y);
}
sort(e+1,e+m+1,cmp);
scanf("%d",&p);
for(int i=1;i<=p;i++){
int x,y,t1,t2;
scanf("%d%d%d%d",&x,&y,&t1,&t2);
if(x>y)swap(x,y);
if(t1>t2)swap(t1,t2);
for(int j=t1;j<=t2;j++)cr[j][x][y]=1;
}
for(int i=1;i<=t;i++)
for(int j=i;j<=t;j++)
w[i][j]=krus(i,j);
dp();
printf("%d",f[t]);
return 0;
}
hzoi