codevs 2817 Tangent的愤怒
【题目描述】
如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段。
第二段:本题改编自Usaco Training 4.4.2...
第三段:本题加大了数据强度...
第四段:本题来自CH Round #1...
第五段:快去看第六段!
Tangent来到OI村,想起Bread经常在他面前晒妹(Lemon),于是要把二人分隔两地,永世不能相见。
黑化的Tangent拥有了分裂大地的力量,他要分裂两人的家之间的一些路,使得Bread不能去找Lemon。(保证Bread家和Lemon家连通)
从Bread家到Lemon家的路现在可以看成是一个有向图,具有N个点,M条边。(点的编号为1~N,边的编号按照离Tangent的距离由近到远依次为1~M)
Tangent想要毁坏一条边的代价是Wi。
由于Tangent想要节省力量去毁坏更多和谐的事物,所以他的炸路方案必定是总代价最小、边数量最小的,而且他希望能尽快做完这件事,所以他炸的路对应编号必定是字典序最小的。
【输入描述】
第一行四个正整数N,M,S0,T0,分别表示点数,边数,Bread家的点编号,Lemon家的点编号。
接下来N行,按照边的编号依次描述每条边,每行三个正整数Si,Ti,Wi,分别表示第i条边的起点、终点和毁坏代价。
【输出描述】
第一行两个正整数W和K,表示总最小代价和最小炸路数量。
接下来K行,输出最小字典序方案,每行一个正整数Number,表示第Number条边要炸毁。
【样例输入】
4 5 1 4
1 3 100
3 2 50
2 4 60
1 2 40
2 3 80
【样例输出】
60 1
3
【解题思路】
题目是一道裸的最小割(话说为何USACO的题都特别裸……)有点不同的是它不仅要求你总代价最小,还要边数最少,还要输出炸了哪些路。
首先我们来看怎么让总代价最小的同时让边数最小。
求边数最小应该还是很容易想到每炸一条边就把计数器+1吧?但显然在求网络流的过程中我们没法知道它删没删边,走得哪条边。于是我们可以用一个巧妙的办法:将每条边的边权*(m+1)+1。
为什么是*(m+1)呢?
不难发现,即使每条边都要炸,也最多只加了m,于是我们将最后的答案div (m+1)就得到总代价了,同理mod (m+1)就得到炸的边数了。
然后对于炸的是哪些路径,我们可以用类似于最短路找所经过的路径的办法(或者求次短路的办法),枚举每条边将其删去,然后再跑最大流,如果所得结果加上该边边权与之前答案相同,那么该边就是被炸的边了。为了加速程序运行,我们可以把这条边删去,同时答案减去该边权。
顺便再讲一个优化……(用dinic做这题不加这优化好像过不去……?)
我们发现数据范围极其恶心,N=50而M=5000,我们在枚举删边的时候构图会有很大的麻烦,时间复杂度蹭蹭地上去了,然后发现M>N^2,肯定有重边,所以我们可以把重边记录在一起,删边的时候就只要减去当前边边权即可,这样不仅加快了构图速度,还加快了网络流速度。详见代码。最后是一个注意事项:因为每条边的边权*(m+1)+1了,所以很多东西要开long long,无穷大也要变为LLONG_MAX。
【代码实现】
1 #include<cstdio> 2 #include<cstring> 3 #include<climits> 4 #include<map> 5 using namespace std; 6 struct edge{ 7 int d,next; 8 long long data; 9 }e[10010]; 10 int n,m,s0,t0,efree=1,f[1000],i,j,x[5010],y[5010],q[1010],h,t,z[5010],dis[1000],p,k; 11 long long ans,cnt,a[1000][1000]; 12 inline int min(int x,int y){ 13 if(x<y)return x; 14 return y; 15 } 16 inline void add(int x,int y,long long z){ 17 e[++efree].d=y; 18 e[efree].next=f[x]; 19 e[efree].data=z; 20 f[x]=efree; 21 } 22 inline bool bfs(){ 23 memset(dis,-1,sizeof(dis)); 24 dis[s0]=1; 25 h=0;t=1; 26 q[1]=s0; 27 while(h!=t){ 28 p=q[++h]; 29 for(int i=f[p];i;i=e[i].next) 30 if(e[i].data>0&&dis[e[i].d]<0){ 31 dis[e[i].d]=dis[p]+1; 32 q[++t]=e[i].d; 33 } 34 } 35 return dis[t0]>0; 36 } 37 inline long long dfs(int x,long long y){ 38 if(x==t0)return y; 39 long long re,tmp=0; 40 for(int i=f[x];i;i=e[i].next) 41 if(dis[e[i].d]==dis[x]+1&&e[i].data>0){ 42 re=dfs(e[i].d,min(y-tmp,e[i].data)); 43 e[i].data-=re; 44 e[i^1].data+=re; 45 tmp+=re; 46 if(y==tmp)return y; 47 } 48 return tmp; 49 } 50 int main(){ 51 scanf("%d%d%d%d",&n,&m,&s0,&t0); 52 for(i=1;i<=m;i++){ 53 scanf("%d%d%lld",&x[i],&y[i],&z[i]); 54 z[i]=z[i]*(m+1)+1;//巧妙的边权设计 55 a[x[i]][y[i]]+=z[i];//重边的记录 56 } 57 for(i=1;i<=n;i++) 58 for(j=1;j<=n;j++) 59 if(a[i][j]){ 60 add(i,j,a[i][j]); 61 add(j,i,0); 62 } 63 while(bfs())ans+=dfs(s0,LLONG_MAX); 64 printf("%lld %lld\n",ans/(m+1),ans%(m+1)); 65 for(i=1;i<=m;i++){ 66 efree=1; 67 memset(f,0,sizeof(f)); 68 a[x[i]][y[i]]-=z[i];//枚举每条边 69 for(j=1;j<=n;j++) 70 for(k=1;k<=n;k++) 71 if(a[j][k]){ 72 add(j,k,a[j][k]); 73 add(k,j,0); 74 } 75 cnt=0; 76 while(bfs())cnt+=dfs(s0,LLONG_MAX); 77 if(cnt+z[i]==ans){ 78 printf("%d\n",i); 79 ans-=z[i];//该边被选择,删边操作 80 } 81 else a[x[i]][y[i]]+=z[i]; 82 } 83 return 0; 84 }