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 }

 

你可能感兴趣的:(codevs 2817 Tangent的愤怒)