JZOJ 3317. 【BOI2013】管道
题目
Description
Hotham市再次被Jester袭击。这一次,Jester攻击的目标是Hotham的供水系统。Hotham的淡水存储在N个水库,由M个管道连接。
任意2个水库之间至少有一条路径,该路径可能包含多个管道。每个管道连接两个不同的水库,任何两个水库之间最多只有一个管道。Jester破坏了一些管道进行排水。管道的排水量为偶数立方米/秒。如果连接水库u和水库v的管道排水量为2D立方米/秒,那么水库u和水库v排水量分别为D立方米/秒。Jester可以也破坏一些管道进行注水。同样,连接水库u和v的管道注入水量为2P立方米/秒,则水库u和水库v注水量分别为P立方米/秒。
每个水库供水量的净变化是由连接到它的管道的排水量和注水量的总和。如果连接该水库的管道排水量分别为2D1, 2D2,…,2Da立方米/秒, 注水量分别为2P1, 2P2 ,…,2Pb 立方米/秒。该水库水量的净变化为P1 + P2 + … + Pb -D1 - D2 –Da。
Hotham市长已在水库安装了传感器,但不是在管道中。因此,他可以观察到在每个水库水的净变化,但并知道有多少水被排出或注入各个管道。
给出水管连接图和每个水库供水量的净变化,编写程序决定这些信息是否能确定一个唯一的计划。如果各个水管排出或注入的水量是唯一的,那么该计划可以被唯一确定。请注意,并不是每一个水管中的注水量和排水量都相等。如果恰好有一个可能性,你的程序应该打印出来。
Input
输入的第一行包含两个整数:N:Hotham的水库数 和M:管道数。
接下来的N行每行包含一个整数ci:表示水库i(1≤i≤N)的水量净变化。
再接下来的M行每行包含两个整数ui和vi(1≤i≤M)。每一行分别表示,水库 ui和vi之间有管道连接(1≤ui,vi≤N)。
Output
如果Jester的计划不能被唯一确定,你的程序则输出0。否则,你的程序应该输出M行,每一行包含一个整数Xi(1≤i≤M)。 如果Jester从连接水库ui和vi间的管道里排水,该管道的排水量为di立方米/秒,则设Xi=-di。如果Jester往连接水库ui和vi间的管道里注水,该管道的注水量为pi立方米/秒,则Xi=pi。如果Jester对连接水库ui和vi间的管道既不排水也不注水,则Xi =0。
Sample Input
输入1:
4 3
-1
1
-3
1
1 2
1 3
1 4
输入2:
4 5
1
2
1
2
1 2
2 3
3 4
4 1
1 3
Sample Output
输出1:
2
-6
2
输出2:
0
Data Constraint
1
≤
N
≤
100000
1≤N≤100 000
1≤N≤100000
1
≤
M
≤
500000
1≤M≤500 000
1≤M≤500000
−
1
0
9
≤
C
i
≤
1
0
9
-10^9≤C_i≤10^9
−109≤Ci≤109
如果方案唯一,则
−
1
0
9
≤
X
i
≤
1
0
9
-10^9≤Xi≤10^9
−109≤Xi≤109
30%的数据满足供水系统的网络是一棵树。
题解
- (题目最早竟然没看懂,什么什么又是D的又是P的,后来发现这是同一个东西)
- 很显然可以把每条边看作一个未知数,把每个点看作一条方程,
- 那么它们可以组成一个方程组……
- And then?
- 一秒想到高斯消元,
- 一秒看到数据,知道这不可能。。。
- 所以怎么做?
- 把所有的 C i C_i Ci乘 2 2 2,
- 先分类讨论:
- 一、 m > n m>n m>n,
- 未知数个数大于方程组个数,没有唯一解, A n s = 0 Ans=0 Ans=0。
- 二、 m = n − 1 m=n-1 m=n−1
- 这就是一棵树,非常好求解(可能无解),
- 从叶子节点向上,每次可以知道当前点 x x x与 C x C_x Cx,与它相连的只有上面的一条边 e e e,
- 那么自然可以得出 e e e的解 s [ e ] = C x s[e]=C_x s[e]=Cx,再对应的 C f a i C_{fa_i} Cfai减去 s [ e ] s[e] s[e],
- 这样下去 一直到根,判断 C r o o t = 0 C_{root}=0 Croot=0那么有唯一解,否则无解。
- 三、 m = n m=n m=n
- 一个优美的 n n n元一次方程组,但也不能用解方程组的常规办法,
- 首先可以知道,这是个环套树(只有一个环),
- 那么随便乱搜把这个环和对应的边找出来,
- 如果是偶环,自己随便推一推即可知道要么无解,要么有无穷多解,所以不成立;
- 如果是奇环,
- 以环中每个点为根分别进行情况二中的步骤,
- 但与之不同的是,不需判断任何东西,每个点的 C C C剩多少就是多少,
- 之后就只剩一个单纯的环了,
- 可以发现有方程组是这样的:( m m m是环中的点数)
- x 1 + x 2 = C 1 x_1+x_2=C_1 x1+x2=C1
- x 2 + x 3 = C 2 x_2+x_3=C_2 x2+x3=C2
- … … …… ……
- x m + x 1 = C m x_m+x_1=C_m xm+x1=Cm
- 按照小学奥数题的办法,
- 先计算出 s u m = ∑ i = 1 m x i sum=sum_{i=1}^mx_i sum=∑i=1mxi,它实际就等于 ∑ i = 1 m C i 2 frac{sum_{i=1}^mC_i}{2} 2∑i=1mCi,
- 接着举个例子,
- 如果 i i i是奇数,设 i = 3 i=3 i=3, m = 7 m=7 m=7,
- s = s u m − x 1 − x 2 − x 4 − x 5 − x 6 − x 7 = s u m − C 1 − C 4 − C 6 s=sum-x_1-x_2-x_4-x_5-x_6-x_7=sum-C_1-C_4-C_6 s=sum−x1−x2−x4−x5−x6−x7=sum−C1−C4−C6,
- 如果 i i i是偶数,设 i = 4 i=4 i=4, m − 7 m-7 m−7,
- s = s u m − x 2 − x 3 − x 5 − x 6 − x 7 − x 1 = s u m − C 2 − C 5 − C 7 s=sum-x_2-x_3-x_5-x_6-x_7-x_1=sum-C_2-C_5-C_7 s=sum−x2−x3−x5−x6−x7−x1=sum−C2−C5−C7,
- 分奇偶性各维护一个 C C C的前缀和,再用 s u m sum sum减去前面一段和后面一段。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100010
int a[N],to[N*2],d[N*2],next[N*2],last[N],len=0;
int bz[N],t[N],g[N],c[N],s[N],st[N],sg[N],ss=0;
struct
{
int x,y;
}b[N];
void add(int x,int y,int id)
{
to[++len]=y,d[len]=id;
next[len]=last[x];
last[x]=len;
}
int dfs(int x,int fa)
{
int s=a[x];
for(int i=last[x];i;i=next[i]) if(to[i]!=fa&&!bz[to[i]])
{
c[d[i]]=dfs(to[i],x);
s-=c[d[i]];
}
return s;
}
void dg(int x,int fa)
{
bz[x]=1;
t[++t[0]]=x;
for(int i=last[x];i;i=next[i]) if(to[i]!=fa)
{
if(!bz[0]&&bz[to[i]])
{
bz[0]=1;
g[t[0]]=d[i];
int ok=0;
for(int j=1;j<=t[0];j++)
{
if(t[j]==to[i]) ok=1;
if(ok)
{
st[++ss]=t[j];
sg[ss]=g[j];
}
}
return;
}
if(!bz[0]) g[t[0]]=d[i],dg(to[i],x);
}
if(bz[0]) return;
t[0]--;
}
int main()
{
int n,m,i,x,y;
scanf("%d%d",&n,&m);
if(m>n)
{
printf("0");
return 0;
}
for(i=1;i<=n;i++) scanf("%d",&a[i]),a[i]*=2;
for(i=1;i<=m;i++) scanf("%d%d",&x,&y),add(x,y,i),add(y,x,i);
if(m==n-1)
{
if(dfs(1,0)!=0) printf("0");
else for(i=1;i<=m;i++) printf("%d
",c[i]);
return 0;
}
dg(1,0);
if(ss%2==0)
{
printf("0");
return 0;
}
memset(bz,0,sizeof(bz));
for(i=1;i<=ss;i++) bz[st[i]]=1;
int sum=0;
for(i=1;i<=ss;i++)
{
a[st[i]]=dfs(st[i],0);
if(i==1) s[i]=a[st[i]]; else s[i]=s[i-2]+a[st[i]];
sum+=a[st[i]];
}
sum/=2;
for(i=1;i<=ss;i++)
if(i%2==1) c[sg[i]]=sum-s[ss]+s[i]-s[i-1]; else c[sg[i]]=sum-s[ss-1]+s[i]-s[i-1];
for(i=1;i<=m;i++) printf("%d
",c[i]);
return 0;
}